Skip to content

Commit

Permalink
refactor: [web] export awsoidc integration UI state and components (#…
Browse files Browse the repository at this point in the history
…47170)

* refactor: export awsoidc integration UI state and components

* awsoidc ui test, copy edit and move requiredMatchingRoleNameAndRoleArn to rules.ts

* update ShowConfigurationScript copy to explain what the command does

* abstract validAwsIAMRoleName and export it
  • Loading branch information
flyinghermit authored Oct 7, 2024
1 parent de6d19b commit bc2cb80
Show file tree
Hide file tree
Showing 9 changed files with 631 additions and 174 deletions.
62 changes: 52 additions & 10 deletions web/packages/shared/components/Validation/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,22 +105,19 @@ const isIamRoleNameValid = roleName => {
);
};

const requiredIamRoleName: Rule = value => () => {
if (!value) {
return {
valid: false,
message: 'IAM role name required',
};
}

if (value.length > 64) {
/**
* @param name validAwsIAMRoleName verifies if the given value is a
* valid AWS IAM role name.
*/
const validAwsIAMRoleName = (name: string): ValidationResult => {
if (name.length > 64) {
return {
valid: false,
message: 'name should be <= 64 characters',
};
}

if (!isIamRoleNameValid(value)) {
if (!isIamRoleNameValid(name)) {
return {
valid: false,
message: 'name can only contain characters @ = , . + - and alphanumerics',
Expand All @@ -132,6 +129,23 @@ const requiredIamRoleName: Rule = value => () => {
};
};

/**
* requiredIamRoleName is a required field and checks for a
* value which should also be a valid AWS IAM role name.
* @param name is a role name.
* @returns ValidationResult
*/
const requiredIamRoleName: Rule = name => (): ValidationResult => {
if (!name) {
return {
valid: false,
message: 'IAM role name required',
};
}

return validAwsIAMRoleName(name);
};

/**
* ROLE_ARN_REGEX_STR checks provided arn (amazon resource names) is
* somewhat in the format as documented here:
Expand Down Expand Up @@ -196,6 +210,32 @@ const requiredEmailLike: Rule<string, EmailValidationResult> = email => () => {
};
};

/**
* requiredMatchingRoleNameAndRoleArn checks if a given roleArn is a valid AWS
* IAM role ARN format and contains a given roleName.
*
* @param roleName Role name that is used to match role ARN.
* @param roleArn Role ARN which is to be tested for a valid AWS IAM role ARN format.
*/
const requiredMatchingRoleNameAndRoleArn =
(roleName: string) => (roleArn: string) => () => {
const regex = new RegExp(
'^arn:aws.*:iam::\\d{12}:role\\/(' + roleName + ')$'
);

if (regex.test(roleArn)) {
return {
valid: true,
};
}

return {
valid: false,
message:
'invalid role ARN, double check you copied and pasted the correct output',
};
};

/**
* A rule function that combines multiple inner rule functions. All rules must
* return `valid`, otherwise it returns a comma separated string containing all
Expand Down Expand Up @@ -233,4 +273,6 @@ export {
requiredIamRoleName,
requiredEmailLike,
requiredAll,
requiredMatchingRoleNameAndRoleArn,
validAwsIAMRoleName,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/**
* Teleport
* Copyright (C) 2024 Gravitational, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/

import {
fireEvent,
render,
screen,
userEvent,
waitFor,
} from 'design/utils/testing';
import { MemoryRouter } from 'react-router';

import { userEventService } from 'teleport/services/userEvent';

import { AwsOidc } from './AwsOidc';

test('render', async () => {
jest
.spyOn(userEventService, 'captureIntegrationEnrollEvent')
.mockImplementation();
render(
<MemoryRouter>
<AwsOidc />
</MemoryRouter>
);

expect(screen.getByText(/Set up your AWS account/i)).toBeInTheDocument();
expect(
screen.getByLabelText(/Give this AWS integration a name/i)
).toBeInTheDocument();
expect(
screen.getByLabelText(
/Give a name for an AWS IAM role this integration will create/i
)
).toBeInTheDocument();
});

test('generate command', async () => {
const user = userEvent.setup({ delay: null });
jest
.spyOn(userEventService, 'captureIntegrationEnrollEvent')
.mockImplementation();

window.prompt = jest.fn();

render(
<MemoryRouter>
<AwsOidc />
</MemoryRouter>
);

const pluginConfig = {
name: 'integration-name',
roleName: 'integration-role-name',
};

expect(screen.getByText(/Set up your AWS account/i)).toBeInTheDocument();
fireEvent.change(screen.getByLabelText(/Give this AWS integration a name/i), {
target: { value: pluginConfig.name },
});
fireEvent.change(
screen.getByLabelText(
/Give a name for an AWS IAM role this integration will create/i
),
{
target: { value: pluginConfig.roleName },
}
);

fireEvent.click(screen.getByRole('button', { name: /Generate Command/i }));

const commandBoxEl = screen.getByText(/AWS CloudShell/i, { exact: false });
await waitFor(() => {
expect(commandBoxEl).toBeInTheDocument();
});

// the first element found shows AWS tags added by OIDC integraiton.
// second element is the command copy box.
await user.click(screen.getAllByTestId('btn-copy')[1]);
const clipboardText = await navigator.clipboard.readText();
expect(clipboardText).toContain(`integrationName=${pluginConfig.name}`);
expect(clipboardText).toContain(`role=${pluginConfig.roleName}`);
});
Loading

0 comments on commit bc2cb80

Please sign in to comment.