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

[v17] Add database and Windows access sections to the role editor #49211

Merged
merged 2 commits into from
Nov 20, 2024
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
3 changes: 3 additions & 0 deletions web/packages/shared/components/FieldSelect/shared.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,7 @@ export function splitSelectProps<
onChange,
onInputChange,
onKeyDown,
openMenuOnClick,
options,
placeholder,
rule,
Expand Down Expand Up @@ -239,6 +240,7 @@ export function splitSelectProps<
onInputChange,
onKeyDown,
options,
openMenuOnClick,
placeholder,
stylesConfig,
value,
Expand Down Expand Up @@ -285,6 +287,7 @@ type KeysRemovedFromOthers =
| 'onChange'
| 'onInputChange'
| 'onKeyDown'
| 'openMenuOnClick'
| 'options'
| 'placeholder'
| 'rule'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,24 @@ import { createTeleportContext } from 'teleport/mocks/contexts';
import {
AccessSpec,
AppAccessSpec,
DatabaseAccessSpec,
KubernetesAccessSpec,
newAccessSpec,
newRole,
roleToRoleEditorModel,
ServerAccessSpec,
StandardEditorModel,
WindowsDesktopAccessSpec,
} from './standardmodel';
import {
AppAccessSpecSection,
DatabaseAccessSpecSection,
KubernetesAccessSpecSection,
SectionProps,
ServerAccessSpecSection,
StandardEditor,
StandardEditorProps,
WindowsDesktopAccessSpecSection,
} from './StandardEditor';

