Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Display permission banner on Enroll Resource page #50657

Merged
merged 1 commit into from
Jan 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,18 +16,32 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

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 = () => {
Expand Down Expand Up @@ -1112,6 +1126,56 @@ describe('sorting Connect My Computer', () => {
});
});

test('displays an info banner if lacking "all" 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(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<SelectResource onSelect={() => {}} />
</ContextProvider>
</MemoryRouter>
);

await waitFor(() => {
expect(
screen.getByText(/You cannot add new resources./i)
).toBeInTheDocument();
});
});

test('does not display erorr banner if user has "some" 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(
<MemoryRouter>
<ContextProvider ctx={ctx}>
<SelectResource onSelect={() => {}} />
</ContextProvider>
</MemoryRouter>
);

expect(
screen.queryByText(/You cannot add new resources./i)
).not.toBeInTheDocument();
});

describe('filterResources', () => {
it('filters out resources based on supportedPlatforms', () => {
const winAndLinux = makeResourceSpec({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,16 @@
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import { useEffect, useState, type ComponentPropsWithoutRef } from 'react';
import {
useEffect,
useMemo,
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';
Expand Down Expand Up @@ -64,19 +69,49 @@ type UrlLocationState = {
searchKeywords: string;
};

function getDefaultResources(
includeEnterpriseResources: boolean
): ResourceSpec[] {
const RESOURCES = includeEnterpriseResources
? BASE_RESOURCES
: [...BASE_RESOURCES, ...SAML_APPLICATIONS];
return RESOURCES;
}

export function SelectResource({ onSelect }: SelectResourceProps) {
const ctx = useTeleport();
const location = useLocation<UrlLocationState>();
const history = useHistory();
const { preferences } = useUser();

const [search, setSearch] = useState('');
const [resources, setResources] = useState<ResourceSpec[]>([]);
const [defaultResources, setDefaultResources] = useState<ResourceSpec[]>([]);
const { acl, authType } = ctx.storeUser.state;
const platform = getPlatform();
const defaultResources: ResourceSpec[] = useMemo(
() =>
sortResources(
// Apply access check to each resource.
addHasAccessField(
acl,
filterResources(
platform,
authType,
getDefaultResources(cfg.isEnterprise)
)
),
preferences,
storageService.getOnboardDiscover()
),
[acl, authType, platform, preferences]
);
const [resources, setResources] = useState(defaultResources);

// 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 && defaultResources.some(r => r.hasAccess);

const [showApp, setShowApp] = useState(false);
const RESOURCES = !cfg.isEnterprise
? BASE_RESOURCES
: [...BASE_RESOURCES, ...SAML_APPLICATIONS];

function onSearch(s: string, customList?: ResourceSpec[]) {
const list = customList || defaultResources;
Expand All @@ -95,23 +130,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,
preferences,
onboardDiscover
);
setDefaultResources(sortedResources);

// A user can come to this screen by clicking on
// a `add <specific-resource-type>` button.
// We sort the list by the specified resource type,
Expand All @@ -127,26 +145,32 @@ export function SelectResource({ onSelect }: SelectResourceProps) {
) {
const sortedResourcesByKind = sortResourcesByKind(
resourceKindSpecifiedByUrlLoc,
sortedResources
defaultResources
);
onSearch(resourceKindSpecifiedByUrlLoc, sortedResourcesByKind);
return;
}

const searchKeywordSpecifiedByUrlLoc = location.state?.searchKeywords;
if (searchKeywordSpecifiedByUrlLoc) {
onSearch(searchKeywordSpecifiedByUrlLoc, sortedResources);
onSearch(searchKeywordSpecifiedByUrlLoc, defaultResources);
return;
}

setResources(sortedResources);
setResources(defaultResources);
// Processing of the lists should only happen once on init.
// User perms remain static and URL loc state does not change.
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

return (
<Box>
{!canAddResources && (
<Alert kind="info" mt={5}>
You cannot add new resources. Reach out to your Teleport administrator
for additional permissions.
</Alert>
)}
<FeatureHeader>
<FeatureHeaderTitle>Select Resource To Add</FeatureHeaderTitle>
</FeatureHeader>
Expand Down
5 changes: 4 additions & 1 deletion web/packages/teleport/src/features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,10 @@ export class FeatureIntegrationEnroll implements TeleportFeature {
};

hasAccess(flags: FeatureFlags) {
return flags.enrollIntegrations;
if (cfg.hideInaccessibleFeatures) {
return flags.enrollIntegrations;
}
return true;
}

navigationItem = {
Expand Down
Loading