Pārlūkot izejas kodu

SONAR-17515 Add an info box on the authentication settings page to advertise about the login message feature

tags/9.8.0.63668
Philippe Perrin pirms 1 gada
vecāks
revīzija
47012e64f3

+ 1
- 1
server/sonar-web/src/main/js/apps/settings/components/__tests__/__snapshots__/AdditionalCategories-test.tsx.snap Parādīt failu

@@ -120,7 +120,7 @@ exports[`should render additional categories component correctly 5`] = `
`;

exports[`should render additional categories component correctly 6`] = `
<Authentication
<withAvailableFeaturesContext(Authentication)
categories={Array []}
component={
Object {

+ 25
- 2
server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx Parādīt failu

@@ -20,7 +20,11 @@
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
import withAvailableFeatures, {
WithAvailableFeaturesProps
} from '../../../../app/components/available-features/withAvailableFeatures';
import DocLink from '../../../../components/common/DocLink';
import Link from '../../../../components/common/Link';
import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
import BoxedTabs, { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
import { Alert } from '../../../../components/ui/Alert';
@@ -28,6 +32,7 @@ import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
import { searchParamsToQuery } from '../../../../helpers/urls';
import { AlmKeys } from '../../../../types/alm-settings';
import { Feature } from '../../../../types/features';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { AUTHENTICATION_CATEGORY } from '../../constants';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
@@ -65,7 +70,7 @@ function renderDevOpsIcon(key: string) {
);
}

export default function Authentication(props: Props) {
export function Authentication(props: Props & WithAvailableFeaturesProps) {
const { definitions } = props;

const [query, setSearchParams] = useSearchParams();
@@ -112,7 +117,23 @@ export default function Authentication(props: Props) {
<h1 className="page-title">{translate('settings.authentication.title')}</h1>
</header>

<div className="spacer-top huge-spacer-bottom">
{props.hasFeature(Feature.LoginMessage) && (
<Alert variant="info">
<FormattedMessage
id="settings.authentication.custom_message_information"
defaultMessage={translate('settings.authentication.custom_message_information')}
values={{
link: (
<Link to="/admin/settings?category=general#sonar.login.message">
{translate('settings.authentication.custom_message_information.link')}
</Link>
)
}}
/>
</Alert>
)}

<div className="big-spacer-top huge-spacer-bottom">
<p>{translate('settings.authentication.description')}</p>
</div>

@@ -171,3 +192,5 @@ export default function Authentication(props: Props) {
</>
);
}

export default withAvailableFeatures(Authentication);

+ 128
- 96
server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-test.tsx Parādīt failu

@@ -21,8 +21,10 @@ import { screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
import { mockDefinition } from '../../../../../helpers/mocks/settings';
import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import { Feature } from '../../../../../types/features';
import { ExtendedSettingDefinition, SettingType } from '../../../../../types/settings';
import Authentication from '../Authentication';

@@ -86,103 +88,133 @@ it('should render tabs and allow navigation', async () => {
);
});

it('should allow user to test the configuration', async () => {
const user = userEvent.setup();

const definitions = [
mockDefinition({
key: 'sonar.auth.saml.certificate.secured',
category: 'authentication',
subCategory: 'saml',
name: 'Certificate',
description: 'Secured certificate',
type: SettingType.PASSWORD
}),
mockDefinition({
key: 'sonar.auth.saml.enabled',
category: 'authentication',
subCategory: 'saml',
name: 'Enabled',
description: 'To enable the flag',
type: SettingType.BOOLEAN
})
];

renderAuthentication(definitions);

await user.click(await screen.findByText('settings.almintegration.form.secret.update_field'));

await user.click(screen.getByRole('textbox', { name: 'Certificate' }));
await user.keyboard('new certificate');

expect(screen.getByText('settings.authentication.saml.form.test')).toHaveClass('disabled');

await user.click(screen.getByRole('button', { name: 'settings.authentication.saml.form.save' }));

expect(screen.getByText('settings.authentication.saml.form.test')).not.toHaveClass('disabled');
describe('SAML tab', () => {
it('should allow user to test the configuration', async () => {
const user = userEvent.setup();

const definitions = [
mockDefinition({
key: 'sonar.auth.saml.certificate.secured',
category: 'authentication',
subCategory: 'saml',
name: 'Certificate',
description: 'Secured certificate',
type: SettingType.PASSWORD
}),
mockDefinition({
key: 'sonar.auth.saml.enabled',
category: 'authentication',
subCategory: 'saml',
name: 'Enabled',
description: 'To enable the flag',
type: SettingType.BOOLEAN
})
];

renderAuthentication(definitions);

await user.click(await screen.findByText('settings.almintegration.form.secret.update_field'));

await user.click(screen.getByRole('textbox', { name: 'Certificate' }));
await user.keyboard('new certificate');

expect(screen.getByText('settings.authentication.saml.form.test')).toHaveClass('disabled');

await user.click(
screen.getByRole('button', { name: 'settings.authentication.saml.form.save' })
);

expect(screen.getByText('settings.authentication.saml.form.test')).not.toHaveClass('disabled');
});

it('should allow user to edit fields and save configuration', async () => {
const user = userEvent.setup();
const definitions = mockDefinitionFields;
renderAuthentication(definitions);

expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'true');
// update fields
await user.click(screen.getByRole('textbox', { name: 'test1' }));
await user.keyboard('new test1');

await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('new test2');
// check if enable is allowed after updating
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'false');

// reset value
await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('{Control>}a{/Control}{Backspace}');
await user.click(
screen.getByRole('button', { name: 'settings.authentication.saml.form.save' })
);
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'true');

await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('new test2');
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'false');

expect(
screen.getByRole('button', { name: 'settings.almintegration.form.secret.update_field' })
).toBeInTheDocument();
await user.click(
screen.getByRole('button', { name: 'settings.almintegration.form.secret.update_field' })
);
// check for secure fields
expect(screen.getByRole('textbox', { name: 'Certificate' })).toBeInTheDocument();
await user.click(screen.getByRole('textbox', { name: 'Certificate' }));
await user.keyboard('new certificate');
// enable the configuration
await user.click(screen.getByRole('button', { name: 'off' }));
expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();

await user.click(
screen.getByRole('button', { name: 'settings.authentication.saml.form.save' })
);
expect(screen.getByText('settings.authentication.saml.form.save_success')).toBeInTheDocument();
// check after switching tab that the flag is still enabled
await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
await user.click(screen.getByRole('tab', { name: 'SAML' }));

expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();
});

it('should handle and show errors to the user', async () => {
const user = userEvent.setup();
const definitions = mockDefinitionFields;
renderAuthentication(definitions);

await user.click(screen.getByRole('textbox', { name: 'test1' }));
await user.keyboard('value');
await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('{Control>}a{/Control}error');
await user.click(
screen.getByRole('button', { name: 'settings.authentication.saml.form.save' })
);
expect(screen.getByText('settings.authentication.saml.form.save_partial')).toBeInTheDocument();
});

it('should not display the login message feature info box', () => {
renderAuthentication([]);

expect(
screen.queryByText('settings.authentication.custom_message_information')
).not.toBeInTheDocument();
});

it('should display the login message feature info box', () => {
renderAuthentication([], [Feature.LoginMessage]);

expect(
screen.getByText('settings.authentication.custom_message_information')
).toBeInTheDocument();
});
});

it('should allow user to edit fields and save configuration', async () => {
const user = userEvent.setup();
const definitions = mockDefinitionFields;
renderAuthentication(definitions);

expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'true');
// update fields
await user.click(screen.getByRole('textbox', { name: 'test1' }));
await user.keyboard('new test1');

await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('new test2');
// check if enable is allowed after updating
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'false');

// reset value
await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('{Control>}a{/Control}{Backspace}');
await user.click(screen.getByRole('button', { name: 'settings.authentication.saml.form.save' }));
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'true');

await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('new test2');
expect(screen.getByRole('button', { name: 'off' })).toHaveAttribute('aria-disabled', 'false');

expect(
screen.getByRole('button', { name: 'settings.almintegration.form.secret.update_field' })
).toBeInTheDocument();
await user.click(
screen.getByRole('button', { name: 'settings.almintegration.form.secret.update_field' })
function renderAuthentication(definitions: ExtendedSettingDefinition[], features: Feature[] = []) {
renderComponent(
<AvailableFeaturesContext.Provider value={features}>
<Authentication definitions={definitions} />
</AvailableFeaturesContext.Provider>
);
// check for secure fields
expect(screen.getByRole('textbox', { name: 'Certificate' })).toBeInTheDocument();
await user.click(screen.getByRole('textbox', { name: 'Certificate' }));
await user.keyboard('new certificate');
// enable the configuration
await user.click(screen.getByRole('button', { name: 'off' }));
expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();

await user.click(screen.getByRole('button', { name: 'settings.authentication.saml.form.save' }));
expect(screen.getByText('settings.authentication.saml.form.save_success')).toBeInTheDocument();
// check after switching tab that the flag is still enabled
await user.click(screen.getByRole('tab', { name: 'github GitHub' }));
await user.click(screen.getByRole('tab', { name: 'SAML' }));

expect(screen.getByRole('button', { name: 'on' })).toBeInTheDocument();
});

it('should handle and show error to the user', async () => {
const user = userEvent.setup();
const definitions = mockDefinitionFields;
renderAuthentication(definitions);

await user.click(screen.getByRole('textbox', { name: 'test1' }));
await user.keyboard('value');
await user.click(screen.getByRole('textbox', { name: 'test2' }));
await user.keyboard('{Control>}a{/Control}error');
await user.click(screen.getByRole('button', { name: 'settings.authentication.saml.form.save' }));
expect(screen.getByText('settings.authentication.saml.form.save_partial')).toBeInTheDocument();
});

function renderAuthentication(definitions: ExtendedSettingDefinition[]) {
renderComponent(<Authentication definitions={definitions} />);
}

+ 2
- 0
sonar-core/src/main/resources/org/sonar/l10n/core.properties Parādīt failu

@@ -1271,6 +1271,8 @@ settings.almintegration.feature.alm_repo_import.disabled.no_url=This feature is
settings.almintegration.tabs.authentication_moved=You can delegate authentication to this DevOps Platform. The relevant settings are under the {link} section.

settings.authentication.title=Authentication
settings.authentication.custom_message_information=You can define a custom log-in message to appear on the log-in page to help your users authenticate. The relevant settings are available under the {link} section.
settings.authentication.custom_message_information.link=General
settings.authentication.description=The following settings allow you to delegate authentication via SAML, or any of the following DevOps Platforms: GitHub, GitLab, and Bitbucket.
settings.authentication.help=If you need help setting up authentication, read our dedicated {link}.
settings.authentication.help.link=documentation

Notiek ielāde…
Atcelt
Saglabāt