const TestStandardEditor = (props: Partial<StandardEditorProps>) => {
Expand Down Expand Up @@ -76,6 +80,8 @@ test('adding and removing sections', async () => {
'Kubernetes',
'Servers',
'Applications',
'Databases',
'Windows Desktops',
]);

await user.click(screen.getByRole('menuitem', { name: 'Servers' }));
Expand All @@ -84,7 +90,12 @@ test('adding and removing sections', async () => {
await user.click(
screen.getByRole('button', { name: 'Add New Specifications' })
);
expect(getAllMenuItemNames()).toEqual(['Kubernetes', 'Applications']);
expect(getAllMenuItemNames()).toEqual([
'Kubernetes',
'Applications',
'Databases',
'Windows Desktops',
]);

await user.click(screen.getByRole('menuitem', { name: 'Kubernetes' }));
expect(getAllSectionNames()).toEqual([
Expand Down Expand Up @@ -353,6 +364,62 @@ test('AppAccessSpecSection', async () => {
} as AppAccessSpec);
});

test('DatabaseAccessSpecSection', async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(
<StatefulSection<DatabaseAccessSpec>
component={DatabaseAccessSpecSection}
defaultValue={newAccessSpec('db')}
onChange={onChange}
/>
);

await user.click(screen.getByRole('button', { name: 'Add a Label' }));
await user.type(screen.getByPlaceholderText('label key'), 'env');
await user.type(screen.getByPlaceholderText('label value'), 'prod');
await selectEvent.create(screen.getByLabelText('Database Names'), 'stuff', {
createOptionText: 'Database Name: stuff',
});
await selectEvent.create(screen.getByLabelText('Database Users'), 'mary', {
createOptionText: 'Database User: mary',
});
await selectEvent.create(screen.getByLabelText('Database Roles'), 'admin', {
createOptionText: 'Database Role: admin',
});
expect(onChange).toHaveBeenLastCalledWith({
kind: 'db',
labels: [{ name: 'env', value: 'prod' }],
names: [expect.objectContaining({ label: 'stuff', value: 'stuff' })],
roles: [expect.objectContaining({ label: 'admin', value: 'admin' })],
users: [expect.objectContaining({ label: 'mary', value: 'mary' })],
} as DatabaseAccessSpec);
});

test('WindowsDesktopAccessSpecSection', async () => {
const user = userEvent.setup();
const onChange = jest.fn();
render(
<StatefulSection<WindowsDesktopAccessSpec>
component={WindowsDesktopAccessSpecSection}
defaultValue={newAccessSpec('windows_desktop')}
onChange={onChange}
/>
);

await user.click(screen.getByRole('button', { name: 'Add a Label' }));
await user.type(screen.getByPlaceholderText('label key'), 'os');
await user.type(screen.getByPlaceholderText('label value'), 'win-xp');
await selectEvent.create(screen.getByLabelText('Logins'), 'julio', {
createOptionText: 'Login: julio',
});
expect(onChange).toHaveBeenLastCalledWith({
kind: 'windows_desktop',
labels: [{ name: 'os', value: 'win-xp' }],
logins: [expect.objectContaining({ label: 'julio', value: 'julio' })],
} as WindowsDesktopAccessSpec);
});

const reactSelectValueContainer = (input: HTMLInputElement) =>
// eslint-disable-next-line testing-library/no-node-access
input.closest('.react-select__value-container');
127 changes: 126 additions & 1 deletion web/packages/teleport/src/Roles/RoleEditor/StandardEditor.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ import {
kubernetesVerbOptions,
KubernetesResourceModel,
AppAccessSpec,
DatabaseAccessSpec,
WindowsDesktopAccessSpec,
} from './standardmodel';
import { EditorSaveCancelButton } from './Shared';
import { RequiresResetToStandard } from './RequiresResetToStandard';
Expand Down Expand Up @@ -359,7 +361,13 @@ const Section = ({
/**
* All access spec kinds, in order of appearance in the resource kind dropdown.
*/
const allAccessSpecKinds: AccessSpecKind[] = ['kube_cluster', 'node', 'app'];
const allAccessSpecKinds: AccessSpecKind[] = [
'kube_cluster',
'node',
'app',
'db',
'windows_desktop',
];

/** Maps access specification kind to UI component configuration. */
const specSections: Record<
Expand All @@ -385,6 +393,16 @@ const specSections: Record<
tooltip: 'Configures access to applications',
component: AppAccessSpecSection,
},
db: {
title: 'Databases',
tooltip: 'Configures access to databases',
component: DatabaseAccessSpecSection,
},
windows_desktop: {
title: 'Windows Desktops',
tooltip: 'Configures access to Windows desktops',
component: WindowsDesktopAccessSpecSection,
},
};

/**
Expand Down Expand Up @@ -436,6 +454,7 @@ export function ServerAccessSpecSection({
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.logins}
onChange={logins => onChange?.({ ...value, logins })}
mt={3}
Expand All @@ -460,6 +479,7 @@ export function KubernetesAccessSpecSection({
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.groups}
onChange={groups => onChange?.({ ...value, groups })}
/>
Expand Down Expand Up @@ -636,6 +656,111 @@ export function AppAccessSpecSection({
);
}

export function DatabaseAccessSpecSection({
value,
isProcessing,
onChange,
}: SectionProps<DatabaseAccessSpec>) {
return (
<>
<Box mb={3}>
<Text typography="body3" mb={1}>
Labels
</Text>
<LabelsInput
disableBtns={isProcessing}
labels={value.labels}
setLabels={labels => onChange?.({ ...value, labels })}
/>
</Box>
<FieldSelectCreatable
isMulti
label="Database Names"
toolTipContent={
<>
List of database names that this role is allowed to connect to.
Special value <MarkInverse>*</MarkInverse> means any name.
</>
}
isDisabled={isProcessing}
formatCreateLabel={label => `Database Name: ${label}`}
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.names}
onChange={names => onChange?.({ ...value, names })}
/>
<FieldSelectCreatable
isMulti
label="Database Users"
toolTipContent={
<>
List of database users that this role is allowed to connect as.
Special value <MarkInverse>*</MarkInverse> means any user.
</>
}
isDisabled={isProcessing}
formatCreateLabel={label => `Database User: ${label}`}
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.users}
onChange={users => onChange?.({ ...value, users })}
/>
<FieldSelectCreatable
isMulti
label="Database Roles"
toolTipContent="If automatic user provisioning is available, this is the list of database roles that will be assigned to the database user after it's created"
isDisabled={isProcessing}
formatCreateLabel={label => `Database Role: ${label}`}
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.roles}
onChange={roles => onChange?.({ ...value, roles })}
mb={0}
/>
</>
);
}

export function WindowsDesktopAccessSpecSection({
value,
isProcessing,
onChange,
}: SectionProps<WindowsDesktopAccessSpec>) {
return (
<>
<Box mb={3}>
<Text typography="body3" mb={1}>
Labels
</Text>
<LabelsInput
disableBtns={isProcessing}
labels={value.labels}
setLabels={labels => onChange?.({ ...value, labels })}
/>
</Box>
<FieldSelectCreatable
isMulti
label="Logins"
toolTipContent="List of desktop logins that this role is allowed to use"
isDisabled={isProcessing}
formatCreateLabel={label => `Login: ${label}`}
components={{
DropdownIndicator: null,
}}
openMenuOnClick={false}
value={value.logins}
onChange={logins => onChange?.({ ...value, logins })}
/>
</>
);
}

export const EditorWrapper = styled(Box)<{ mute?: boolean }>`
opacity: ${p => (p.mute ? 0.4 : 1)};
pointer-events: ${p => (p.mute ? 'none' : '')};
Expand Down
Loading
Loading