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

Add the options panel to role editor #49801

Merged
merged 8 commits into from
Dec 10, 2024
Merged
Show file tree
Hide file tree
Changes from 3 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
5 changes: 5 additions & 0 deletions api/types/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ import (
)

const (
// The `Kind*` constants in this const block identify resource kinds used for
// storage an/or and access control. Please keep these in sync with the
// `ResourceKind` enum in
// `web/packages/teleport/src/services/resources/types.ts`.

// DefaultAPIGroup is a default group of permissions API,
// lets us to add different permission types
DefaultAPIGroup = "gravitational.io/teleport"
Expand Down
2 changes: 1 addition & 1 deletion web/packages/design/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import CardSuccess, { CardSuccessLogin } from './CardSuccess';
import { Indicator } from './Indicator';
import Input from './Input';
import Label from './Label';
import LabelInput from './LabelInput';
import { LabelInput } from './LabelInput';
import LabelState from './LabelState';
import Link from './Link';
import { Mark } from './Mark';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ export default {
}
return (
<TeleportContextProvider ctx={ctx}>
<Flex flexDirection="column" width="500px" height="800px">
<Flex flexDirection="column" width="700px" height="800px">
<Story />
</Flex>
</TeleportContextProvider>
Expand Down
94 changes: 83 additions & 11 deletions web/packages/teleport/src/Roles/RoleEditor/StandardEditor.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,19 +26,22 @@ import selectEvent from 'react-select-event';
import TeleportContextProvider from 'teleport/TeleportContextProvider';
import { createTeleportContext } from 'teleport/mocks/contexts';

import { ResourceKind } from 'teleport/services/resources';

import {
AccessSpec,
AppAccessSpec,
DatabaseAccessSpec,
KubernetesAccessSpec,
newAccessSpec,
newRole,
roleToRoleEditorModel,
RuleModel,
ServerAccessSpec,
StandardEditorModel,
WindowsDesktopAccessSpec,
} from './standardmodel';
import {
AdminRules,
AppAccessSpecSection,
DatabaseAccessSpecSection,
KubernetesAccessSpecSection,
Expand All @@ -48,7 +51,12 @@ import {
StandardEditorProps,
WindowsDesktopAccessSpecSection,
} from './StandardEditor';
import { validateAccessSpec } from './validation';
import {
AccessSpecValidationResult,
AdminRuleValidationResult,
validateAccessSpec,
validateAdminRule,
} from './validation';

const TestStandardEditor = (props: Partial<StandardEditorProps>) => {
const ctx = createTeleportContext();
Expand Down Expand Up @@ -165,19 +173,21 @@ const getSectionByName = (name: string) =>
// eslint-disable-next-line testing-library/no-node-access
screen.getByRole('heading', { level: 3, name }).closest('details');

const StatefulSection = <S extends AccessSpec>({
function StatefulSection<S, V>({
defaultValue,
component: Component,
onChange,
validatorRef,
validate,
}: {
defaultValue: S;
component: React.ComponentType<SectionProps<S, any>>;
onChange(spec: S): void;
validatorRef?(v: Validator): void;
}) => {
validate(arg: S): V;
}) {
const [model, setModel] = useState<S>(defaultValue);
const validation = validateAccessSpec(model);
const validation = validate(model);
return (
<Validation>
{({ validator }) => {
Expand All @@ -196,20 +206,21 @@ const StatefulSection = <S extends AccessSpec>({
}}
</Validation>
);
};
}

describe('ServerAccessSpecSection', () => {
const setup = () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<ServerAccessSpec>
<StatefulSection<ServerAccessSpec, AccessSpecValidationResult>
component={ServerAccessSpecSection}
defaultValue={newAccessSpec('node')}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={validateAccessSpec}
/>
);
return { user: userEvent.setup(), onChange, validator };
Expand Down Expand Up @@ -258,13 +269,14 @@ describe('KubernetesAccessSpecSection', () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<KubernetesAccessSpec>
<StatefulSection<KubernetesAccessSpec, AccessSpecValidationResult>
component={KubernetesAccessSpecSection}
defaultValue={newAccessSpec('kube_cluster')}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={validateAccessSpec}
/>
);
return { user: userEvent.setup(), onChange, validator };
Expand Down Expand Up @@ -399,13 +411,14 @@ describe('AppAccessSpecSection', () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<AppAccessSpec>
<StatefulSection<AppAccessSpec, AccessSpecValidationResult>
component={AppAccessSpecSection}
defaultValue={newAccessSpec('app')}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={validateAccessSpec}
/>
);
return { user: userEvent.setup(), onChange, validator };
Expand Down Expand Up @@ -476,13 +489,14 @@ describe('DatabaseAccessSpecSection', () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<DatabaseAccessSpec>
<StatefulSection<DatabaseAccessSpec, AccessSpecValidationResult>
component={DatabaseAccessSpecSection}
defaultValue={newAccessSpec('db')}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={validateAccessSpec}
/>
);
return { user: userEvent.setup(), onChange, validator };
Expand Down Expand Up @@ -532,13 +546,14 @@ describe('WindowsDesktopAccessSpecSection', () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<WindowsDesktopAccessSpec>
<StatefulSection<WindowsDesktopAccessSpec, AccessSpecValidationResult>
component={WindowsDesktopAccessSpecSection}
defaultValue={newAccessSpec('windows_desktop')}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={validateAccessSpec}
/>
);
return { user: userEvent.setup(), onChange, validator };
Expand Down Expand Up @@ -569,6 +584,63 @@ describe('WindowsDesktopAccessSpecSection', () => {
});
});

describe('AdminRules', () => {
const setup = () => {
const onChange = jest.fn();
let validator: Validator;
render(
<StatefulSection<RuleModel[], AdminRuleValidationResult[]>
component={AdminRules}
defaultValue={[]}
onChange={onChange}
validatorRef={v => {
validator = v;
}}
validate={rules => rules.map(validateAdminRule)}
/>
);
return { user: userEvent.setup(), onChange, validator };
};

test('editing', async () => {
const { user, onChange } = setup();
await user.click(screen.getByRole('button', { name: 'Add New' }));
await selectEvent.select(screen.getByLabelText('Resources'), [
'db',
'node',
]);
await selectEvent.select(screen.getByLabelText('Permissions'), [
'list',
'read',
]);
expect(onChange).toHaveBeenLastCalledWith([
{
id: expect.any(String),
resources: [
{ label: ResourceKind.Database, value: 'db' },
{ label: ResourceKind.Node, value: 'node' },
],
verbs: [
{ label: 'list', value: 'list' },
{ label: 'read', value: 'read' },
],
},
] as RuleModel[]);
});

test('validation', async () => {
const { user, validator } = setup();
await user.click(screen.getByRole('button', { name: 'Add New' }));
act(() => validator.validate());
expect(
screen.getByText('At least one resource kind is required')
).toBeInTheDocument();
expect(
screen.getByText('At least one permission is required')
).toBeInTheDocument();
});
});

const reactSelectValueContainer = (input: HTMLInputElement) =>
// eslint-disable-next-line testing-library/no-node-access
input.closest('.react-select__value-container');
Loading
Loading