aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-web/src/main/js/api/mocks/AuthenticationServiceMock.ts71
-rw-r--r--server/sonar-web/src/main/js/api/settings.ts14
-rw-r--r--server/sonar-web/src/main/js/apps/quality-profiles/styles.css3
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx56
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx33
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx2
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx94
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx609
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/SamlConfigurationForm.tsx150
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/SamlFormField.tsx23
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/SamlSecuredField.tsx25
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/SamlToggleField.tsx6
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx248
-rw-r--r--server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useLoadSamlSettings.ts160
-rw-r--r--server/sonar-web/src/main/js/apps/settings/styles.css46
-rw-r--r--server/sonar-web/src/main/js/components/controls/RadioCard.tsx3
-rw-r--r--server/sonar-web/src/main/js/helpers/mocks/definitions-list.ts2529
-rw-r--r--server/sonar-web/src/main/js/types/features.ts1
-rw-r--r--server/sonar-web/src/main/js/types/settings.ts2
-rw-r--r--sonar-core/src/main/resources/org/sonar/l10n/core.properties27
21 files changed, 3507 insertions, 601 deletions
diff --git a/server/sonar-web/src/main/js/api/mocks/AuthenticationServiceMock.ts b/server/sonar-web/src/main/js/api/mocks/AuthenticationServiceMock.ts
index 8aba451dd6e..f4986f2d69b 100644
--- a/server/sonar-web/src/main/js/api/mocks/AuthenticationServiceMock.ts
+++ b/server/sonar-web/src/main/js/api/mocks/AuthenticationServiceMock.ts
@@ -21,32 +21,79 @@ import { cloneDeep } from 'lodash';
import { mockSettingValue } from '../../helpers/mocks/settings';
import { BranchParameters } from '../../types/branch-like';
import { SettingDefinition, SettingValue } from '../../types/settings';
-import { getValues, resetSettingValue, setSettingValue } from '../settings';
+import {
+ activateScim,
+ deactivateScim,
+ fetchIsScimEnabled,
+ getValues,
+ resetSettingValue,
+ setSettingValue,
+} from '../settings';
export default class AuthenticationServiceMock {
settingValues: SettingValue[];
+ scimStatus: boolean;
defaulSettingValues: SettingValue[] = [
mockSettingValue({ key: 'test1', value: '' }),
mockSettingValue({ key: 'test2', value: 'test2' }),
- mockSettingValue({ key: 'sonar.auth.saml.certificate.secured' }),
- mockSettingValue({ key: 'sonar.auth.saml.enabled', value: 'false' }),
+ {
+ key: 'sonar.auth.saml.signature.enabled',
+ value: 'false',
+ inherited: true,
+ },
+ {
+ key: 'sonar.auth.saml.enabled',
+ value: 'false',
+ inherited: true,
+ },
+ {
+ key: 'sonar.auth.saml.applicationId',
+ value: 'sonarqube',
+ inherited: true,
+ },
+ {
+ key: 'sonar.auth.saml.providerName',
+ value: 'SAML',
+ inherited: true,
+ },
];
constructor() {
this.settingValues = cloneDeep(this.defaulSettingValues);
- (getValues as jest.Mock).mockImplementation(this.getValuesHandler);
- (setSettingValue as jest.Mock).mockImplementation(this.setValueHandler);
- (resetSettingValue as jest.Mock).mockImplementation(this.resetValueHandler);
+ this.scimStatus = false;
+ jest.mocked(getValues).mockImplementation(this.handleGetValues);
+ jest.mocked(setSettingValue).mockImplementation(this.handleSetValue);
+ jest.mocked(resetSettingValue).mockImplementation(this.handleResetValue);
+ jest.mocked(activateScim).mockImplementation(this.handleActivateScim);
+ jest.mocked(deactivateScim).mockImplementation(this.handleDeactivateScim);
+
+ jest.mocked(fetchIsScimEnabled).mockImplementation(this.handleFetchIsScimEnabled);
}
- getValuesHandler = (data: { keys: string; component?: string } & BranchParameters) => {
- if (data.keys) {
+ handleActivateScim = () => {
+ this.scimStatus = true;
+ return Promise.resolve();
+ };
+
+ handleDeactivateScim = () => {
+ this.scimStatus = false;
+ return Promise.resolve();
+ };
+
+ handleFetchIsScimEnabled = () => {
+ return Promise.resolve(this.scimStatus);
+ };
+
+ handleGetValues = (
+ data: { keys: string[]; component?: string } & BranchParameters
+ ): Promise<SettingValue[]> => {
+ if (data.keys.length > 1) {
return Promise.resolve(this.settingValues.filter((set) => data.keys.includes(set.key)));
}
return Promise.resolve(this.settingValues);
};
- setValueHandler = (definition: SettingDefinition, value: string) => {
+ handleSetValue = (definition: SettingDefinition, value: string | boolean) => {
if (value === 'error') {
const res = new Response('', {
status: 400,
@@ -57,12 +104,14 @@ export default class AuthenticationServiceMock {
}
const updatedSettingValue = this.settingValues.find((set) => set.key === definition.key);
if (updatedSettingValue) {
- updatedSettingValue.value = value;
+ updatedSettingValue.value = String(value);
+ } else {
+ this.settingValues.push({ key: definition.key, value: String(value), inherited: false });
}
return Promise.resolve();
};
- resetValueHandler = (data: { keys: string; component?: string } & BranchParameters) => {
+ handleResetValue = (data: { keys: string; component?: string } & BranchParameters) => {
if (data.keys) {
this.settingValues.forEach((set) => {
if (data.keys.includes(set.key)) {
diff --git a/server/sonar-web/src/main/js/api/settings.ts b/server/sonar-web/src/main/js/api/settings.ts
index 1d81865ddbe..d741cb0bc00 100644
--- a/server/sonar-web/src/main/js/api/settings.ts
+++ b/server/sonar-web/src/main/js/api/settings.ts
@@ -116,3 +116,17 @@ export function encryptValue(value: string): Promise<{ encryptedValue: string }>
export function getLoginMessage(): Promise<{ message: string }> {
return getJSON('/api/settings/login_message').catch(throwGlobalError);
}
+
+export function fetchIsScimEnabled(): Promise<boolean> {
+ return getJSON('/api/scim_management/status')
+ .then((r) => r.enabled)
+ .catch(throwGlobalError);
+}
+
+export function activateScim(): Promise<void> {
+ return post('/api/scim_management/enable').catch(throwGlobalError);
+}
+
+export function deactivateScim(): Promise<void> {
+ return post('/api/scim_management/disable').catch(throwGlobalError);
+}
diff --git a/server/sonar-web/src/main/js/apps/quality-profiles/styles.css b/server/sonar-web/src/main/js/apps/quality-profiles/styles.css
index 4fb6b68c3e7..a62977828a6 100644
--- a/server/sonar-web/src/main/js/apps/quality-profiles/styles.css
+++ b/server/sonar-web/src/main/js/apps/quality-profiles/styles.css
@@ -21,9 +21,6 @@
padding-top: 7px;
}
-.quality-profiles-table-name {
-}
-
.quality-profiles-table-inheritance {
width: 280px;
}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx
new file mode 100644
index 00000000000..57e3e16812c
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionDescription.tsx
@@ -0,0 +1,56 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { Tooltip } from 'design-system/lib';
+import * as React from 'react';
+import { translateWithParameters } from '../../../helpers/l10n';
+import { sanitizeStringRestricted } from '../../../helpers/sanitize';
+import { ExtendedSettingDefinition } from '../../../types/settings';
+import { getPropertyDescription, getPropertyName } from '../utils';
+
+interface Props {
+ definition: ExtendedSettingDefinition;
+}
+
+export default function DefinitionDescription({ definition }: Props) {
+ const propertyName = getPropertyName(definition);
+ const description = getPropertyDescription(definition);
+
+ return (
+ <div className="settings-definition-left">
+ <h4 className="settings-definition-name" title={propertyName}>
+ {propertyName}
+ </h4>
+
+ {description && (
+ <div
+ className="markdown small spacer-top"
+ // eslint-disable-next-line react/no-danger
+ dangerouslySetInnerHTML={{ __html: sanitizeStringRestricted(description) }}
+ />
+ )}
+
+ <Tooltip overlay={translateWithParameters('settings.key_x', definition.key)}>
+ <div className="settings-definition-key note little-spacer-top">
+ {translateWithParameters('settings.key_x', definition.key)}
+ </div>
+ </Tooltip>
+ </div>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx b/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx
index 62d5573ce42..47249b579be 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/DefinitionRenderer.tsx
@@ -19,20 +19,13 @@
*/
import classNames from 'classnames';
import * as React from 'react';
-import Tooltip from '../../../components/controls/Tooltip';
import AlertErrorIcon from '../../../components/icons/AlertErrorIcon';
import AlertSuccessIcon from '../../../components/icons/AlertSuccessIcon';
import { translate, translateWithParameters } from '../../../helpers/l10n';
-import { sanitizeStringRestricted } from '../../../helpers/sanitize';
import { ExtendedSettingDefinition, SettingValue } from '../../../types/settings';
-import {
- combineDefinitionAndSettingValue,
- getPropertyDescription,
- getPropertyName,
- getSettingValue,
- isDefaultOrInherited,
-} from '../utils';
+import { combineDefinitionAndSettingValue, getSettingValue, isDefaultOrInherited } from '../utils';
import DefinitionActions from './DefinitionActions';
+import DefinitionDescription from './DefinitionDescription';
import Input from './inputs/Input';
export interface DefinitionRendererProps {
@@ -56,12 +49,10 @@ export default function DefinitionRenderer(props: DefinitionRendererProps) {
const { changedValue, loading, validationMessage, settingValue, success, definition, isEditing } =
props;
- const propertyName = getPropertyName(definition);
const hasError = validationMessage != null;
const hasValueChanged = changedValue != null;
const effectiveValue = hasValueChanged ? changedValue : getSettingValue(definition, settingValue);
const isDefault = isDefaultOrInherited(settingValue);
- const description = getPropertyDescription(definition);
const settingDefinitionAndValue = combineDefinitionAndSettingValue(definition, settingValue);
@@ -72,25 +63,7 @@ export default function DefinitionRenderer(props: DefinitionRendererProps) {
})}
data-key={definition.key}
>
- <div className="settings-definition-left">
- <h3 className="settings-definition-name" title={propertyName}>
- {propertyName}
- </h3>
-
- {description && (
- <div
- className="markdown small spacer-top"
- // eslint-disable-next-line react/no-danger
- dangerouslySetInnerHTML={{ __html: sanitizeStringRestricted(description) }}
- />
- )}
-
- <Tooltip overlay={translateWithParameters('settings.key_x', definition.key)}>
- <div className="settings-definition-key note little-spacer-top">
- {translateWithParameters('settings.key_x', definition.key)}
- </div>
- </Tooltip>
- </div>
+ <DefinitionDescription definition={definition} />
<div className="settings-definition-right">
<div className="settings-definition-state">
diff --git a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
index a2a54aba945..7d42d29c7ad 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/PageHeader.tsx
@@ -42,7 +42,7 @@ export default function PageHeader({ component, definitions }: PageHeaderProps)
<header className="top-bar-outer">
<div className="top-bar">
<div className="top-bar-inner bordered-bottom big-padded-top padded-bottom">
- <h1 className="page-title">{title}</h1>
+ <h2 className="page-title">{title}</h2>
<div className="page-description spacer-top">{description}</div>
<SettingsSearch
className="big-spacer-top"
diff --git a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
index 697791cc20d..68872889c96 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/SubCategoryDefinitionsList.tsx
@@ -82,13 +82,13 @@ class SubCategoryDefinitionsList extends React.PureComponent<SubCategoryDefiniti
{filteredSubCategories.map((subCategory) => (
<li key={subCategory.key}>
{displaySubCategoryTitle && (
- <h2
- className="settings-sub-category-name"
+ <h3
+ className="settings-sub-category-name h2"
data-key={subCategory.key}
ref={this.scrollToSubCategoryOrDefinition}
>
{subCategory.name}
- </h2>
+ </h3>
)}
{subCategory.description != null && (
<div
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
index 6f6cde5ea53..361c2495e63 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/Authentication.tsx
@@ -17,6 +17,7 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
+import classNames from 'classnames';
import * as React from 'react';
import { FormattedMessage } from 'react-intl';
import { useSearchParams } from 'react-router-dom';
@@ -36,7 +37,7 @@ import { Feature } from '../../../../types/features';
import { ExtendedSettingDefinition } from '../../../../types/settings';
import { AUTHENTICATION_CATEGORY } from '../../constants';
import CategoryDefinitionsList from '../CategoryDefinitionsList';
-import SamlAuthentication from './SamlAuthentication';
+import SamlAuthentication, { SAML } from './SamlAuthentication';
interface Props {
definitions: ExtendedSettingDefinition[];
@@ -45,7 +46,6 @@ interface Props {
// We substract the footer height with padding (80) and the main layout padding (20)
const HEIGHT_ADJUSTMENT = 100;
-const SAML = 'saml';
export type AuthenticationTabs =
| typeof SAML
| AlmKeys.GitHub
@@ -114,7 +114,7 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) {
return (
<>
<header className="page-header">
- <h1 className="page-title">{translate('settings.authentication.title')}</h1>
+ <h3 className="page-title h2">{translate('settings.authentication.title')}</h3>
</header>
{props.hasFeature(Feature.LoginMessage) && (
@@ -147,48 +147,54 @@ export function Authentication(props: Props & WithAvailableFeaturesProps) {
{/* Adding a key to force re-rendering of the tab container, so that it resets the scroll position */}
<ScreenPositionHelper>
{({ top }) => (
- <div
- style={{
- maxHeight: `calc(100vh - ${top + HEIGHT_ADJUSTMENT}px)`,
- }}
- className="bordered overflow-y-auto tabbed-definitions"
- key={currentTab}
- role="tabpanel"
- aria-labelledby={getTabId(currentTab)}
- id={getTabPanelId(currentTab)}
- >
- <div className="big-padded-top big-padded-left big-padded-right">
- <Alert variant="info">
- <FormattedMessage
- id="settings.authentication.help"
- defaultMessage={translate('settings.authentication.help')}
- values={{
- link: (
- <DocLink
- to={`/instance-administration/authentication/${DOCUMENTATION_LINK_SUFFIXES[currentTab]}/`}
- >
- {translate('settings.authentication.help.link')}
- </DocLink>
- ),
- }}
- />
- </Alert>
- {currentTab === SAML && (
- <SamlAuthentication
- definitions={definitions.filter((def) => def.subCategory === SAML)}
- />
- )}
+ <>
+ {tabs.map((tab) => (
+ <div
+ style={{
+ maxHeight: tab.key !== SAML ? `calc(100vh - ${top + HEIGHT_ADJUSTMENT}px)` : '',
+ }}
+ className={classNames('bordered overflow-y-auto tabbed-definitions', {
+ hidden: currentTab !== tab.key,
+ })}
+ key={tab.key}
+ role="tabpanel"
+ aria-labelledby={getTabId(tab.key)}
+ id={getTabPanelId(tab.key)}
+ >
+ <div className="big-padded-top big-padded-left big-padded-right">
+ {tab.key === SAML && <SamlAuthentication definitions={definitions} />}
- {currentTab !== SAML && (
- <CategoryDefinitionsList
- category={AUTHENTICATION_CATEGORY}
- definitions={definitions}
- subCategory={currentTab}
- displaySubCategoryTitle={false}
- />
- )}
- </div>
- </div>
+ {tab.key !== SAML && (
+ <>
+ <Alert variant="info">
+ <FormattedMessage
+ id="settings.authentication.help"
+ defaultMessage={translate('settings.authentication.help')}
+ values={{
+ link: (
+ <DocLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[tab.key as AuthenticationTabs]
+ }/`}
+ >
+ {translate('settings.authentication.help.link')}
+ </DocLink>
+ ),
+ }}
+ />
+ </Alert>
+ <CategoryDefinitionsList
+ category={AUTHENTICATION_CATEGORY}
+ definitions={definitions}
+ subCategory={tab.key}
+ displaySubCategoryTitle={false}
+ />
+ </>
+ )}
+ </div>
+ </div>
+ ))}
+ </>
)}
</ScreenPositionHelper>
</>
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx
index 379f179f1ef..9186bbada03 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlAuthentication.tsx
@@ -17,382 +17,297 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import classNames from 'classnames';
-import { keyBy } from 'lodash';
+import { isEmpty } from 'lodash';
import React from 'react';
-import { getValues, resetSettingValue, setSettingValue } from '../../../../api/settings';
-import { SubmitButton } from '../../../../components/controls/buttons';
-import Tooltip from '../../../../components/controls/Tooltip';
-import { Location, withRouter } from '../../../../components/hoc/withRouter';
-import AlertSuccessIcon from '../../../../components/icons/AlertSuccessIcon';
-import AlertWarnIcon from '../../../../components/icons/AlertWarnIcon';
-import DetachIcon from '../../../../components/icons/DetachIcon';
-import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
-import { translate, translateWithParameters } from '../../../../helpers/l10n';
-import { isSuccessStatus, parseError } from '../../../../helpers/request';
+import { FormattedMessage } from 'react-intl';
+import {
+ activateScim,
+ deactivateScim,
+ resetSettingValue,
+ setSettingValue,
+} from '../../../../api/settings';
+import DocLink from '../../../../components/common/DocLink';
+import Link from '../../../../components/common/Link';
+import { Button, ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
+import ConfirmModal from '../../../../components/controls/ConfirmModal';
+import RadioCard from '../../../../components/controls/RadioCard';
+import CheckIcon from '../../../../components/icons/CheckIcon';
+import DeleteIcon from '../../../../components/icons/DeleteIcon';
+import EditIcon from '../../../../components/icons/EditIcon';
+import { Alert } from '../../../../components/ui/Alert';
+import { translate } from '../../../../helpers/l10n';
import { getBaseUrl } from '../../../../helpers/system';
-import { ExtendedSettingDefinition, SettingType, SettingValue } from '../../../../types/settings';
-import SamlFormField from './SamlFormField';
-import SamlToggleField from './SamlToggleField';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
+import { getPropertyName } from '../../utils';
+import DefinitionDescription from '../DefinitionDescription';
+import useSamlConfiguration, { SAML_ENABLED_FIELD } from './hook/useLoadSamlSettings';
+import SamlConfigurationForm from './SamlConfigurationForm';
interface SamlAuthenticationProps {
definitions: ExtendedSettingDefinition[];
- location: Location;
}
-interface SamlAuthenticationState {
- settingValue: Pick<SettingValue, 'key' | 'value'>[];
- submitting: boolean;
- dirtyFields: string[];
- securedFieldsSubmitted: string[];
- error: { [key: string]: string };
- success?: boolean;
-}
+export const SAML = 'saml';
const CONFIG_TEST_PATH = '/saml/validation_init';
-const SAML_ENABLED_FIELD = 'sonar.auth.saml.enabled';
-
-const OPTIONAL_FIELDS = [
- 'sonar.auth.saml.sp.certificate.secured',
- 'sonar.auth.saml.sp.privateKey.secured',
- 'sonar.auth.saml.signature.enabled',
- 'sonar.auth.saml.user.email',
- 'sonar.auth.saml.group.name',
- 'sonar.scim.enabled',
-];
-
-class SamlAuthentication extends React.PureComponent<
- SamlAuthenticationProps,
- SamlAuthenticationState
-> {
- formFieldRef: React.RefObject<HTMLDivElement> = React.createRef();
-
- constructor(props: SamlAuthenticationProps) {
- super(props);
- const settingValue = props.definitions.map((def) => {
- return {
- key: def.key,
- };
- });
-
- this.state = {
- settingValue,
- submitting: false,
- dirtyFields: [],
- securedFieldsSubmitted: [],
- error: {},
- };
- }
-
- componentDidMount() {
- const { definitions } = this.props;
- const keys = definitions.map((definition) => definition.key);
- // Added setTimeout to make sure the component gets updated before scrolling
- setTimeout(() => {
- if (location.hash) {
- this.scrollToSearchedField();
- }
- });
- this.loadSettingValues(keys);
- }
-
- componentDidUpdate(prevProps: SamlAuthenticationProps) {
- const { location } = this.props;
- if (prevProps.location.hash !== location.hash) {
- this.scrollToSearchedField();
- }
- }
-
- scrollToSearchedField = () => {
- if (this.formFieldRef.current) {
- this.formFieldRef.current.scrollIntoView({
- behavior: 'smooth',
- block: 'center',
- inline: 'nearest',
- });
- }
+export default function SamlAuthentication(props: SamlAuthenticationProps) {
+ const { definitions } = props;
+ const [showEditModal, setShowEditModal] = React.useState(false);
+ const [showConfirmProvisioningModal, setShowConfirmProvisioningModal] = React.useState(false);
+ const {
+ hasScim,
+ scimStatus,
+ loading,
+ samlEnabled,
+ name,
+ groupValue,
+ url,
+ hasConfiguration,
+ values,
+ setNewValue,
+ canBeSave,
+ hasScimConfigChange,
+ newScimStatus,
+ setNewScimStatus,
+ setNewGroupSetting,
+ onReload,
+ } = useSamlConfiguration(definitions);
+
+ const handleDeleteConfiguration = async () => {
+ await resetSettingValue({ keys: Object.keys(values).join(',') });
+ await onReload();
};
- onFieldChange = (id: string, value: string | boolean) => {
- const { settingValue, dirtyFields } = this.state;
- const updatedSettingValue = settingValue?.map((set) => {
- if (set.key === id) {
- set.value = String(value);
- }
- return set;
- });
-
- if (!dirtyFields.includes(id)) {
- const updatedDirtyFields = [...dirtyFields, id];
- this.setState({
- dirtyFields: updatedDirtyFields,
- });
- }
-
- this.setState({
- settingValue: updatedSettingValue,
- });
+ const handleCreateConfiguration = () => {
+ setShowEditModal(true);
};
- async loadSettingValues(keys: string[]) {
- const { settingValue, securedFieldsSubmitted } = this.state;
- const values = await getValues({
- keys,
- });
- const valuesByDefinitionKey = keyBy(values, 'key');
- const updatedSecuredFieldsSubmitted: string[] = [...securedFieldsSubmitted];
- const updateSettingValue = settingValue?.map((set) => {
- if (valuesByDefinitionKey[set.key]) {
- set.value =
- valuesByDefinitionKey[set.key].value ?? valuesByDefinitionKey[set.key].parentValue;
- }
-
- if (
- this.isSecuredField(set.key) &&
- valuesByDefinitionKey[set.key] &&
- !securedFieldsSubmitted.includes(set.key)
- ) {
- updatedSecuredFieldsSubmitted.push(set.key);
- }
-
- return set;
- });
-
- this.setState({
- settingValue: updateSettingValue,
- securedFieldsSubmitted: updatedSecuredFieldsSubmitted,
- });
- }
-
- isSecuredField = (key: string) => {
- const { definitions } = this.props;
- const fieldDefinition = definitions.find((def) => def.key === key);
- if (fieldDefinition && fieldDefinition.type === SettingType.PASSWORD) {
- return true;
- }
- return false;
+ const handleCancelConfiguration = () => {
+ setShowEditModal(false);
};
- onSaveConfig = async () => {
- const { settingValue, dirtyFields } = this.state;
- const { definitions } = this.props;
-
- if (dirtyFields.length === 0) {
- return;
- }
-
- this.setState({ submitting: true, error: {}, success: false });
- const promises: Promise<void>[] = [];
-
- dirtyFields.forEach((field) => {
- const definition = definitions.find((def) => def.key === field);
- const value = settingValue.find((def) => def.key === field)?.value;
- if (definition && value !== undefined) {
- const apiCall =
- value.length > 0
- ? setSettingValue(definition, value)
- : resetSettingValue({ keys: definition.key });
-
- promises.push(apiCall);
- }
- });
-
- await Promise.all(promises.map((p) => p.catch((e) => e))).then((data) => {
- const dataWithError = data
- .map((data, index) => ({ data, index }))
- .filter((d) => d.data !== undefined && !isSuccessStatus(d.data.status));
- if (dataWithError.length > 0) {
- dataWithError.forEach(async (d) => {
- const validationMessage = await parseError(d.data as Response);
- const { error } = this.state;
- this.setState({
- error: { ...error, ...{ [dirtyFields[d.index]]: validationMessage } },
- });
- });
- }
- this.setState({ success: dirtyFields.length !== dataWithError.length });
- });
- await this.loadSettingValues(dirtyFields);
- this.setState({ submitting: false, dirtyFields: [] });
+ const handleToggleEnable = async () => {
+ const value = values[SAML_ENABLED_FIELD];
+ await setSettingValue(value.definition, !samlEnabled);
+ await onReload();
};
- allowEnabling = () => {
- const { settingValue } = this.state;
- const enabledFlagSettingValue = settingValue.find((set) => set.key === SAML_ENABLED_FIELD);
-
- if (enabledFlagSettingValue && enabledFlagSettingValue.value === 'true') {
- return true;
- }
-
- return this.getEmptyRequiredFields().length === 0;
- };
-
- onEnableFlagChange = (value: boolean) => {
- const { settingValue, dirtyFields } = this.state;
-
- const updatedSettingValue = settingValue?.map((set) => {
- if (set.key === SAML_ENABLED_FIELD) {
- set.value = String(value);
+ const handleSaveGroup = async () => {
+ if (groupValue.newValue !== undefined) {
+ if (isEmpty(groupValue.newValue)) {
+ await resetSettingValue({ keys: groupValue.definition.key });
+ } else {
+ await setSettingValue(groupValue.definition, groupValue.newValue);
}
- return set;
- });
-
- this.setState(
- {
- settingValue: updatedSettingValue,
- dirtyFields: [...dirtyFields, SAML_ENABLED_FIELD],
- },
- () => {
- this.onSaveConfig();
- }
- );
- };
-
- getTestButtonTooltipContent = (formIsIncomplete: boolean, hasDirtyFields: boolean) => {
- if (hasDirtyFields) {
- return translate('settings.authentication.saml.form.test.help.dirty');
- }
-
- if (formIsIncomplete) {
- return translate('settings.authentication.saml.form.test.help.incomplete');
+ await onReload();
}
-
- return null;
};
- getEmptyRequiredFields = () => {
- const { settingValue, securedFieldsSubmitted } = this.state;
- const { definitions } = this.props;
-
- const updatedRequiredFields: string[] = [];
-
- for (const setting of settingValue) {
- const isMandatory = !OPTIONAL_FIELDS.includes(setting.key);
- const isSecured = this.isSecuredField(setting.key);
- const isSecuredAndNotSubmitted = isSecured && !securedFieldsSubmitted.includes(setting.key);
- const isNotSecuredAndNotSubmitted =
- !isSecured && (setting.value === '' || setting.value === undefined);
- if (isMandatory && (isSecuredAndNotSubmitted || isNotSecuredAndNotSubmitted)) {
- const settingDef = definitions.find((def) => def.key === setting.key);
-
- if (settingDef && settingDef.name) {
- updatedRequiredFields.push(settingDef.name);
- }
- }
+ const handleConfirmChangeProvisioning = async () => {
+ if (newScimStatus) {
+ await activateScim();
+ } else {
+ await deactivateScim();
+ await handleSaveGroup();
}
- return updatedRequiredFields;
+ await onReload();
};
- render() {
- const { definitions } = this.props;
- const { submitting, settingValue, securedFieldsSubmitted, error, dirtyFields, success } =
- this.state;
- const enabledFlagDefinition = definitions.find((def) => def.key === SAML_ENABLED_FIELD);
+ return (
+ <div className="saml-configuration">
+ <div className="spacer-bottom display-flex-space-between display-flex-center">
+ <h4>{translate('settings.authentication.saml.configuration')}</h4>
- const formIsIncomplete = !this.allowEnabling();
- const preventTestingConfig = this.getEmptyRequiredFields().length > 0 || dirtyFields.length > 0;
-
- return (
- <div>
- {definitions.map((def) => {
- if (def.key === SAML_ENABLED_FIELD) {
- return null;
- }
- return (
- <div
- key={def.key}
- ref={this.props.location.hash.substring(1) === def.key ? this.formFieldRef : null}
- >
- <SamlFormField
- settingValue={settingValue?.find((set) => set.key === def.key)}
- definition={def}
- mandatory={!OPTIONAL_FIELDS.includes(def.key)}
- onFieldChange={this.onFieldChange}
- showSecuredTextArea={
- !securedFieldsSubmitted.includes(def.key) || dirtyFields.includes(def.key)
- }
- error={error}
- />
+ {!hasConfiguration && (
+ <div>
+ <Button onClick={handleCreateConfiguration}>
+ {translate('settings.authentication.form.create')}
+ </Button>
+ </div>
+ )}
+ </div>
+ {!hasConfiguration && (
+ <div className="big-padded text-center huge-spacer-bottom saml-no-config">
+ {translate('settings.authentication.saml.form.not_configured')}
+ </div>
+ )}
+
+ {hasConfiguration && (
+ <>
+ <div className="spacer-bottom big-padded bordered display-flex-space-between">
+ <div>
+ <h5>{name}</h5>
+ <p>{url}</p>
+ <p className="big-spacer-top big-spacer-bottom">
+ {samlEnabled ? (
+ <span className="saml-enabled spacer-left">
+ <CheckIcon className="spacer-right" />
+ {translate('settings.authentication.saml.form.enabled')}
+ </span>
+ ) : (
+ translate('settings.authentication.saml.form.not_enabled')
+ )}
+ </p>
+ <Button className="spacer-top" disabled={scimStatus} onClick={handleToggleEnable}>
+ {samlEnabled
+ ? translate('settings.authentication.saml.form.disable')
+ : translate('settings.authentication.saml.form.enable')}
+ </Button>
</div>
- );
- })}
- <div className="fixed-footer padded">
- {enabledFlagDefinition && (
- <Tooltip
- overlay={
- this.allowEnabling()
- ? null
- : translateWithParameters(
- 'settings.authentication.saml.tooltip.required_fields',
- this.getEmptyRequiredFields().join(', ')
- )
- }
- >
- <div className="display-inline-flex-center">
- <label className="h3 spacer-right">{enabledFlagDefinition.name}</label>
- <SamlToggleField
- definition={enabledFlagDefinition}
- settingValue={settingValue?.find((set) => set.key === enabledFlagDefinition.key)}
- toggleDisabled={formIsIncomplete}
- onChange={this.onEnableFlagChange}
- />
- </div>
- </Tooltip>
- )}
- <div className="display-inline-flex-center">
- {success && (
- <div className="spacer-right">
- <Tooltip
- overlay={
- Object.keys(error).length > 0
- ? translateWithParameters(
- 'settings.authentication.saml.form.save_warn',
- Object.keys(error).length
- )
- : null
- }
- >
- {Object.keys(error).length > 0 ? (
- <span>
- <AlertWarnIcon className="spacer-right" />
- {translate('settings.authentication.saml.form.save_partial')}
- </span>
- ) : (
- <span>
- <AlertSuccessIcon className="spacer-right" />
- {translate('settings.authentication.saml.form.save_success')}
- </span>
- )}
- {}
- </Tooltip>
- </div>
- )}
- <SubmitButton className="button-primary spacer-right" onClick={this.onSaveConfig}>
- {translate('settings.authentication.saml.form.save')}
- <DeferredSpinner className="spacer-left" loading={submitting} />
- </SubmitButton>
-
- <Tooltip
- overlay={this.getTestButtonTooltipContent(formIsIncomplete, dirtyFields.length > 0)}
- >
- <a
- className={classNames('button', {
- disabled: preventTestingConfig,
- })}
- href={preventTestingConfig ? undefined : `${getBaseUrl()}${CONFIG_TEST_PATH}`}
+ <div>
+ <Link
+ className="button spacer-right"
target="_blank"
- rel="noopener noreferrer"
+ to={`${getBaseUrl()}${CONFIG_TEST_PATH}`}
>
- <DetachIcon className="spacer-right" />
{translate('settings.authentication.saml.form.test')}
- </a>
- </Tooltip>
+ </Link>
+ <Button className="spacer-right" onClick={handleCreateConfiguration}>
+ <EditIcon />
+ {translate('settings.authentication.form.edit')}
+ </Button>
+ <Button
+ className="button-red"
+ disabled={samlEnabled}
+ onClick={handleDeleteConfiguration}
+ >
+ <DeleteIcon />
+ {translate('settings.authentication.form.delete')}
+ </Button>
+ </div>
</div>
- </div>
- </div>
- );
- }
+ {hasScim && (
+ <div className="spacer-bottom big-padded bordered display-flex-space-between">
+ <form
+ onSubmit={(e) => {
+ e.preventDefault();
+ if (newScimStatus !== scimStatus) {
+ setShowConfirmProvisioningModal(true);
+ } else {
+ handleSaveGroup();
+ }
+ }}
+ >
+ <fieldset className="display-flex-column big-spacer-bottom">
+ <label className="h5">
+ {translate('settings.authentication.saml.form.provisioning')}
+ </label>
+ {samlEnabled ? (
+ <div className="display-flex-row spacer-top">
+ <RadioCard
+ label={translate(
+ 'settings.authentication.saml.form.provisioning_with_scim'
+ )}
+ title={translate(
+ 'settings.authentication.saml.form.provisioning_with_scim'
+ )}
+ selected={newScimStatus ?? scimStatus}
+ onClick={() => setNewScimStatus(true)}
+ >
+ <p className="spacer-bottom">
+ {translate(
+ 'settings.authentication.saml.form.provisioning_with_scim.sub'
+ )}
+ </p>
+ <p>
+ <FormattedMessage
+ id="settings.authentication.saml.form.provisioning_with_scim.description"
+ defaultMessage={translate(
+ 'settings.authentication.saml.form.provisioning_with_scim.description'
+ )}
+ values={{
+ doc: (
+ <DocLink to="/instance-administration/authentication/saml/scim/overview">
+ {translate('documentation')}
+ </DocLink>
+ ),
+ }}
+ />
+ </p>
+ </RadioCard>
+ <RadioCard
+ label={translate('settings.authentication.saml.form.provisioning_at_login')}
+ title={translate('settings.authentication.saml.form.provisioning_at_login')}
+ selected={!(newScimStatus ?? scimStatus)}
+ onClick={() => setNewScimStatus(false)}
+ >
+ <p>
+ {translate('settings.authentication.saml.form.provisioning_at_login.sub')}
+ </p>
+ {groupValue && (
+ <div className="settings-definition">
+ <DefinitionDescription definition={groupValue.definition} />
+ <div className="settings-definition-right">
+ <input
+ id={groupValue.definition.key}
+ maxLength={4000}
+ name={groupValue.definition.key}
+ onChange={(e) => setNewGroupSetting(e.currentTarget.value)}
+ type="text"
+ value={String(groupValue.newValue ?? groupValue.value ?? '')}
+ aria-label={getPropertyName(groupValue.definition)}
+ />
+ </div>
+ </div>
+ )}
+ </RadioCard>
+ </div>
+ ) : (
+ <Alert className="big-spacer-top" variant="info">
+ {translate('settings.authentication.saml.enable_first')}
+ </Alert>
+ )}
+ </fieldset>
+ {samlEnabled && (
+ <>
+ <SubmitButton disabled={!hasScimConfigChange}>{translate('save')}</SubmitButton>
+ <ResetButtonLink
+ className="spacer-left"
+ onClick={() => {
+ setNewScimStatus(undefined);
+ setNewGroupSetting();
+ }}
+ disabled={!hasScimConfigChange}
+ >
+ {translate('cancel')}
+ </ResetButtonLink>
+ </>
+ )}
+ {showConfirmProvisioningModal && (
+ <ConfirmModal
+ onConfirm={() => handleConfirmChangeProvisioning()}
+ header={translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit'
+ )}
+ onClose={() => setShowConfirmProvisioningModal(false)}
+ isDestructive={!newScimStatus}
+ confirmButtonText={translate('yes')}
+ >
+ {translate(
+ 'settings.authentication.saml.confirm',
+ newScimStatus ? 'scim' : 'jit',
+ 'description'
+ )}
+ </ConfirmModal>
+ )}
+ </form>
+ </div>
+ )}
+ </>
+ )}
+ {showEditModal && (
+ <SamlConfigurationForm
+ loading={loading}
+ values={values}
+ setNewValue={setNewValue}
+ canBeSave={canBeSave}
+ onClose={handleCancelConfiguration}
+ create={!hasConfiguration}
+ onReload={onReload}
+ />
+ )}
+ </div>
+ );
}
-
-export default withRouter(SamlAuthentication);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlConfigurationForm.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlConfigurationForm.tsx
new file mode 100644
index 00000000000..028429f6031
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlConfigurationForm.tsx
@@ -0,0 +1,150 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { isEmpty, keyBy } from 'lodash';
+import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import { setSettingValue } from '../../../../api/settings';
+import DocLink from '../../../../components/common/DocLink';
+import { ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
+import Modal from '../../../../components/controls/Modal';
+import { Alert } from '../../../../components/ui/Alert';
+import DeferredSpinner from '../../../../components/ui/DeferredSpinner';
+import { translate } from '../../../../helpers/l10n';
+import { Dict } from '../../../../types/types';
+import {
+ SamlSettingValue,
+ SAML_ENABLED_FIELD,
+ SAML_GROUP_NAME,
+ SAML_SCIM_DEPRECATED,
+} from './hook/useLoadSamlSettings';
+import SamlFormField from './SamlFormField';
+
+interface Props {
+ create: boolean;
+ loading: boolean;
+ values: Dict<SamlSettingValue>;
+ setNewValue: (key: string, value: string | boolean) => void;
+ canBeSave: boolean;
+ onClose: () => void;
+ onReload: () => Promise<void>;
+}
+
+interface ErrorValue {
+ key: string;
+ message: string;
+}
+
+export const SAML = 'saml';
+
+const SAML_EXCLUDED_FIELD = [SAML_ENABLED_FIELD, SAML_GROUP_NAME, SAML_SCIM_DEPRECATED];
+
+export default function SamlConfigurationForm(props: Props) {
+ const { create, loading, values, setNewValue, canBeSave } = props;
+ const [errors, setErrors] = React.useState<Dict<ErrorValue>>({});
+
+ const headerLabel = translate('settings.authentication.saml.form', create ? 'create' : 'edit');
+
+ const handleSubmit = async (event: React.SyntheticEvent<HTMLFormElement>) => {
+ event.preventDefault();
+
+ if (canBeSave) {
+ const r = await Promise.all(
+ Object.values(values)
+ .filter((v) => v.newValue !== undefined)
+ .map(async ({ key, newValue, definition }) => {
+ try {
+ await setSettingValue(definition, newValue);
+ return { key, success: true };
+ } catch (error) {
+ return { key, success: false };
+ }
+ })
+ );
+ const errors = r
+ .filter(({ success }) => !success)
+ .map(({ key }) => ({ key, message: translate('default_save_field_error_message') }));
+ setErrors(keyBy(errors, 'key'));
+ if (isEmpty(errors)) {
+ await props.onReload();
+ props.onClose();
+ }
+ } else {
+ const errors = Object.values(values)
+ .filter((v) => v.newValue === undefined && v.value === undefined && v.mandatory)
+ .map((v) => ({ key: v.key, message: translate('field_required') }));
+ setErrors(keyBy(errors, 'key'));
+ }
+ };
+
+ return (
+ <Modal contentLabel={headerLabel} shouldCloseOnOverlayClick={false} size="medium">
+ <form className="views-form create-saml-form" onSubmit={handleSubmit}>
+ <div className="modal-head">
+ <h2>{headerLabel}</h2>
+ </div>
+ <div className="modal-body modal-container">
+ <DeferredSpinner
+ loading={loading}
+ ariaLabel={translate('settings.authentication.saml.form.loading')}
+ >
+ <Alert variant="info">
+ <FormattedMessage
+ id="settings.authentication.help"
+ defaultMessage={translate('settings.authentication.help')}
+ values={{
+ link: (
+ <DocLink to="/instance-administration/authentication/saml/overview/">
+ {translate('settings.authentication.help.link')}
+ </DocLink>
+ ),
+ }}
+ />
+ </Alert>
+ {Object.values(values).map((val) => {
+ if (SAML_EXCLUDED_FIELD.includes(val.key)) {
+ return null;
+ }
+ return (
+ <div key={val.key}>
+ <SamlFormField
+ settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
+ definition={val.definition}
+ mandatory={val.mandatory}
+ onFieldChange={setNewValue}
+ isNotSet={val.isNotSet}
+ error={errors[val.key]?.message}
+ />
+ </div>
+ );
+ })}
+ </DeferredSpinner>
+ </div>
+
+ <div className="modal-foot">
+ <SubmitButton disabled={!canBeSave}>
+ {translate('settings.almintegration.form.save')}
+ <DeferredSpinner className="spacer-left" loading={loading} />
+ </SubmitButton>
+ <ResetButtonLink onClick={props.onClose}>{translate('cancel')}</ResetButtonLink>
+ </div>
+ </form>
+ </Modal>
+ );
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlFormField.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlFormField.tsx
index 639c37c69ef..d86afef207d 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlFormField.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlFormField.tsx
@@ -22,24 +22,24 @@ import ValidationInput, {
ValidationInputErrorPlacement,
} from '../../../../components/controls/ValidationInput';
import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
-import { ExtendedSettingDefinition, SettingType, SettingValue } from '../../../../types/settings';
+import { ExtendedSettingDefinition, SettingType } from '../../../../types/settings';
import SamlSecuredField from './SamlSecuredField';
import SamlToggleField from './SamlToggleField';
interface SamlToggleFieldProps {
- settingValue?: SettingValue;
+ settingValue?: string | boolean;
definition: ExtendedSettingDefinition;
mandatory?: boolean;
onFieldChange: (key: string, value: string | boolean) => void;
- showSecuredTextArea?: boolean;
- error: { [key: string]: string };
+ isNotSet: boolean;
+ error?: string;
}
export default function SamlFormField(props: SamlToggleFieldProps) {
- const { mandatory = false, definition, settingValue, showSecuredTextArea = true, error } = props;
+ const { mandatory = false, definition, settingValue, isNotSet, error } = props;
return (
- <div className="settings-definition" key={definition.key}>
+ <div className="settings-definition">
<div className="settings-definition-left">
<label className="h3" htmlFor={definition.key}>
{definition.name}
@@ -53,9 +53,9 @@ export default function SamlFormField(props: SamlToggleFieldProps) {
{definition.type === SettingType.PASSWORD && (
<SamlSecuredField
definition={definition}
- settingValue={settingValue}
+ settingValue={String(settingValue ?? '')}
onFieldChange={props.onFieldChange}
- showTextArea={showSecuredTextArea}
+ isNotSet={isNotSet}
/>
)}
{definition.type === SettingType.BOOLEAN && (
@@ -68,10 +68,10 @@ export default function SamlFormField(props: SamlToggleFieldProps) {
)}
{definition.type === undefined && (
<ValidationInput
- error={error[definition.key]}
+ error={error}
errorPlacement={ValidationInputErrorPlacement.Bottom}
isValid={false}
- isInvalid={Boolean(error[definition.key])}
+ isInvalid={Boolean(error)}
>
<input
className="width-100"
@@ -80,8 +80,7 @@ export default function SamlFormField(props: SamlToggleFieldProps) {
name={definition.key}
onChange={(e) => props.onFieldChange(definition.key, e.currentTarget.value)}
type="text"
- value={settingValue?.value ?? ''}
- aria-label={definition.key}
+ value={String(settingValue ?? '')}
/>
</ValidationInput>
)}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlSecuredField.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlSecuredField.tsx
index e8947ee85c6..a7177a2a114 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlSecuredField.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlSecuredField.tsx
@@ -20,27 +20,30 @@
import React, { useEffect } from 'react';
import { ButtonLink } from '../../../../components/controls/buttons';
import { translate } from '../../../../helpers/l10n';
-import { ExtendedSettingDefinition, SettingValue } from '../../../../types/settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
+import { isSecuredDefinition } from '../../utils';
interface SamlToggleFieldProps {
onFieldChange: (key: string, value: string) => void;
- settingValue?: SettingValue;
+ settingValue?: string;
definition: ExtendedSettingDefinition;
optional?: boolean;
- showTextArea: boolean;
+ isNotSet: boolean;
}
export default function SamlSecuredField(props: SamlToggleFieldProps) {
- const { settingValue, definition, optional = true, showTextArea } = props;
- const [showField, setShowField] = React.useState(showTextArea);
+ const { settingValue, definition, optional = true, isNotSet } = props;
+ const [showSecretField, setShowSecretField] = React.useState(
+ !isNotSet && isSecuredDefinition(definition)
+ );
useEffect(() => {
- setShowField(showTextArea);
- }, [showTextArea]);
+ setShowSecretField(!isNotSet && isSecuredDefinition(definition));
+ }, [isNotSet, definition]);
return (
<>
- {showField && (
+ {!showSecretField && (
<textarea
className="width-100"
id={definition.key}
@@ -48,15 +51,15 @@ export default function SamlSecuredField(props: SamlToggleFieldProps) {
onChange={(e) => props.onFieldChange(definition.key, e.currentTarget.value)}
required={!optional}
rows={5}
- value={settingValue?.value ?? ''}
+ value={settingValue ?? ''}
/>
)}
- {!showField && (
+ {showSecretField && (
<div>
<p>{translate('settings.almintegration.form.secret.field')}</p>
<ButtonLink
onClick={() => {
- setShowField(true);
+ setShowSecretField(false);
}}
>
{translate('settings.almintegration.form.secret.update_field')}
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlToggleField.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlToggleField.tsx
index ba0cd8b1c13..a7c787c2cd0 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlToggleField.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/SamlToggleField.tsx
@@ -19,12 +19,12 @@
*/
import React from 'react';
import Toggle from '../../../../components/controls/Toggle';
-import { ExtendedSettingDefinition, SettingValue } from '../../../../types/settings';
+import { ExtendedSettingDefinition } from '../../../../types/settings';
interface SamlToggleFieldProps {
toggleDisabled: boolean;
onChange: (value: boolean) => void;
- settingValue?: SettingValue;
+ settingValue?: string | boolean;
definition: ExtendedSettingDefinition;
}
@@ -35,7 +35,7 @@ export default function SamlToggleField(props: SamlToggleFieldProps) {
<Toggle
name={definition.key}
onChange={props.onChange}
- value={settingValue?.value ?? ''}
+ value={settingValue ?? ''}
disabled={toggleDisabled}
/>
);
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx
index 463e2b9666d..0e597dc7bb3 100644
--- a/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/__tests__/Authentication-it.tsx
@@ -17,53 +17,20 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-import { screen } from '@testing-library/react';
+import { act, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
+import { UserEvent } from '@testing-library/user-event/dist/types/setup/setup';
import React from 'react';
import { byRole, byText } from 'testing-library-selector';
import AuthenticationServiceMock from '../../../../../api/mocks/AuthenticationServiceMock';
import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
-import { mockDefinition } from '../../../../../helpers/mocks/settings';
+import { definitions } from '../../../../../helpers/mocks/definitions-list';
import { renderComponent } from '../../../../../helpers/testReactTestingUtils';
import { Feature } from '../../../../../types/features';
-import { ExtendedSettingDefinition, SettingType } from '../../../../../types/settings';
import Authentication from '../Authentication';
jest.mock('../../../../../api/settings');
-const mockDefinitionFields = [
- mockDefinition({
- key: 'test1',
- category: 'authentication',
- subCategory: 'saml',
- name: 'test1',
- description: 'desc1',
- }),
- mockDefinition({
- key: 'test2',
- category: 'authentication',
- subCategory: 'saml',
- name: 'test2',
- description: 'desc2',
- }),
- 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,
- }),
-];
-
let handler: AuthenticationServiceMock;
beforeEach(() => {
@@ -79,11 +46,57 @@ const ui = {
testButton: byText('settings.authentication.saml.form.test'),
textbox1: byRole('textbox', { name: 'test1' }),
textbox2: byRole('textbox', { name: 'test2' }),
+ saml: {
+ noSamlConfiguration: byText('settings.authentication.saml.form.not_configured'),
+ createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
+ providerName: byRole('textbox', { name: 'Provider Name' }),
+ providerId: byRole('textbox', { name: 'Provider ID' }),
+ providerCertificate: byRole('textbox', { name: 'Identity provider certificate' }),
+ loginUrl: byRole('textbox', { name: 'SAML login url' }),
+ userLoginAttribute: byRole('textbox', { name: 'SAML user login attribute' }),
+ userNameAttribute: byRole('textbox', { name: 'SAML user name attribute' }),
+ saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
+ confirmProvisioningButton: byRole('button', { name: 'yes' }),
+ saveScim: byRole('button', { name: 'save' }),
+ groupAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.group.name.name' }),
+ enableConfigButton: byRole('button', { name: 'settings.authentication.saml.form.enable' }),
+ disableConfigButton: byRole('button', { name: 'settings.authentication.saml.form.disable' }),
+ editConfigButton: byRole('button', { name: 'settings.authentication.form.edit' }),
+ enableFirstMessage: byText('settings.authentication.saml.enable_first'),
+ jitProvisioningButton: byRole('radio', {
+ name: 'settings.authentication.saml.form.provisioning_at_login',
+ }),
+ scimProvisioningButton: byRole('radio', {
+ name: 'settings.authentication.saml.form.provisioning_with_scim',
+ }),
+ fillForm: async (user: UserEvent) => {
+ const { saml } = ui;
+ await act(async () => {
+ await user.clear(saml.providerName.get());
+ await user.type(saml.providerName.get(), 'Awsome SAML config');
+ await user.type(saml.providerId.get(), 'okta-1234');
+ await user.type(saml.loginUrl.get(), 'http://test.org');
+ await user.type(saml.providerCertificate.get(), '-secret-');
+ await user.type(saml.userLoginAttribute.get(), 'login');
+ await user.type(saml.userNameAttribute.get(), 'name');
+ });
+ },
+ createConfiguration: async (user: UserEvent) => {
+ const { saml } = ui;
+ await act(async () => {
+ await user.click(await saml.createConfigButton.find());
+ });
+ await saml.fillForm(user);
+ await act(async () => {
+ await user.click(saml.saveConfigButton.get());
+ });
+ },
+ },
};
it('should render tabs and allow navigation', async () => {
const user = userEvent.setup();
- renderAuthentication([]);
+ renderAuthentication();
expect(screen.getAllByRole('tab')).toHaveLength(4);
@@ -98,118 +111,87 @@ it('should render tabs and allow navigation', async () => {
);
});
+it('should not display the login message feature info box', () => {
+ renderAuthentication();
+
+ expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
+});
+
+it('should display the login message feature info box', () => {
+ renderAuthentication([Feature.LoginMessage]);
+
+ expect(ui.customMessageInformation.get()).toBeInTheDocument();
+});
+
describe('SAML tab', () => {
- it('should allow user to test the configuration', async () => {
- const user = userEvent.setup();
+ const { saml } = ui;
- 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(ui.testButton.get()).toHaveClass('disabled');
-
- await user.click(ui.saveButton.get());
-
- expect(ui.testButton.get()).not.toHaveClass('disabled');
+ it('should render an empty SAML configuration', async () => {
+ renderAuthentication();
+ expect(await saml.noSamlConfiguration.find()).toBeInTheDocument();
});
- it('should allow user to edit fields and save configuration', async () => {
+ it('should be able to create a configuration', async () => {
const user = userEvent.setup();
- const definitions = mockDefinitionFields;
- renderAuthentication(definitions);
-
- expect(ui.enabledToggle.get()).toHaveAttribute('aria-disabled', 'true');
- // update fields
- await user.click(ui.textbox1.get());
- await user.keyboard('new test1');
-
- await user.click(ui.textbox2.get());
- await user.keyboard('new test2');
- // check if enable is allowed after updating
- expect(ui.enabledToggle.get()).toHaveAttribute('aria-disabled', 'false');
-
- // reset value
- await user.click(ui.textbox2.get());
- await user.keyboard('{Control>}a{/Control}{Backspace}');
- await user.click(ui.saveButton.get());
- expect(ui.enabledToggle.get()).toHaveAttribute('aria-disabled', 'true');
-
- await user.click(ui.textbox2.get());
- await user.keyboard('new test2');
- expect(ui.enabledToggle.get()).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(ui.enabledToggle.get());
- expect(ui.enabledToggle.get()).toBeChecked();
-
- await user.click(ui.saveButton.get());
- 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(ui.enabledToggle.get()).toBeChecked();
+ renderAuthentication();
+
+ await user.click(await saml.createConfigButton.find());
+
+ expect(saml.saveConfigButton.get()).toBeDisabled();
+ await saml.fillForm(user);
+ expect(saml.saveConfigButton.get()).toBeEnabled();
+
+ await act(async () => {
+ await user.click(saml.saveConfigButton.get());
+ });
+
+ expect(await saml.editConfigButton.find()).toBeInTheDocument();
});
- it('should handle and show errors to the user', async () => {
+ it('should be able to enable/disable configuration', async () => {
+ const { saml } = ui;
const user = userEvent.setup();
- const definitions = mockDefinitionFields;
- renderAuthentication(definitions);
-
- await user.click(ui.textbox1.get());
- await user.keyboard('value');
- await user.click(ui.textbox2.get());
- await user.keyboard('{Control>}a{/Control}error');
- await user.click(ui.saveButton.get());
- expect(screen.getByText('settings.authentication.saml.form.save_partial')).toBeInTheDocument();
- });
+ renderAuthentication();
+
+ await saml.createConfiguration(user);
+ await user.click(await saml.enableConfigButton.find());
- it('should not display the login message feature info box', () => {
- renderAuthentication([]);
+ expect(await saml.disableConfigButton.find()).toBeInTheDocument();
+ await user.click(saml.disableConfigButton.get());
+ expect(saml.disableConfigButton.query()).not.toBeInTheDocument();
- expect(ui.customMessageInformation.query()).not.toBeInTheDocument();
+ expect(await saml.enableConfigButton.find()).toBeInTheDocument();
});
- it('should display the login message feature info box', () => {
- renderAuthentication([], [Feature.LoginMessage]);
+ it('should be able to choose provisioning', async () => {
+ const { saml } = ui;
+ const user = userEvent.setup();
+
+ renderAuthentication([Feature.Scim]);
+
+ await saml.createConfiguration(user);
+
+ expect(await saml.enableFirstMessage.find()).toBeInTheDocument();
+ await user.click(await saml.enableConfigButton.find());
+
+ expect(await saml.jitProvisioningButton.find()).toBeChecked();
+
+ await user.type(saml.groupAttribute.get(), 'group');
+ expect(saml.saveScim.get()).toBeEnabled();
+ await user.click(saml.saveScim.get());
+ expect(await saml.saveScim.find()).toBeDisabled();
+
+ await user.click(saml.scimProvisioningButton.get());
+ expect(saml.saveScim.get()).toBeEnabled();
+ await user.click(saml.saveScim.get());
+ await user.click(saml.confirmProvisioningButton.get());
- expect(ui.customMessageInformation.get()).toBeInTheDocument();
+ expect(await saml.scimProvisioningButton.find()).toBeChecked();
+ expect(await saml.saveScim.find()).toBeDisabled();
});
});
-function renderAuthentication(definitions: ExtendedSettingDefinition[], features: Feature[] = []) {
+function renderAuthentication(features: Feature[] = []) {
renderComponent(
<AvailableFeaturesContext.Provider value={features}>
<Authentication definitions={definitions} />
diff --git a/server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useLoadSamlSettings.ts b/server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useLoadSamlSettings.ts
new file mode 100644
index 00000000000..af29e0ea0c5
--- /dev/null
+++ b/server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useLoadSamlSettings.ts
@@ -0,0 +1,160 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { every, isEmpty, keyBy } from 'lodash';
+import React from 'react';
+import { fetchIsScimEnabled, getValues } from '../../../../../api/settings';
+import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
+import { Feature } from '../../../../../types/features';
+import { ExtendedSettingDefinition } from '../../../../../types/settings';
+import { Dict } from '../../../../../types/types';
+
+const SAML = 'saml';
+
+export const SAML_ENABLED_FIELD = 'sonar.auth.saml.enabled';
+export const SAML_GROUP_NAME = 'sonar.auth.saml.group.name';
+export const SAML_SCIM_DEPRECATED = 'sonar.scim.enabled';
+const SAML_PROVIDER_NAME = 'sonar.auth.saml.providerName';
+const SAML_LOGIN_URL = 'sonar.auth.saml.loginUrl';
+
+const OPTIONAL_FIELDS = [
+ 'sonar.auth.saml.sp.certificate.secured',
+ 'sonar.auth.saml.sp.privateKey.secured',
+ 'sonar.auth.saml.signature.enabled',
+ 'sonar.auth.saml.user.email',
+ 'sonar.auth.saml.group.name',
+ SAML_SCIM_DEPRECATED,
+];
+
+export interface SamlSettingValue {
+ key: string;
+ mandatory: boolean;
+ isNotSet: boolean;
+ value?: string;
+ newValue?: string | boolean;
+ definition: ExtendedSettingDefinition;
+}
+
+export default function useSamlConfiguration(definitions: ExtendedSettingDefinition[]) {
+ const [loading, setLoading] = React.useState(true);
+ const [scimStatus, setScimStatus] = React.useState<boolean>(false);
+ const [values, setValues] = React.useState<Dict<SamlSettingValue>>({});
+ const [newScimStatus, setNewScimStatus] = React.useState<boolean>();
+ const hasScim = React.useContext(AvailableFeaturesContext).includes(Feature.Scim);
+
+ const onReload = React.useCallback(async () => {
+ const samlDefinition = definitions.filter((def) => def.subCategory === SAML);
+ const keys = samlDefinition.map((definition) => definition.key);
+
+ setLoading(true);
+
+ try {
+ const values = await getValues({
+ keys,
+ });
+
+ setValues(
+ keyBy(
+ samlDefinition.map((definition) => ({
+ key: definition.key,
+ value: values.find((v) => v.key === definition.key)?.value,
+ mandatory: !OPTIONAL_FIELDS.includes(definition.key),
+ isNotSet: values.find((v) => v.key === definition.key) === undefined,
+ definition,
+ })),
+ 'key'
+ )
+ );
+
+ if (hasScim) {
+ setScimStatus(await fetchIsScimEnabled());
+ }
+ } finally {
+ setLoading(false);
+ }
+ }, [...definitions]);
+
+ React.useEffect(() => {
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
+ (async () => {
+ await onReload();
+ })();
+ }, [...definitions]);
+
+ const setNewValue = (key: string, newValue?: string | boolean) => {
+ const newValues = {
+ ...values,
+ [key]: {
+ key,
+ newValue,
+ mandatory: values[key]?.mandatory,
+ isNotSet: values[key]?.isNotSet,
+ value: values[key]?.value,
+ definition: values[key]?.definition,
+ },
+ };
+ setValues(newValues);
+ };
+
+ const canBeSave = every(
+ Object.values(values).filter((v) => v.mandatory),
+ (v) =>
+ (v.newValue !== undefined && !isEmpty(v.newValue)) ||
+ (!v.isNotSet && v.newValue === undefined)
+ );
+
+ const hasConfiguration = every(
+ Object.values(values).filter((v) => v.mandatory),
+ (v) => !v.isNotSet
+ );
+
+ const name = values[SAML_PROVIDER_NAME]?.value;
+ const url = values[SAML_LOGIN_URL]?.value;
+ const samlEnabled = values[SAML_ENABLED_FIELD]?.value === 'true';
+ const groupValue = values[SAML_GROUP_NAME];
+
+ const setNewGroupSetting = (value?: string) => {
+ setNewValue(SAML_GROUP_NAME, value);
+ };
+
+ const hasScimConfigChange =
+ newScimStatus !== undefined &&
+ groupValue &&
+ (newScimStatus !== scimStatus ||
+ (groupValue.newValue !== undefined && (groupValue.value ?? '') !== groupValue.newValue));
+
+ return {
+ hasScim,
+ scimStatus,
+ loading,
+ samlEnabled,
+ name,
+ url,
+ groupValue,
+ hasConfiguration,
+ canBeSave,
+ values,
+ setNewValue,
+ onReload,
+ hasScimConfigChange,
+ newScimStatus,
+ setNewScimStatus,
+ setNewGroupSetting,
+ };
+}
diff --git a/server/sonar-web/src/main/js/apps/settings/styles.css b/server/sonar-web/src/main/js/apps/settings/styles.css
index ecf27c1be01..d116f640ea7 100644
--- a/server/sonar-web/src/main/js/apps/settings/styles.css
+++ b/server/sonar-web/src/main/js/apps/settings/styles.css
@@ -229,3 +229,49 @@
padding: 16px;
overflow-wrap: break-word;
}
+
+.saml-enabled {
+ color: var(--success500);
+}
+
+.saml-no-config {
+ background-color: var(--neutral50);
+ color: var(--blacka60);
+}
+
+.saml-configuration .radio-card {
+ width: 50%;
+ background-color: var(--neutral50);
+ border: 1px solid var(--neutral200);
+}
+
+.saml-configuration .radio-card.selected {
+ background-color: var(--info50);
+ border: 1px solid var(--info500);
+}
+
+.saml-configuration .radio-card:hover:not(.selected) {
+ border: 1px solid var(--info500);
+}
+
+.saml-configuration fieldset > div {
+ justify-content: space-between;
+}
+
+.saml-configuration .radio-card-header {
+ justify-content: space-around;
+}
+
+.saml-configuration .radio-card-body {
+ justify-content: flex-start;
+}
+
+.saml-configuration .settings-definition-left {
+ width: 50%;
+}
+
+.saml-configuration .settings-definition-right {
+ display: flex;
+ align-items: center;
+ width: 50%;
+}
diff --git a/server/sonar-web/src/main/js/components/controls/RadioCard.tsx b/server/sonar-web/src/main/js/components/controls/RadioCard.tsx
index e11b30b4300..1e0a7ee30ba 100644
--- a/server/sonar-web/src/main/js/components/controls/RadioCard.tsx
+++ b/server/sonar-web/src/main/js/components/controls/RadioCard.tsx
@@ -39,6 +39,7 @@ interface Props extends RadioCardProps {
title: React.ReactNode;
titleInfo?: React.ReactNode;
vertical?: boolean;
+ label?: string;
}
export default function RadioCard(props: Props) {
@@ -49,6 +50,7 @@ export default function RadioCard(props: Props) {
recommended,
selected,
titleInfo,
+ label,
vertical = false,
noRadio = false,
} = props;
@@ -68,6 +70,7 @@ export default function RadioCard(props: Props) {
)}
onClick={isActionable && !disabled ? onClick : undefined}
role="radio"
+ aria-label={label}
tabIndex={0}
>
<h2 className="radio-card-header big-spacer-bottom">
diff --git a/server/sonar-web/src/main/js/helpers/mocks/definitions-list.ts b/server/sonar-web/src/main/js/helpers/mocks/definitions-list.ts
new file mode 100644
index 00000000000..279a1ac3f7b
--- /dev/null
+++ b/server/sonar-web/src/main/js/helpers/mocks/definitions-list.ts
@@ -0,0 +1,2529 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2023 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+import { ExtendedSettingDefinition, SettingType } from '../../types/settings';
+
+export const definitions: ExtendedSettingDefinition[] = [
+ {
+ key: 'sonar.abap.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for files to analyze. To not filter, leave the list empty.',
+ category: 'ABAP',
+ subCategory: 'General',
+ defaultValue: '.abap,.ab4,.flow,.asprog',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.apex.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Apex',
+ subCategory: 'General',
+ defaultValue: '.cls,.trigger',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.apex.coverage.reportPath',
+ name: 'Path to coverage report',
+ description:
+ 'Path to coverage report file (test-result-codecoverage.json) generated by Salesforce CLI test command for Apex. The path may be absolute or relative to the project base directory.',
+ category: 'Apex',
+ subCategory: 'Test and Coverage',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.enabled',
+ name: 'Enabled',
+ description:
+ 'Enable Gitlab users to login. Value is ignored if URL, Application ID, and Secret are not set.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'gitlab',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.enabled',
+ name: 'Enabled',
+ description:
+ 'Enable SAML users to login. Value is ignored if provider ID, login url, certificate, login, name attributes are not defined.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'saml',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.enabled',
+ name: 'Enabled',
+ description:
+ 'Enable GitHub users to login. Value is ignored if client ID and secret are not defined.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'github',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.bitbucket.enabled',
+ name: 'Enabled',
+ description:
+ 'Enable Bitbucket users to login. Value is ignored if consumer key and secret are not defined.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'bitbucket',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.applicationId',
+ name: 'Application ID',
+ description: 'The identifier used on the Identity Provider for registering SonarQube.',
+ category: 'authentication',
+ subCategory: 'saml',
+ defaultValue: 'sonarqube',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.clientId.secured',
+ name: 'Client ID',
+ description: 'Client ID provided by GitHub when registering the application.',
+ category: 'authentication',
+ subCategory: 'github',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.url',
+ name: 'GitLab URL',
+ description: 'URL to access GitLab.',
+ category: 'authentication',
+ subCategory: 'gitlab',
+ defaultValue: 'https://gitlab.com',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.bitbucket.clientId.secured',
+ name: 'OAuth consumer key',
+ description: 'Consumer key provided by Bitbucket when registering the consumer.',
+ category: 'authentication',
+ subCategory: 'bitbucket',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.applicationId.secured',
+ name: 'Application ID',
+ description: 'Application ID provided by GitLab when registering the application.',
+ category: 'authentication',
+ subCategory: 'gitlab',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.clientSecret.secured',
+ name: 'Client Secret',
+ description: 'Client password provided by GitHub when registering the application.',
+ type: SettingType.PASSWORD,
+ category: 'authentication',
+ subCategory: 'github',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.bitbucket.clientSecret.secured',
+ name: 'OAuth consumer secret',
+ description: 'Consumer secret provided by Bitbucket when registering the consumer.',
+ category: 'authentication',
+ subCategory: 'bitbucket',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.providerName',
+ name: 'Provider Name',
+ description:
+ 'Name of the Identity Provider displayed in the login page when SAML authentication is active.',
+ category: 'authentication',
+ subCategory: 'saml',
+ defaultValue: 'SAML',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.bitbucket.allowUsersToSignUp',
+ name: 'Allow users to sign-up',
+ description:
+ "Allow new users to authenticate. When set to 'false', only existing users will be able to authenticate.",
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'bitbucket',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.allowUsersToSignUp',
+ name: 'Allow users to sign-up',
+ description:
+ "Allow new users to authenticate. When set to 'false', only existing users will be able to authenticate to the server.",
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'github',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.providerId',
+ name: 'Provider ID',
+ description:
+ 'Identifier of the Identity Provider, the entity that provides SAML authentication.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.secret.secured',
+ name: 'Secret',
+ description: 'Secret provided by GitLab when registering the application.',
+ type: SettingType.PASSWORD,
+ category: 'authentication',
+ subCategory: 'gitlab',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.allowUsersToSignUp',
+ name: 'Allow users to sign-up',
+ description:
+ "Allow new users to authenticate. When set to 'false', only existing users will be able to authenticate to the server.",
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'gitlab',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.loginUrl',
+ name: 'SAML login url',
+ description: 'The URL where the Identity Provider expects to receive SAML requests.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.bitbucket.workspaces',
+ name: 'Workspaces',
+ description:
+ 'Only members of at least one of these workspace will be able to authenticate. Keep empty to disable workspace restriction. You can use either the workspace name, or the workspace slug (ex: https://bitbucket.org/{workspace-slug}).',
+ category: 'authentication',
+ subCategory: 'bitbucket',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.certificate.secured',
+ name: 'Identity provider certificate',
+ description:
+ 'The public X.509 certificate used by the Identity Provider to authenticate SAML messages.',
+ type: SettingType.PASSWORD,
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.groupsSync',
+ name: 'Synchronize teams as groups',
+ description:
+ "For each team they belong to, the user will be associated to a group named 'Organization/Team' (if it exists) in SonarQube.",
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'github',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.gitlab.groupsSync',
+ name: 'Synchronize user groups',
+ description:
+ 'For each GitLab group they belong to, the user will be associated to a group with the same name (if it exists) in SonarQube. If enabled, the GitLab Oauth2 application will need to provide the api scope.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'gitlab',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.auth.gitlab.sync_user_groups',
+ },
+ {
+ key: 'sonar.auth.saml.user.login',
+ name: 'SAML user login attribute',
+ description:
+ 'The name of the attribute where the SAML Identity Provider will put the login of the authenticated user.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.apiUrl',
+ name: 'The API url for a GitHub instance.',
+ description:
+ 'The API url for a GitHub instance. https://api.github.com/ for Github.com, https://github.company.com/api/v3/ when using Github Enterprise',
+ category: 'authentication',
+ subCategory: 'github',
+ defaultValue: 'https://api.github.com/',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.user.name',
+ name: 'SAML user name attribute',
+ description:
+ 'The name of the attribute where the SAML Identity Provider will put the name of the authenticated user.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.webUrl',
+ name: 'The WEB url for a GitHub instance.',
+ description:
+ 'The WEB url for a GitHub instance. https://github.com/ for Github.com, https://github.company.com/ when using GitHub Enterprise.',
+ category: 'authentication',
+ subCategory: 'github',
+ defaultValue: 'https://github.com/',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.github.organizations',
+ name: 'Organizations',
+ description:
+ 'Only members of these organizations will be able to authenticate to the server. If a user is a member of any of the organizations listed they will be authenticated.',
+ category: 'authentication',
+ subCategory: 'github',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.user.email',
+ name: 'SAML user email attribute',
+ description:
+ 'The name of the attribute where the SAML Identity Provider will put the email of the authenticated user.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.group.name',
+ name: 'SAML group attribute',
+ description:
+ 'Attribute defining the user groups in SAML. Users are associated to the default group only if no attribute is defined.',
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.signature.enabled',
+ name: 'Sign requests',
+ description:
+ 'Enables signature of SAML requests. It requires both service provider private key and certificate to be set.',
+ type: SettingType.BOOLEAN,
+ category: 'authentication',
+ subCategory: 'saml',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.sp.privateKey.secured',
+ name: 'Service provider private key',
+ description:
+ 'PKCS8 stored private key used for signing the requests and decrypting responses from the identity provider. ',
+ type: SettingType.PASSWORD,
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.saml.sp.certificate.secured',
+ name: 'Service provider certificate',
+ description: 'X.509 certificate for the service provider, used for signing the requests.',
+ type: SettingType.PASSWORD,
+ category: 'authentication',
+ subCategory: 'saml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cfamily.bullseye.reportPath',
+ name: 'Bullseye XML report',
+ description:
+ 'Path to the Bullseye XML Coverage Report. The path may be either absolute or relative to the project base directory.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Coverage',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.c.file.suffixes',
+ name: 'C file suffixes',
+ description: 'List of suffixes of C files to analyze.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' C and C++',
+ defaultValue: '.c,.h',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cpp.file.suffixes',
+ name: 'C++ file suffixes',
+ description: 'List of suffixes of C++ files to analyze.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' C and C++',
+ defaultValue: '.cc,.cpp,.cxx,.c++,.hh,.hpp,.hxx,.h++,.ipp',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cfamily.cppunit.reportsPath',
+ name: 'CppUnit reports',
+ description:
+ 'Path to the directory containing the *.xml CppUnit report files. The path may be either absolute or relative to the project base directory.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Tests',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.cpp.cppunit.reportsPath',
+ },
+ {
+ key: 'sonar.cfamily.gcov.reportsPath',
+ name: 'Gcov reports',
+ description:
+ 'Path to the directory containing the *.gcov Gcov report files. The path may be either absolute or relative to the project base directory.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Coverage',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.cpp.gcov.reportsPath',
+ },
+ {
+ key: 'sonar.cfamily.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description:
+ 'If set to "true", the file headers (that are usually the same on each file: licensing information for example) are not considered as comments. Thus metrics such as "Comment lines" do not get incremented. If set to "false", those file headers are considered as comments and metrics such as "Comment lines" get incremented.',
+ type: SettingType.BOOLEAN,
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Miscellaneous',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.cpp.ignoreHeaderComments',
+ },
+ {
+ key: 'sonar.cfamily.llvm-cov.reportPath',
+ name: 'llvm-cov report',
+ description:
+ 'Path to the Coverage Report generated by "llvm-cov show". The path may be either absolute or relative to the project base directory.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Coverage',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.objc.file.suffixes',
+ name: 'Objective-C file suffixes',
+ description: 'List of suffixes of Objective-C files to analyze.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Objective-C',
+ defaultValue: '.m',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cfamily.vscoveragexml.reportsPath',
+ name: 'Visual Studio XML reports',
+ description:
+ 'Pattern for search for Visual Studio Coverage XML reports. The pattern may be either absolute or relative to the project base directory. For example: "**/*.coveragexml" will find all "*.coveragexml" files in all sub-directories of current project.',
+ category: 'C / C++ / Objective-C',
+ subCategory: ' Coverage',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.analyzeGeneratedCode',
+ name: 'Analyze generated code',
+ description:
+ 'If set to "true", the files containing generated code are analyzed. If set to "false", the files containing generated code are ignored.',
+ type: SettingType.BOOLEAN,
+ category: 'C#',
+ subCategory: 'C#',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'C#',
+ subCategory: 'C#',
+ defaultValue: '.cs',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description:
+ 'If set to "true", the file headers (that are usually the same on each file: licensing information for example) are not considered as comments. Thus metrics such as "Comment lines" do not get incremented. If set to "false", those file headers are considered as comments and metrics such as "Comment lines" get incremented.',
+ type: SettingType.BOOLEAN,
+ category: 'C#',
+ subCategory: 'C#',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cloudformation.activate',
+ name: 'Activate CloudFormation analysis',
+ description: 'Activate analysis of JSON and Yaml files recognized as CloudFormation files.',
+ type: SettingType.BOOLEAN,
+ category: 'CloudFormation',
+ subCategory: 'General',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cloudformation.file.identifier',
+ name: 'File Identifier',
+ description:
+ 'Files without the identifier are excluded from the analysis. The identifier can be anywhere in the file.',
+ category: 'CloudFormation',
+ subCategory: 'General',
+ defaultValue: 'AWSTemplateFormatVersion',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.aucobol.preprocessor.directives.default',
+ name: 'AcuCobol preprocessor default directives',
+ description:
+ 'This property allows to set preprocessor directives used to compile every COBOL program. See the \'ACUCOBOL-GT Source Code Control directives\' section in the <a target="_blank" href="http://docs.sonarqube.org/display/PLUG/COBOL+Plugin+Advanced+Configuration">documentation of the plugin</a>.',
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.adaprep.activation',
+ name: 'ADAPREP preprocessor activation',
+ description:
+ 'ADAPREP is a COBOL preprocessor which provides the programmer with the full ADABAS (See Software AG) capability. Activating this property is mandatory if the COBOL source code might contain some ADAPREP directives.',
+ type: SettingType.BOOLEAN,
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.copy.suffixes',
+ name: 'Copy suffixes',
+ description:
+ "Comma-separated list of suffixes for copybook to analyze, ex: 'cpy, cbl'. To not filter, leave the list empty.",
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.copy.directories',
+ name: 'Copybook directories',
+ description:
+ 'Comma-separated list of all directories containing some copybooks required to analyze the COBOL programs. Both relative and absolute paths can be used.',
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.copy.exclusions',
+ name: 'Copybooks to exclude',
+ description:
+ 'Comma-separated list of copybooks exclusion patterns. If one copybook name matches one exclusion pattern, no violation will be reported on this copybook. The exclusion pattern must be a case-insensitive regular expression and should not include the copybook suffix.<p>For instance "one\\d*,TWO." will exclude violations reported on one23.cpy, ONE111.cpy, TWOX, and TWO2.cpy copybooks.</p>',
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.byteBasedColumnCount',
+ name: 'Count columns based on bytes',
+ description:
+ 'This property should be set to true if source columns have to be counted based on bytes rather than characters. That may be useful for projects using double-byte character set.',
+ type: SettingType.BOOLEAN,
+ category: 'COBOL',
+ subCategory: 'COBOL',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.preprocessor.skipping.first.matching.characters',
+ name: 'Custom skipping preprocessor first matching characters',
+ description:
+ "<p>To support a maximum number of legacy COBOL preprocessors, this property allows to define which lines of code should be considered as a preprocessing line and so should be ignored when analyzing the COBOL source code.</p><p>For example, if this property is set to '%', all lines starting with a '%' character, and whatever is the position of this first character in the line, won't be analyzed. If this property is set to '%?', all lines starting with a '%' or a '?' will be ignored.</p>",
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.db2include.directories',
+ name: 'DB2 Include directories',
+ description:
+ 'Comma-separated list of all directories (either relative or absolute paths) containing some copybooks required to analyze the COBOL programs. This property is used when interpreting the DB2 INCLUDE preprocessing directive (EXEC SQL INCLUDE ... END-EXEC) to locate the copybook files. When this property is not set, the property "sonar.cobol.copy.directories" is used instead.',
+ category: 'COBOL',
+ subCategory: 'SQL/CICS',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.dialect',
+ name: 'Dialect',
+ description: 'The COBOL dialect to be used for the analysis.',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'COBOL',
+ subCategory: 'COBOL',
+ defaultValue: 'ibm-enterprise-cobol',
+ options: [
+ 'bull-gcos-cobol',
+ 'hp-tandem-cobol',
+ 'ibm-os/vs-cobol',
+ 'ibm-ile-cobol',
+ 'ibm-cobol/ii',
+ 'ibm-cobol/400',
+ 'ibm-enterprise-cobol',
+ 'microfocus-cobol',
+ 'microfocus-acucobol-gt-cobol',
+ 'opencobol/cobol-it',
+ ],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.exec.recoveryMode',
+ name: 'Exec SQL/CICS Recovery mode',
+ description:
+ 'This option must be activated when the COBOL parser is unable to parse a specific SQL dialect like Pro*Cobol for instance.',
+ type: SettingType.BOOLEAN,
+ category: 'COBOL',
+ subCategory: 'SQL/CICS',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.cobol.sql.recoveryMode',
+ },
+ {
+ key: 'sonar.cobol.file.suffixes',
+ name: 'File suffixes',
+ description:
+ 'Comma-separated list of suffixes for files to analyze. To not filter, leave the list empty.',
+ category: 'COBOL',
+ subCategory: 'COBOL',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cpd.cobol.ignoreLiteral',
+ name: 'Ignore literals',
+ description:
+ "If true, CPD ignores literal value differences when evaluating a duplicated block. This means that 'my first text'; and 'my second text' will be seen as equivalent.",
+ type: SettingType.BOOLEAN,
+ category: 'COBOL',
+ subCategory: 'Duplications',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.compilationConstants',
+ name: 'Microfocus Compilation Constants',
+ description:
+ 'If your code takes advantage of conditional compilation features provided by Microfocus, you may have to configure compiler constants for your analysis.',
+ type: SettingType.PROPERTY_SET,
+ category: 'COBOL',
+ subCategory: 'Preprocessor',
+ multiValues: true,
+ options: [],
+ fields: [
+ {
+ key: 'name',
+ name: 'Compilation Constant Name',
+ options: [],
+ },
+ {
+ key: 'value',
+ name: 'Compilation Constant Value',
+ options: [],
+ },
+ ],
+ },
+ {
+ key: 'sonar.cobol.sql.catalog.defaultSchema',
+ name: 'Names of default database schemas',
+ description:
+ 'Comma-separated list of default database schemas used in embedded SQL statements. If a table name matches more than one, the table will be resolved to the first matching schema.',
+ category: 'COBOL',
+ subCategory: 'SQL/CICS',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.sql.catalog.csv.path',
+ name: 'Path for database catalog CSV files',
+ description: 'Path of the directory containing CSV files for the database catalog.',
+ category: 'COBOL',
+ subCategory: 'SQL/CICS',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.sourceFormat',
+ name: 'Source format',
+ description:
+ 'In fixed format, there is both a left and right margin; the indicator area is expected in column 7 and the code area ends in column 72. In variable format, there is only a left margin; the indicator area is expected in column 7. In free format, there is no margin; the indicator area is expected in column 1.',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'COBOL',
+ subCategory: 'COBOL',
+ defaultValue: 'fixed',
+ options: ['fixed', 'variable', 'free'],
+ fields: [],
+ },
+ {
+ key: 'sonar.cobol.tab.width',
+ name: 'Tab width',
+ description:
+ 'Number of expanded spaces for a tab character (\'\t\'). This property really matters when the Source format is "fixed".',
+ type: SettingType.INTEGER,
+ category: 'COBOL',
+ subCategory: 'COBOL',
+ defaultValue: '8',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.css.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'CSS',
+ subCategory: 'General',
+ defaultValue: '.css,.less,.scss',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.docker.activate',
+ name: 'Activate Docker Analysis',
+ description:
+ 'Disabling Docker analysis ensures that no Docker files are parsed, highlighted and analyzed, and no IaC analysis results are included in the quality gate.',
+ type: SettingType.BOOLEAN,
+ category: 'Docker',
+ subCategory: 'General',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.global.exclusions',
+ name: 'Global Source File Exclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.exclusions',
+ name: 'Source File Exclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.global.test.exclusions',
+ name: 'Global Test File Exclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.issue.ignore.allfile',
+ name: 'Ignore Issues on Files',
+ description:
+ 'Patterns to ignore all issues on files that contain a block of code matching a given regular expression.',
+ type: SettingType.PROPERTY_SET,
+ category: 'exclusions',
+ subCategory: 'issues',
+ options: [],
+ fields: [
+ {
+ key: 'fileRegexp',
+ name: 'Regular Expression',
+ description:
+ 'If this regular expression is found in a file, then the whole file is ignored.',
+ options: [],
+ },
+ ],
+ },
+ {
+ key: 'sonar.inclusions',
+ name: 'Source File Inclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.issue.ignore.block',
+ name: 'Ignore Issues in Blocks',
+ description:
+ 'Patterns to ignore all issues on specific blocks of code, while continuing to scan and mark issues on the remainder of the file.',
+ type: SettingType.PROPERTY_SET,
+ category: 'exclusions',
+ subCategory: 'issues',
+ options: [],
+ fields: [
+ {
+ key: 'beginBlockRegexp',
+ name: 'Regular Expression for Start of Block',
+ description:
+ 'If this regular expression is found in a file, then following lines are ignored until end of block.',
+ options: [],
+ },
+ {
+ key: 'endBlockRegexp',
+ name: 'Regular Expression for End of Block',
+ description:
+ 'If specified, this regular expression is used to determine the end of code blocks to ignore. If not, then block ends at the end of file.',
+ options: [],
+ },
+ ],
+ },
+ {
+ key: 'sonar.test.exclusions',
+ name: 'Test File Exclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.issue.ignore.multicriteria',
+ name: 'Ignore Issues on Multiple Criteria',
+ description:
+ 'Patterns to ignore issues on certain components and for certain coding rules.<br/>A rule key pattern consists of the rule repository name, followed by a colon, followed by a rule key or rule name fragment. For example:<ul><li>java:S1195</li><li>java:*Naming*</li></ul>',
+ type: SettingType.PROPERTY_SET,
+ category: 'exclusions',
+ subCategory: 'issues',
+ options: [],
+ fields: [
+ {
+ key: 'ruleKey',
+ name: 'Rule Key Pattern',
+ description: 'Pattern to match rules which should be ignored.',
+ options: [],
+ },
+ {
+ key: 'resourceKey',
+ name: 'File Path Pattern',
+ description: 'Pattern to match files which should be ignored.',
+ options: [],
+ },
+ ],
+ },
+ {
+ key: 'sonar.test.inclusions',
+ name: 'Test File Inclusions',
+ category: 'exclusions',
+ subCategory: 'files',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.issue.enforce.multicriteria',
+ name: 'Restrict Scope of Coding Rules',
+ description:
+ 'Patterns to restrict the application of a rule to only certain components, ignoring all others.<br/>A rule key pattern consists of the rule repository name, followed by a colon, followed by a rule key or rule name fragment. For example:<ul><li>java:S1195</li><li>java:*Naming*</li></ul>',
+ type: SettingType.PROPERTY_SET,
+ category: 'exclusions',
+ subCategory: 'issues',
+ options: [],
+ fields: [
+ {
+ key: 'ruleKey',
+ name: 'Rule Key Pattern',
+ description: 'Pattern used to match rules which should be restricted.',
+ options: [],
+ },
+ {
+ key: 'resourceKey',
+ name: 'File Path Pattern',
+ description: 'Pattern used to match files to which the rules should be restricted.',
+ options: [],
+ },
+ ],
+ },
+ {
+ key: 'sonar.coverage.exclusions',
+ category: 'exclusions',
+ subCategory: 'coverage',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cpd.exclusions',
+ name: 'Duplication Exclusions',
+ description:
+ 'Patterns used to exclude some source files from the duplication detection mechanism. See below to know how to use wildcards to specify this property.',
+ category: 'exclusions',
+ subCategory: 'duplications',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.roslyn.ignoreIssues',
+ name: 'Ignore issues from external Roslyn analyzers',
+ description:
+ "If set to 'true', issues reported by external Roslyn analyzers won't be imported.",
+ type: SettingType.BOOLEAN,
+ category: 'External Analyzers',
+ subCategory: 'VB.NET',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.roslyn.ignoreIssues',
+ name: 'Ignore issues from external Roslyn analyzers',
+ description:
+ "If set to 'true', issues reported by external Roslyn analyzers won't be imported.",
+ type: SettingType.BOOLEAN,
+ category: 'External Analyzers',
+ subCategory: 'C#',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.roslyn.bugCategories',
+ name: 'Rule categories associated with Bugs',
+ description: 'External rule categories to be treated as Bugs.',
+ category: 'External Analyzers',
+ subCategory: 'VB.NET',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.roslyn.bugCategories',
+ name: 'Rule categories associated with Bugs',
+ description: 'External rule categories to be treated as Bugs.',
+ category: 'External Analyzers',
+ subCategory: 'C#',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.roslyn.vulnerabilityCategories',
+ name: 'Rule categories associated with Vulnerabilities',
+ description: 'External rule categories to be treated as Vulnerabilities.',
+ category: 'External Analyzers',
+ subCategory: 'C#',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.roslyn.vulnerabilityCategories',
+ name: 'Rule categories associated with Vulnerabilities',
+ description: 'External rule categories to be treated as Vulnerabilities.',
+ category: 'External Analyzers',
+ subCategory: 'VB.NET',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cs.roslyn.codeSmellCategories',
+ name: 'Rule categories associated with Code Smells',
+ description:
+ 'External rule categories to be treated as Code Smells. By default, external issues are Code Smells, or Bugs when the severity is error.',
+ category: 'External Analyzers',
+ subCategory: 'C#',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.roslyn.codeSmellCategories',
+ name: 'Rule categories associated with Code Smells',
+ description:
+ 'External rule categories to be treated as Code Smells. By default, external issues are Code Smells, or Bugs when the severity is error.',
+ category: 'External Analyzers',
+ subCategory: 'VB.NET',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cloudformation.cfn-lint.reportPaths',
+ name: 'Cfn-Lint Report Files',
+ description: 'Paths (absolute or relative) to the files with Cfn-Lint issues.',
+ category: 'External Analyzers',
+ subCategory: 'CloudFormation',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.androidLint.reportPaths',
+ name: 'Android Lint Report Files',
+ description: 'Paths (absolute or relative) to xml files with Android Lint issues.',
+ category: 'External Analyzers',
+ subCategory: 'Android',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.bandit.reportPaths',
+ name: 'Bandit Report Files',
+ description: 'Paths (absolute or relative) to json files with Bandit issues.',
+ category: 'External Analyzers',
+ subCategory: 'Python',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.java.checkstyle.reportPaths',
+ name: 'Checkstyle Report Files',
+ description: 'Paths (absolute or relative) to xml files with Checkstyle issues.',
+ category: 'External Analyzers',
+ subCategory: 'Java',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.kotlin.detekt.reportPaths',
+ name: 'Detekt Report Files',
+ description: 'Paths (absolute or relative) to checkstyle xml files with Detekt issues.',
+ category: 'External Analyzers',
+ subCategory: 'Kotlin',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.eslint.reportPaths',
+ name: 'ESLint Report Files',
+ description: 'Paths (absolute or relative) to the JSON files with ESLint issues.',
+ category: 'External Analyzers',
+ subCategory: 'JavaScript/TypeScript',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.flake8.reportPaths',
+ name: 'Flake8 Report Files',
+ description: 'Paths (absolute or relative) to report files with Flake8 issues.',
+ category: 'External Analyzers',
+ subCategory: 'Python',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.kotlin.ktlint.reportPaths',
+ name: 'Ktlint Report Files',
+ description: 'Paths (absolute or relative) to checkstyle xml or json files with Ktlint issues.',
+ category: 'External Analyzers',
+ subCategory: 'Kotlin',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.phpstan.reportPaths',
+ name: 'PHPStan Report Files',
+ description: 'Paths (absolute or relative) to report files with PHPStan issues.',
+ category: 'External Analyzers',
+ subCategory: 'PHP',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.apex.pmd.reportPaths',
+ name: 'PMD Report Files',
+ description: 'Paths (absolute or relative) to xml files with PMD issues.',
+ category: 'External Analyzers',
+ subCategory: 'Apex',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.java.pmd.reportPaths',
+ name: 'PMD Report Files',
+ description: 'Paths (absolute or relative) to xml files with PMD issues.',
+ category: 'External Analyzers',
+ subCategory: 'Java',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.psalm.reportPaths',
+ name: 'Psalm Report Files',
+ description: 'Paths (absolute or relative) to report files with Psalm issues.',
+ category: 'External Analyzers',
+ subCategory: 'PHP',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.pylint.reportPaths',
+ name: 'Pylint Report Files',
+ description: 'Paths (absolute or relative) to report files with Pylint issues.',
+ category: 'External Analyzers',
+ subCategory: 'Python',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.ruby.rubocop.reportPaths',
+ name: 'RuboCop Report Files',
+ description: 'Paths (absolute or relative) to json files with RuboCop issues.',
+ category: 'External Analyzers',
+ subCategory: 'Ruby',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.scala.scalastyle.reportPaths',
+ name: 'Scalastyle Report Files',
+ description: 'Paths (absolute or relative) to scalastyle xml files with Scalastyle issues.',
+ category: 'External Analyzers',
+ subCategory: 'Scala',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.scala.scapegoat.reportPaths',
+ name: 'Scapegoat Report Files',
+ description:
+ 'Paths (absolute or relative) to scapegoat xml files using scalastyle format. For example: scapegoat-scalastyle.xml',
+ category: 'External Analyzers',
+ subCategory: 'Scala',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.java.spotbugs.reportPaths',
+ name: 'SpotBugs Report Files',
+ description: 'Paths (absolute or relative) to xml files with SpotBugs issues.',
+ category: 'External Analyzers',
+ subCategory: 'Java',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.css.stylelint.reportPaths',
+ name: 'Stylelint Report Files',
+ description: 'Paths (absolute or relative) to the JSON files with stylelint issues.',
+ category: 'External Analyzers',
+ subCategory: 'CSS',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.swift.swiftLint.reportPaths',
+ name: 'SwiftLint Report Files',
+ description: 'Paths (absolute or relative) to the JSON files with SwiftLint issues.',
+ category: 'External Analyzers',
+ subCategory: 'Swift',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.typescript.tslint.reportPaths',
+ name: 'TSLint Report Files',
+ description: 'Paths (absolute or relative) to the JSON files with TSLint issues.',
+ category: 'External Analyzers',
+ subCategory: 'JavaScript/TypeScript',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.flex.cobertura.reportPaths',
+ name: 'Cobertura xml report paths',
+ description:
+ 'Comma separated list of paths to the Cobertura coverage report file. The paths may be either absolute or relative to the project base directory.',
+ category: 'Flex',
+ subCategory: 'Flex',
+ multiValues: true,
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.flex.cobertura.reportPath',
+ },
+ {
+ key: 'sonar.flex.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for files to analyze. To not filter, leave the list empty.',
+ category: 'Flex',
+ subCategory: 'Flex',
+ defaultValue: 'as',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.smtp_host.secured',
+ name: 'SMTP host',
+ description: 'For example "smtp.gmail.com". Leave blank to disable email sending.',
+ category: 'general',
+ subCategory: 'email',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.login.message',
+ name: 'Log-in Message',
+ description:
+ 'If "Display log-in message" is set to True, the log-in message will be visible to anyone who can access the log-in page of this SonarQube instance.',
+ type: SettingType.FORMATTED_TEXT,
+ category: 'general',
+ subCategory: 'Log-in message',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.smtp_port.secured',
+ name: 'SMTP port',
+ description: 'Port number to connect with SMTP server.',
+ type: SettingType.INTEGER,
+ category: 'general',
+ subCategory: 'email',
+ defaultValue: '25',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.login.displayMessage',
+ name: 'Display log-in Message',
+ description:
+ 'Display the log-in message on the log-in page of this SonarQube instance. <br><br>Note: If the log-in message is empty. It will not appear even if this parameter is set to True',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'Log-in message',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.smtp_secure_connection.secured',
+ name: 'Secure connection',
+ description: 'Type of secure connection. Leave empty to not use secure connection.',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'general',
+ subCategory: 'email',
+ options: ['ssl', 'starttls'],
+ fields: [],
+ },
+ {
+ key: 'email.smtp_username.secured',
+ name: 'SMTP username',
+ description: 'Username to use with authenticated SMTP.',
+ category: 'general',
+ subCategory: 'email',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.smtp_password.secured',
+ name: 'SMTP password',
+ description: 'Password to use with authenticated SMTP.',
+ type: SettingType.PASSWORD,
+ category: 'general',
+ subCategory: 'email',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.from',
+ name: 'From address',
+ description:
+ 'Emails will come from this address. For example - "noreply@sonarsource.com". Note that the mail server may ignore this setting.',
+ category: 'general',
+ subCategory: 'email',
+ defaultValue: 'noreply@nowhere',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.fromName',
+ name: 'From name',
+ description:
+ 'Emails will come from this address name. For example - "SonarQube". Note that the mail server may ignore this setting.',
+ category: 'general',
+ subCategory: 'email',
+ defaultValue: 'SonarQube',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'email.prefix',
+ name: 'Email prefix',
+ description: 'Prefix will be prepended to all outgoing email subjects.',
+ category: 'general',
+ subCategory: 'email',
+ defaultValue: '[SONARQUBE]',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.announcement.message',
+ name: 'Announcement message',
+ description:
+ 'If "Display announcement message" is set to True, this message will be displayed in a warning banner to anyone who can access SonarQube. If this field is empty, no message will be displayed, even if "Display announcement message" is set to True.',
+ type: SettingType.TEXT,
+ category: 'general',
+ subCategory: 'announcement',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.builtInQualityProfiles.disableNotificationOnUpdate',
+ name: 'Avoid quality profiles notification',
+ description:
+ 'Avoid sending email notification on each update of built-in quality profiles to quality profile administrators.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'general',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.cpd.cross_project',
+ name: 'Cross project duplication detection',
+ description:
+ 'DEPRECATED - By default, SonarQube detects duplications at project level. This means that a block duplicated on two different projects won\'t be reported. Setting this parameter to "true" allows to detect duplicates across projects. Note that activating this property will significantly increase each SonarQube analysis time, and therefore badly impact the performances of report processing as more and more projects are getting involved in this cross project duplication mechanism.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'duplications',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.issues.defaultAssigneeLogin',
+ name: 'Default Assignee',
+ description:
+ 'New issues will be assigned to this user each time it is not possible to determine the user who is the author of the issue.',
+ type: SettingType.USER_LOGIN,
+ category: 'general',
+ subCategory: 'issues',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.projectCreation.mainBranchName',
+ name: 'Default main branch name',
+ description:
+ 'Each project has a main branch at creation. This setting defines the instance-wide default main branch name. A user can override this when creating a project. This setting does not apply to projects imported from a DevOps platform.',
+ category: 'general',
+ subCategory: 'subProjectCreation',
+ defaultValue: 'main',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.developerAggregatedInfo.disabled',
+ name: 'Disable developer aggregated information',
+ description: "Don't show issue facets aggregating information per developer",
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'issues',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.announcement.displayMessage',
+ name: 'Display announcement message',
+ description:
+ 'If set to True, the "Announcement message" will be displayed in a warning banner to anyone who can access SonarQube.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'announcement',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.ce.parallelProjectTasks',
+ name: 'Enable running project analysis tasks in parallel',
+ description:
+ 'When enabled, this feature will allow the Compute Engine to process pull request analysis tasks in parallel with other pull request or branch analysis tasks of the same project.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'Compute Engine',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.lf.enableGravatar',
+ name: 'Enable support of gravatars',
+ description: 'Gravatars are profile pictures of users based on their email.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'looknfeel',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.lf.gravatarServerUrl',
+ name: 'Gravatar URL',
+ description:
+ 'Optional URL of custom Gravatar service. Accepted variables are {EMAIL_MD5} for MD5 hash of email and {SIZE} for the picture size in pixels.',
+ category: 'general',
+ subCategory: 'looknfeel',
+ defaultValue: 'https://secure.gravatar.com/avatar/{EMAIL_MD5}.jpg?s={SIZE}&d=identicon',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.qualitygate.ignoreSmallChanges',
+ name: 'Ignore duplication and coverage on small changes',
+ description:
+ 'Quality Gate conditions about duplications in new code and coverage on new code are ignored until the number of new lines is at least 20.',
+ type: SettingType.BOOLEAN,
+ category: 'general',
+ subCategory: 'qualityGate',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.lf.logoUrl',
+ name: 'Logo URL',
+ description: 'URL to logo image. Any standard format is accepted.',
+ category: 'general',
+ subCategory: 'looknfeel',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.branding.image',
+ },
+ {
+ key: 'sonar.core.serverBaseURL',
+ name: 'Server base URL',
+ description:
+ 'HTTP(S) URL of this SonarQube server, such as <i>https://yourhost.yourdomain/sonar</i>. This value is used outside SonarQube itself, e.g. for PR decoration, emails, etc.',
+ category: 'general',
+ subCategory: 'general',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.lf.logoWidthPx',
+ name: 'Width of image in pixels',
+ description: 'Width in pixels, given that the height of the the image is constrained to 30px.',
+ category: 'general',
+ subCategory: 'looknfeel',
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.branding.image.width',
+ },
+ {
+ key: 'sonar.go.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Go',
+ subCategory: 'General',
+ defaultValue: '.go',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.exclusions',
+ name: 'Go Exclusions',
+ description: 'List of file path patterns to be excluded from analysis of Go files.',
+ category: 'Go',
+ subCategory: 'General',
+ defaultValue: '**/vendor/**',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.tests.reportPaths',
+ name: 'Path to test execution report(s)',
+ description:
+ "Path to test execution reports generated by Go with '-json' key, available since go1.10 (e.g.: go test -json > test-report.out).",
+ category: 'Go',
+ subCategory: 'Test and Coverage',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.coverage.reportPaths',
+ name: 'Path to coverage report(s)',
+ description:
+ 'Path to coverage reports generated by Go (e.g.: go test -coverprofile=coverage.out), ant patterns relative to project root are supported.',
+ category: 'Go',
+ subCategory: 'Test and Coverage',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.govet.reportPaths',
+ name: '"go vet" Report Files',
+ description: 'Paths (absolute or relative) to the files with "go vet" issues.',
+ category: 'Go',
+ subCategory: 'Popular Rule Engines',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.golint.reportPaths',
+ name: 'Golint Report Files',
+ description: 'Paths (absolute or relative) to the files with Golint issues.',
+ category: 'Go',
+ subCategory: 'Popular Rule Engines',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.gometalinter.reportPaths',
+ name: 'GoMetaLinter Report Files',
+ description: 'Paths (absolute or relative) to the files with GoMetaLinter issues.',
+ category: 'Go',
+ subCategory: 'Popular Rule Engines',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.go.golangci-lint.reportPaths',
+ name: 'GolangCI-Lint Report Files',
+ description: 'Paths (absolute or relative) to the files with GolangCI-Lint issues.',
+ category: 'Go',
+ subCategory: 'Popular Rule Engines',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.governance.report.project.branch.frequency',
+ name: 'PDF Reports Frequency',
+ description:
+ 'Define the frequency at which to send PDF reports.<ul><li>"Daily" => report is sent on a daily basis</li><li>"Weekly" => report is sent on a weekly basis</li><li>"Monthly" => report is sent on a monthly basis</li></ul>',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'Governance',
+ subCategory: 'Project and Application PDF Reports',
+ defaultValue: 'Monthly',
+ options: ['Daily', 'Weekly', 'Monthly'],
+ fields: [],
+ },
+ {
+ key: 'sonar.portfolios.recompute.hours',
+ name: 'Portfolio Calculation Hours',
+ description:
+ 'Hours of the day at which outdated portfolios will be recalculated. Portfolios will be queued at the beginning of each selected hour. A 24-hour clock is used, so valid values are 0–23. If this value is empty or invalid, each portfolio will be recalculated immediately after it becomes outdated.',
+ type: SettingType.INTEGER,
+ category: 'Governance',
+ subCategory: 'Recalculation',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.governance.report.view.frequency',
+ name: 'Portfolio Reports Frequency',
+ description:
+ 'Define the default frequency that will be used to send PDF reports for portfolios.<ul><li>"Daily" => report is sent during the first portfolio calculation of the day (if any)</li><li>"Weekly" => report is sent during the first portfolio calculation of the week (if any), starting from Midnight on Monday</li><li>"Monthly" => report is sent during the first portfolio calculation of the month (if any), starting from the first day of the current month</li></ul>',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'Governance',
+ subCategory: 'Portfolio PDF Reports',
+ defaultValue: 'Monthly',
+ options: ['Daily', 'Weekly', 'Monthly'],
+ fields: [],
+ },
+ {
+ key: 'sonar.governance.report.view.recipients',
+ name: 'Recipients',
+ description:
+ 'Email addresses of people who will automatically receive a PDF report for every portfolio defined in the system, based on the given frequency.',
+ category: 'Governance',
+ subCategory: 'Portfolio PDF Reports',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.hoursBeforeKeepingOnlyOneSnapshotByDay',
+ name: 'Keep only one analysis a day after',
+ description:
+ 'After this number of hours, if there are several analyses during the same day, the DbCleaner keeps the most recent one and fully deletes the other ones.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '24',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.daysBeforeDeletingInactiveBranchesAndPRs',
+ name: 'Number of days before purging inactive branches and pull requests',
+ description:
+ 'Branches and pull requests are permanently deleted when there has been no analysis for the configured number of days.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'branchesAndPullRequests',
+ defaultValue: '30',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.branchesToKeepWhenInactive',
+ name: 'Branches to keep when inactive',
+ description:
+ 'By default, branches and pull requests are automatically deleted when inactive. This setting allows you to protect branches (but not pull requests) from this deletion. When a branch is created with a name that matches any of the regular expressions on the list of values of this setting, the branch will not be deleted automatically even when it becomes inactive.<br>Example:<ul><li>develop</li><li>release-.*</li></ul>',
+ type: SettingType.REGULAR_EXPRESSION,
+ category: 'housekeeping',
+ subCategory: 'branchesAndPullRequests',
+ defaultValue: 'main,master,develop,trunk',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByWeek',
+ name: 'Keep only one analysis a week after',
+ description:
+ 'After this number of weeks, if there are several analyses during the same week, the DbCleaner keeps the most recent one and fully deletes the other ones',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '4',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.weeksBeforeKeepingOnlyOneSnapshotByMonth',
+ name: 'Keep only one analysis a month after',
+ description:
+ 'After this number of weeks, if there are several analyses during the same month, the DbCleaner keeps the most recent one and fully deletes the other ones.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '52',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.weeksBeforeKeepingOnlyAnalysesWithVersion',
+ name: 'Keep only analyses with a version event after',
+ description:
+ 'After this number of weeks, the DbCleaner keeps only analyses with a version event associated.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '104',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.weeksBeforeDeletingAllSnapshots',
+ name: 'Delete all analyses after',
+ description: 'After this number of weeks, all analyses are fully deleted.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '260',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.daysBeforeDeletingClosedIssues',
+ name: 'Delete closed issues after',
+ description: 'Issues that have been closed for more than this number of days will be deleted.',
+ type: SettingType.INTEGER,
+ category: 'housekeeping',
+ subCategory: 'general',
+ defaultValue: '30',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.dbcleaner.auditHousekeeping',
+ name: 'Audit Logs Housekeeping Frequency',
+ description:
+ 'Define the frequency that will be used to delete security-related audit logs.<br>Setting your housekeeping policy to keep your audit logs for a long period of time (for example, only deleting logs yearly) can increase your database size and the amount of time it takes to download audit logs.<ul><li>"Weekly" => Audit logs older than a week will be deleted</li><li>"Monthly" => Audit logs older than a month will be deleted</li><li>"Trimestrial" => AAudit logs older than 3 months will be deleted</li><li>"Yearly" => Audit logs older than a year will be deleted</li></ul>',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'housekeeping',
+ subCategory: 'auditLogs',
+ defaultValue: 'Monthly',
+ options: ['Weekly', 'Monthly', 'Trimestrial', 'Yearly'],
+ fields: [],
+ },
+ {
+ key: 'sonar.html.file.suffixes',
+ name: 'HTML File suffixes',
+ description: 'List of file suffixes that will be scanned.',
+ category: 'HTML',
+ subCategory: 'HTML',
+ defaultValue: '.html,.xhtml,.cshtml,.vbhtml,.aspx,.ascx,.rhtml,.erb,.shtm,.shtml,.cmp,.twig',
+ multiValues: true,
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.web.file.suffixes',
+ },
+ {
+ key: 'sonar.jsp.file.suffixes',
+ name: 'JSP File suffixes',
+ description: 'List of JSP file suffixes that will be scanned.',
+ category: 'HTML',
+ subCategory: 'HTML',
+ defaultValue: '.jsp,.jspf,.jspx',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.coverage.jacoco.xmlReportPaths',
+ description:
+ 'Paths to JaCoCo XML coverage report files. Each path can be either absolute or relative to the project base directory. Wildcard patterns are accepted (*, ** and ?).',
+ category: 'JaCoCo',
+ subCategory: 'JaCoCo',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.java.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for Java files to analyze. To not filter, leave the list empty.',
+ category: 'java',
+ subCategory: 'General',
+ defaultValue: '.java,.jav',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.junit.reportPaths',
+ name: 'JUnit Report Paths',
+ description:
+ 'Comma-separated paths to the various directories containing the *.xml JUnit report files. Each path may be absolute or relative to the project base directory.',
+ category: 'java',
+ subCategory: 'JUnit',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.globals',
+ name: 'Global variables',
+ description: 'List of global variables.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue: 'angular,goog,google,OenLayers,d3,dojo,dojox,dijit,Backbone,moment,casper,_,sap',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description: 'True to not count file header comments in comment metrics.',
+ type: SettingType.BOOLEAN,
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.environments',
+ name: 'JavaScript execution environments',
+ description:
+ 'List of environments names. The analyzer automatically adds global variables based on that list. Available environment names: amd, applescript, atomtest, browser, commonjs, couch, embertest, flow, greasemonkey, jasmine, jest, jquery, meteor, mocha, mongo, nashorn, node, phantomjs, prototypejs, protractor, qunit, rhino, serviceworker, shared-node-browser, shelljs, webextensions, worker, wsh, yui.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue:
+ 'amd,applescript,atomtest,browser,commonjs,couch,embertest,flow,greasemonkey,jasmine,jest,jquery,meteor,mocha,mongo,nashorn,node,phantomjs,prototypejs,protractor,qunit,rhino,serviceworker,shared-node-browser,shelljs,webextensions,worker,wsh,yui',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.file.suffixes',
+ name: 'JavaScript File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue: '.js,.jsx,.cjs,.mjs,.vue',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.lcov.reportPaths',
+ name: 'LCOV Files',
+ description: 'Paths (absolute or relative) to the files with LCOV data.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'Tests and Coverage',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.javascript.maxFileSize',
+ name: 'Maximum size of analyzed files',
+ description:
+ 'Threshold for the maximum size of analyzed files (in kilobytes). Files that are larger are excluded from the analysis.',
+ type: SettingType.INTEGER,
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue: '1000',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.typescript.file.suffixes',
+ name: 'TypeScript File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'General',
+ defaultValue: '.ts,.tsx,.cts,.mts',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.typescript.tsconfigPaths',
+ name: 'TypeScript tsconfig.json location',
+ description: 'Comma-delimited list of paths to TSConfig files. Wildcards are supported.',
+ category: 'JavaScript / TypeScript',
+ subCategory: 'TypeScript',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.json.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of JSON files to be indexed.',
+ category: SettingType.JSON,
+ subCategory: 'General',
+ defaultValue: '.json',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.kotlin.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Kotlin',
+ subCategory: 'General',
+ defaultValue: '.kt',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.kubernetes.activate',
+ name: 'Activate Kubernetes analysis',
+ description: 'Activate analysis of JSON and Yaml files recognized as Kubernetes files.',
+ type: SettingType.BOOLEAN,
+ category: 'Kubernetes',
+ subCategory: 'General',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.coverage.reportPaths',
+ name: 'Coverage Reports',
+ description:
+ 'List of PHPUnit code coverage report files. Each path can be either absolute or relative.',
+ category: 'PHP',
+ subCategory: 'PHPUnit',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of PHP files to analyze.',
+ category: 'PHP',
+ subCategory: 'General',
+ defaultValue: 'php,php3,php4,php5,phtml,inc',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.exclusions',
+ name: 'PHP Exclusions',
+ description: 'List of file path patterns to be excluded from analysis of PHP files.',
+ category: 'PHP',
+ subCategory: 'General',
+ defaultValue: '**/vendor/**',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.php.tests.reportPath',
+ name: 'Unit Test Report',
+ description:
+ 'Path to the PHPUnit unit test execution report file. The path may be either absolute or relative to the project base directory.',
+ category: 'PHP',
+ subCategory: 'PHPUnit',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.pli.extralingualCharacters',
+ name: 'Extralingual characters',
+ description:
+ 'Extralingual characters which should be considered as valid in identifiers. No separator should be used. Example: #@$£§',
+ category: 'PL/I',
+ subCategory: 'PL/I',
+ defaultValue: '#@$',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.pli.file.suffixes',
+ name: 'File suffixes',
+ description:
+ 'List of suffixes for PL/I files to analyze. To not change the defaults, leave the list empty.',
+ category: 'PL/I',
+ subCategory: 'PL/I',
+ defaultValue: '.pli',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.pli.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description: "Set to 'true' to enable, or 'false' to disable.",
+ type: SettingType.BOOLEAN,
+ category: 'PL/I',
+ subCategory: 'PL/I',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.pli.marginLeft',
+ name: 'Margin left',
+ description:
+ "The column number of the source's leftmost character, must be greater or equal to 1.",
+ type: SettingType.INTEGER,
+ category: 'PL/I',
+ subCategory: 'PL/I',
+ defaultValue: '2',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.pli.marginRight',
+ name: 'Margin right',
+ description:
+ "The column number of the source's rightmost character, must be greater or equal to the leftmost's one, or 0 to indicate an unlimited right margin.",
+ type: SettingType.INTEGER,
+ category: 'PL/I',
+ subCategory: 'PL/I',
+ defaultValue: '72',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.plsql.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'PL/SQL',
+ subCategory: 'General',
+ defaultValue: 'sql,pks,pkb',
+ multiValues: true,
+ options: [],
+ fields: [],
+ deprecatedKey: 'sonar.plsql.suffixes',
+ },
+ {
+ key: 'sonar.plsql.ignoreHeaderComments',
+ name: 'Ignore Header Comments',
+ description:
+ 'If set to "true", the file headers (that are usually the same on each file: licensing information for example) are not considered as comments. Thus metrics such as "Comment lines" do not get incremented. If set to "false", those file headers are considered as comments and metrics such as "Comment lines" get incremented.',
+ type: SettingType.BOOLEAN,
+ category: 'PL/SQL',
+ subCategory: 'General',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of Python files to analyze.',
+ category: 'Python',
+ subCategory: 'General',
+ defaultValue: 'py',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.coverage.reportPaths',
+ name: 'Path to coverage report(s)',
+ description:
+ 'List of paths pointing to coverage reports. Ant patterns are accepted for relative path. The reports have to conform to the Cobertura XML format.',
+ category: 'Python',
+ subCategory: 'Tests and Coverage',
+ defaultValue: 'coverage-reports/*coverage-*.xml',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.coverage.reportPath',
+ name: 'Path to coverage report',
+ description:
+ 'DEPRECATED : Use sonar.python.coverage.reportPaths instead. Path to a coverage report. Ant patterns are accepted for relative path. The report has to conform to the Cobertura XML format.',
+ category: 'Python',
+ subCategory: 'Tests and Coverage',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.xunit.skipDetails',
+ name: 'Skip the details when importing the Xunit reports',
+ description:
+ 'When enabled the test execution statistics is provided only on project level. Use this mode when paths in report are not found. Disabled by default.',
+ type: SettingType.BOOLEAN,
+ category: 'Python',
+ subCategory: 'Tests and Coverage',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.python.xunit.reportPath',
+ name: 'Path to xunit report(s)',
+ description:
+ "Path to the report of test execution, relative to project's root. Ant patterns are accepted. The reports have to conform to the junitreport XML format.",
+ category: 'Python',
+ subCategory: 'Tests and Coverage',
+ defaultValue: 'xunit-reports/xunit-result-*.xml',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.rpg.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of RPG files to analyze.',
+ category: 'RPG',
+ subCategory: 'RPG',
+ defaultValue: '.rpg,.rpgle,.sqlrpgle,.RPG,.RPGLE,.SQLRPGLE',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.rpg.leftMarginWidth',
+ name: 'Left margin width',
+ description:
+ 'Number of characters to the left of the standard 5-character comment block. In an RPG "source physical file" this will typically be 12, but it may not be present in your files depending on your extraction process.',
+ type: SettingType.INTEGER,
+ category: 'RPG',
+ subCategory: 'RPG',
+ defaultValue: '12',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.ruby.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Ruby',
+ subCategory: 'General',
+ defaultValue: '.rb',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.ruby.coverage.reportPaths',
+ name: 'Path to coverage report(s)',
+ description:
+ 'Path to coverage report files (.resultset.json) generated by SimpleCov. The path may be absolute or relative to the project base directory.',
+ category: 'Ruby',
+ subCategory: 'Test and Coverage',
+ defaultValue: 'coverage/.resultset.json',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.ruby.exclusions',
+ name: 'Ruby Exclusions',
+ description: 'List of file path patterns to be excluded from analysis of Ruby files.',
+ category: 'Ruby',
+ subCategory: 'General',
+ defaultValue: '**/vendor/**',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.security.config.roslyn.sonaranalyzer.security.cs',
+ name: 'C# custom configuration',
+ description:
+ "Custom configuration of the C# SAST engine. Details on the expected JSON format can be found on the 'Security Engine Custom Configuration' documentation page.",
+ type: SettingType.JSON,
+ category: 'SAST Engine',
+ subCategory: 'Configuration',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.security.config.javasecurity',
+ name: 'Java custom configuration',
+ description:
+ "Custom configuration of the Java SAST engine. Details on the expected JSON format can be found on the 'Security Engine Custom Configuration' documentation page.",
+ type: SettingType.JSON,
+ category: 'SAST Engine',
+ subCategory: 'Configuration',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.security.config.phpsecurity',
+ name: 'PHP custom configuration',
+ description:
+ "Custom configuration of the PHP SAST engine. Details on the expected JSON format can be found on the 'Security Engine Custom Configuration' documentation page.",
+ type: SettingType.JSON,
+ category: 'SAST Engine',
+ subCategory: 'Configuration',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.security.config.pythonsecurity',
+ name: 'Python custom configuration',
+ description:
+ "Custom configuration of the Python SAST engine. Details on the expected JSON format can be found on the 'Security Engine Custom Configuration' documentation page.",
+ type: SettingType.JSON,
+ category: 'SAST Engine',
+ subCategory: 'Configuration',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.scala.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Scala',
+ subCategory: 'General',
+ defaultValue: '.scala',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.scala.coverage.reportPaths',
+ name: 'Path to Scoverage report',
+ description:
+ 'Path to Scoverage report file(s) (scoverage.xml). Usually in target\\scala-X.X\\scoverage-report',
+ category: 'Scala',
+ subCategory: 'Test and Coverage',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.scm.disabled',
+ name: 'Disable the SCM Sensor',
+ description: 'Disable the retrieval of blame information from Source Control Manager',
+ type: SettingType.BOOLEAN,
+ category: 'scm',
+ subCategory: 'scm',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.text.excluded.file.suffixes',
+ name: 'Additional binary file suffixes',
+ description:
+ 'Additional list of binary file suffixes that should not be analyzed with rules targeting text files.',
+ category: 'Secrets',
+ subCategory: 'General',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.auth.token.max.allowed.lifetime',
+ name: 'Maximum allowed lifetime for token',
+ description:
+ 'Define the maximum lifetime that can be used for new tokens. Existing tokens are not impacted and may need to be manually revoked.',
+ type: SettingType.SINGLE_SELECT_LIST,
+ category: 'security',
+ subCategory: 'security',
+ defaultValue: 'No expiration',
+ options: ['30 days', '90 days', '1 year', 'No expiration'],
+ fields: [],
+ },
+ {
+ key: 'sonar.validateWebhooks',
+ name: 'Enable local webhooks validation',
+ description:
+ 'Forcing local webhooks validation prevents the creation and triggering of local webhooks<br><strong>Disabling this setting can expose the instance to security risks.</strong>',
+ type: SettingType.BOOLEAN,
+ category: 'security',
+ subCategory: 'security',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.allowPermissionManagementForProjectAdmins',
+ name: 'Enable permission management for project administrators',
+ description:
+ "Set if users with 'Administer' role in a project should be allowed to change project permissions. By default users with 'Administer' role are allowed to change both project configuration and project permissions.",
+ type: SettingType.BOOLEAN,
+ category: 'security',
+ subCategory: 'security',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.forceAuthentication',
+ name: 'Force user authentication',
+ description:
+ 'Forcing user authentication prevents anonymous users from accessing the SonarQube UI, or project data via the Web API. Some specific read-only Web APIs, including those required to prompt authentication, are still available anonymously.<br><strong>Disabling this setting can expose the instance to security risks.</strong>',
+ type: SettingType.BOOLEAN,
+ category: 'security',
+ subCategory: 'security',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.swift.coverage.reportPath',
+ name: 'Code coverage report',
+ description:
+ 'DEPRECATED: Path of the coverage report generated from "llvm-cov show". This path can be either absolute or relative to the project directory.',
+ category: 'Swift',
+ subCategory: 'Swift',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.swift.coverage.reportPaths',
+ name: 'Code coverage reports',
+ description:
+ 'Paths to the coverage reports generated from "llvm-cov show". These paths can be either absolute or relative to the project directory.',
+ category: 'Swift',
+ subCategory: 'Swift',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.swift.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'Swift',
+ subCategory: 'Swift',
+ defaultValue: '.swift',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.tsql.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'T-SQL',
+ subCategory: 'General',
+ defaultValue: '.tsql',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.technicalDebt.developmentCost',
+ name: 'Development cost',
+ description:
+ 'Cost to develop one line of code (LOC). Example: if the cost to develop 1 LOC has been estimated at 30 minutes, then the value of this property would be 30.',
+ category: 'technicalDebt',
+ subCategory: 'technicalDebt',
+ defaultValue: '30',
+ options: [],
+ fields: [],
+ deprecatedKey: 'workUnitsBySizePoint',
+ },
+ {
+ key: 'languageSpecificParameters',
+ name: 'Language specific parameters',
+ description:
+ 'DEPRECATED - The parameters specified here for a given language will override the general parameters defined in this section.',
+ type: SettingType.PROPERTY_SET,
+ category: 'technicalDebt',
+ subCategory: 'technicalDebt',
+ options: [],
+ fields: [
+ {
+ key: 'language',
+ name: 'Language Key',
+ description: 'Ex: java, cs, cpp...',
+ options: [],
+ },
+ {
+ key: 'man_days',
+ name: 'Development cost',
+ description: 'If left blank, the generic value defined in this section will be used.',
+ type: SettingType.FLOAT,
+ options: [],
+ },
+ ],
+ deprecatedKey: 'languageSpecificParameters',
+ },
+ {
+ key: 'sonar.technicalDebt.ratingGrid',
+ name: 'Maintainability rating grid',
+ description:
+ 'Maintainability ratings range from A (very good) to E (very bad). The rating is determined by the value of the Technical Debt Ratio, which compares the technical debt on a project to the cost it would take to rewrite the code from scratch. The default values for A through D are 0.05,0.1,0.2,0.5. Anything over 0.5 is an E. Example: assuming the development cost is 30 minutes, a project with a technical debt of 24,000 minutes for 2,500 LOC will have a technical debt ratio of 24000/(30 * 2,500) = 0.32. That yields a maintainability rating of D.',
+ category: 'technicalDebt',
+ subCategory: 'technicalDebt',
+ defaultValue: '0.05,0.1,0.2,0.5',
+ options: [],
+ fields: [],
+ deprecatedKey: 'ratingGrid',
+ },
+ {
+ key: 'sonar.terraform.activate',
+ name: 'Activate Terraform Analysis',
+ description:
+ 'Disabling Terraform analysis ensures that no Terraform files are parsed, highlighted and analyzed, and no IaC analysis results are included in the quality gate.',
+ type: SettingType.BOOLEAN,
+ category: 'Terraform',
+ subCategory: 'General',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.terraform.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of Terraform files to analyze.',
+ category: 'Terraform',
+ subCategory: 'General',
+ defaultValue: '.tf',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.terraform.provider.aws.version',
+ name: 'AWS Provider Version',
+ description:
+ 'Version of the AWS provider of lifecycle management of AWS resources. Use semantic versioning format like `3.4`, `4.17.1` or `4`',
+ category: 'Terraform',
+ subCategory: 'Provider Versions',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.terraform.provider.azure.version',
+ name: 'Azure Provider Version',
+ description:
+ 'Version of the Azure Resource Manager provider of lifecycle management of Microsoft Azure resources. Use semantic versioning format like `3.4`, `4.17.1` or `4`',
+ category: 'Terraform',
+ subCategory: 'Provider Versions',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.analyzeGeneratedCode',
+ name: 'Analyze generated code',
+ description:
+ 'If set to "true", the files containing generated code are analyzed. If set to "false", the files containing generated code are ignored.',
+ type: SettingType.BOOLEAN,
+ category: 'VB.NET',
+ subCategory: 'VB.NET',
+ defaultValue: 'false',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for files to analyze.',
+ category: 'VB.NET',
+ subCategory: 'VB.NET',
+ defaultValue: '.vb',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vbnet.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description:
+ 'If set to "true", the file headers (that are usually the same on each file: licensing information for example) are not considered as comments. Thus metrics such as "Comment lines" do not get incremented. If set to "false", those file headers are considered as comments and metrics such as "Comment lines" get incremented.',
+ type: SettingType.BOOLEAN,
+ category: 'VB.NET',
+ subCategory: 'VB.NET',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vb.file.suffixes',
+ name: 'File Suffixes',
+ description:
+ 'List of suffixes for Visual Basic files to analyze. To not change the defaults, leave the list empty.',
+ category: 'Visual Basic',
+ subCategory: 'Visual Basic',
+ defaultValue: '.bas,.frm,.cls,.ctl,.BAS,.FRM,.CLS,.CTL',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.vb.ignoreHeaderComments',
+ name: 'Ignore header comments',
+ description: "Set to 'true' to enable, or 'false' to disable.",
+ type: SettingType.BOOLEAN,
+ category: 'Visual Basic',
+ subCategory: 'Visual Basic',
+ defaultValue: 'true',
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.xml.file.suffixes',
+ name: 'File suffixes',
+ description: 'List of suffixes for XML files to analyze.',
+ category: 'XML',
+ subCategory: 'XML',
+ defaultValue: '.xml,.xsd,.xsl',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+ {
+ key: 'sonar.yaml.file.suffixes',
+ name: 'File Suffixes',
+ description: 'List of suffixes of YAML files to be indexed.',
+ category: 'YAML',
+ subCategory: 'General',
+ defaultValue: '.yaml,.yml',
+ multiValues: true,
+ options: [],
+ fields: [],
+ },
+];
diff --git a/server/sonar-web/src/main/js/types/features.ts b/server/sonar-web/src/main/js/types/features.ts
index f51364ac2e8..a586efb67a0 100644
--- a/server/sonar-web/src/main/js/types/features.ts
+++ b/server/sonar-web/src/main/js/types/features.ts
@@ -25,4 +25,5 @@ export enum Feature {
MultipleAlm = 'multiple-alm',
ProjectImport = 'project-import',
RegulatoryReport = 'regulatory-reports',
+ Scim = 'scim',
}
diff --git a/server/sonar-web/src/main/js/types/settings.ts b/server/sonar-web/src/main/js/types/settings.ts
index 2412af05bf4..76f3d45751c 100644
--- a/server/sonar-web/src/main/js/types/settings.ts
+++ b/server/sonar-web/src/main/js/types/settings.ts
@@ -63,6 +63,8 @@ export enum SettingType {
SINGLE_SELECT_LIST = 'SINGLE_SELECT_LIST',
PROPERTY_SET = 'PROPERTY_SET',
FORMATTED_TEXT = 'FORMATTED_TEXT',
+ REGULAR_EXPRESSION = 'REGULAR_EXPRESSION',
+ USER_LOGIN = 'USER_LOGIN',
}
export interface SettingDefinition {
description?: string;
diff --git a/sonar-core/src/main/resources/org/sonar/l10n/core.properties b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
index 074a44418a6..a72eb17133d 100644
--- a/sonar-core/src/main/resources/org/sonar/l10n/core.properties
+++ b/sonar-core/src/main/resources/org/sonar/l10n/core.properties
@@ -264,6 +264,7 @@ copy_to_clipboard=Click to copy to clipboard
copied_action=Copied to clipboard
created_by=Created by
default_error_message=The request cannot be processed. Try again later.
+default_save_field_error_message=This field cannot be saved. Try again later.
default_severity=Default severity
edit_permissions=Edit Permissions
facet_might_have_more_results=There might be more results, try another set of filters to see them.
@@ -1289,15 +1290,35 @@ 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
+settings.authentication.form.create=Create configuration
+settings.authentication.form.edit=Edit
+settings.authentication.form.delete=Delete
+settings.authentication.saml.configuration=SAML Configuration
+settings.authentication.saml.confirm.scim=Activate auto provisioning
+settings.authentication.saml.confirm.jit=Use just-in-time provisioning
+settings.authentication.saml.confirm.scim.description=Turning on auto provisioning with SCIM will no longer allow you to edit group, user and group membership. All action will need to be done on your identity provider. Are you sure?
+settings.authentication.saml.confirm.jit.description=Turning off auto provisioning will remove all information provided during SCIM provisioning. This cannot be reverted. Are you sure?
+settings.authentication.saml.form.loading=Loading SAML configuration
+settings.authentication.saml.form.not_configured=SAML is not configured
+settings.authentication.saml.form.enable=Enable configuration
+settings.authentication.saml.form.disable=Disable configuration
+settings.authentication.saml.form.enabled=Enabled
+settings.authentication.saml.form.not_enabled=This configuration is disabled
+settings.authentication.saml.form.create=New SAML configuration
+settings.authentication.saml.form.edit=Edit SAML configuration
settings.authentication.saml.form.save=Save configuration
settings.authentication.saml.form.test=Test configuration
settings.authentication.saml.form.test.help.dirty=You must save your changes
settings.authentication.saml.form.test.help.incomplete=Some mandatory fields are empty
settings.authentication.saml.form.save_success=Saved successfully
settings.authentication.saml.form.save_partial=Saved partially
-settings.authentication.saml.form.save_warn=Please check the error messages in the form above, saving failed for {0} field(s).
-settings.authentication.saml.tooltip.required_fields=Please provide a value for the following required field(s): {0}
-
+settings.authentication.saml.form.provisioning=Provisioning
+settings.authentication.saml.form.provisioning_at_login=Just-in-time user and group provisioning (default)
+settings.authentication.saml.form.provisioning_at_login.sub=Use this option if your identity provider does not support SCIM protocol.
+settings.authentication.saml.form.provisioning_with_scim=Automatically provision user and group with SCIM
+settings.authentication.saml.form.provisioning_with_scim.sub=Prefered option when using the supported identity provider
+settings.authentication.saml.form.provisioning_with_scim.description=Users and groups are automatically provisioned from your identity provider using SCIM protocol. Once activated, managed users and groups can only be modified from your identity provider. Existing local users and groups will be kept. See {doc} for more details
+settings.authentication.saml.enable_first=Enable your SAML configuration to benefit from automatic user provisioning options.
settings.pr_decoration.binding.category=DevOps Platform Integration
settings.pr_decoration.binding.no_bindings=A system administrator needs to enable this feature in the global settings.
settings.pr_decoration.binding.no_bindings.admin=Set up a {link} first before you and your team can enable Pull Request Decoration.