import { cloneDeep, isArray, isObject, isString } from 'lodash';
import { HousekeepingPolicy } from '../../apps/audit-logs/utils';
import { mockDefinition, mockSettingFieldDefinition } from '../../helpers/mocks/settings';
+import { isDefined } from '../../helpers/types';
import { BranchParameters } from '../../types/branch-like';
import {
ExtendedSettingDefinition,
mockSettingFieldDefinition({ key: 'value', name: 'Value' }),
],
}),
+ mockDefinition({
+ category: 'authentication',
+ defaultValue: 'true',
+ key: 'sonar.auth.github.allowUsersToSignUp',
+ subCategory: 'github',
+ name: 'Compilation Constants',
+ description: 'Lets do it',
+ type: SettingType.BOOLEAN,
+ }),
];
export default class SettingsServiceMock {
};
handleGetValues = (data: { keys: string[]; component?: string } & BranchParameters) => {
- const settings = this.#settingValues.filter((s) => data.keys.includes(s.key));
+ const settings = data.keys
+ .map((k) => {
+ const def = this.#definitions.find((d) => d.key === k);
+ const v = this.#settingValues.find((s) => s.key === k);
+ if (v === undefined && def?.type === SettingType.BOOLEAN) {
+ return { key: k, value: def.defaultValue, inherited: true };
+ }
+ return v;
+ })
+ .filter(isDefined);
+
return this.reply(settings);
};
setting.fieldValues = [];
} else if (definition.multiValues === true) {
setting.values = definition.defaultValue?.split(',') ?? [];
- } else {
+ } else if (setting) {
setting.value = definition.defaultValue ?? '';
}
} from '../../../../components/controls/ValidationInput';
import MandatoryFieldMarker from '../../../../components/ui/MandatoryFieldMarker';
import { ExtendedSettingDefinition, SettingType } from '../../../../types/settings';
-import { isSecuredDefinition } from '../../utils';
+import { getPropertyDescription, getPropertyName, isSecuredDefinition } from '../../utils';
import AuthenticationMultiValueField from './AuthenticationMultiValuesField';
import AuthenticationSecuredField from './AuthenticationSecuredField';
import AuthenticationToggleField from './AuthenticationToggleField';
export default function AuthenticationFormField(props: SamlToggleFieldProps) {
const { mandatory = false, definition, settingValue, isNotSet, error } = props;
+ const name = getPropertyName(definition);
+ const description = getPropertyDescription(definition);
+
return (
<div className="settings-definition">
<div className="settings-definition-left">
<label className="h3" htmlFor={definition.key}>
- {definition.name}
+ {name}
</label>
{mandatory && <MandatoryFieldMarker />}
- {definition.description && (
- <div className="markdown small spacer-top">{definition.description}</div>
- )}
+ {definition.description && <div className="markdown small spacer-top">{description}</div>}
</div>
<div className="settings-definition-right big-padded-top display-flex-column">
{definition.multiValues && (
currentTab: AuthenticationTabs;
}
-const GITHUB_EXCLUDED_FIELD = [
- 'sonar.auth.github.enabled',
- 'sonar.auth.github.groupsSync',
- 'sonar.auth.github.allowUsersToSignUp',
-];
+const GITHUB_EXCLUDED_FIELD = ['sonar.auth.github.enabled', 'sonar.auth.github.allowUsersToSignUp'];
export default function GithubAuthenticationTab(props: GithubAuthenticationProps) {
const { definitions, currentTab } = props;
</label>
{enabled ? (
- <div className="display-flex-row spacer-top">
+ <div className="display-flex-column spacer-top">
<RadioCard
+ label={translate('settings.authentication.form.provisioning_at_login')}
+ title={translate('settings.authentication.form.provisioning_at_login')}
+ selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
+ onClick={() => setNewGithubProvisioningStatus(false)}
+ >
+ <p className="spacer-bottom">
+ <FormattedMessage id="settings.authentication.github.form.provisioning_at_login.description" />
+ </p>
+ <p className="spacer-bottom">
+ <FormattedMessage
+ id="settings.authentication.github.form.description.doc"
+ tagName="p"
+ values={{
+ documentation: (
+ <DocLink
+ to={`/instance-administration/authentication/${
+ DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
+ }/`}
+ >
+ {translate('documentation')}
+ </DocLink>
+ ),
+ }}
+ />
+ </p>
+
+ {!(newGithubProvisioningStatus ?? githubProvisioningStatus) && (
+ <>
+ <hr />
+ {Object.values(values).map((val) => {
+ if (!GITHUB_JIT_FIELDS.includes(val.key)) {
+ return null;
+ }
+ return (
+ <div key={val.key}>
+ <AuthenticationFormField
+ settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
+ definition={val.definition}
+ mandatory={val.mandatory}
+ onFieldChange={setNewValue}
+ isNotSet={val.isNotSet}
+ />
+ </div>
+ );
+ })}
+ </>
+ )}
+ </RadioCard>
+ <RadioCard
+ className="spacer-top"
label={translate(
'settings.authentication.github.form.provisioning_with_github'
)}
{hasGithubProvisioning ? (
<>
{hasDifferentProvider && (
- <p className="spacer-bottom text-bold">
+ <p className="spacer-bottom text-bold ">
{translate('settings.authentication.form.other_provisioning_enabled')}
</p>
)}
</p>
<p className="spacer-bottom">
<FormattedMessage
- id="settings.authentication.github.form.provisioning_with_github.description.doc"
- defaultMessage={translate(
- 'settings.authentication.github.form.provisioning_with_github.description.doc'
- )}
+ id="settings.authentication.github.form.description.doc"
values={{
documentation: (
<DocLink
}}
/>
</p>
+
{githubProvisioningStatus && <GitHubSynchronisationWarning />}
- <div className="sw-flex sw-flex-1 sw-items-end">
+
+ <div className="sw-flex sw-flex-1">
<Button
className="spacer-top width-30"
onClick={synchronizeNow}
</p>
)}
</RadioCard>
- <RadioCard
- label={translate('settings.authentication.form.provisioning_at_login')}
- title={translate('settings.authentication.form.provisioning_at_login')}
- selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
- onClick={() => setNewGithubProvisioningStatus(false)}
- >
- {Object.values(values).map((val) => {
- if (!GITHUB_JIT_FIELDS.includes(val.key)) {
- return null;
- }
- return (
- <div key={val.key}>
- <AuthenticationFormField
- settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
- definition={val.definition}
- mandatory={val.mandatory}
- onFieldChange={setNewValue}
- isNotSet={val.isNotSet}
- />
- </div>
- );
- })}
- </RadioCard>
</div>
) : (
<Alert className="big-spacer-top" variant="info">
{translate('settings.authentication.form.provisioning')}
</label>
{samlEnabled ? (
- <div className="display-flex-row spacer-top">
+ <div className="display-flex-column spacer-top">
<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>
+ </RadioCard>
+ <RadioCard
+ className="spacer-top"
label={translate('settings.authentication.saml.form.provisioning_with_scim')}
title={translate('settings.authentication.saml.form.provisioning_with_scim')}
selected={newScimStatus ?? scimStatus}
<p>
<FormattedMessage
id="settings.authentication.saml.form.provisioning.disabled"
- defaultMessage={translate(
- 'settings.authentication.saml.form.provisioning.disabled'
- )}
values={{
documentation: (
<DocLink to="/instance-administration/authentication/saml/scim/overview">
{translate('settings.authentication.form.other_provisioning_enabled')}
</p>
)}
- <p className="spacer-bottom">
+ <p className="spacer-bottom ">
{translate(
'settings.authentication.saml.form.provisioning_with_scim.sub'
)}
</p>
- <p className="spacer-bottom">
+ <p className="spacer-bottom ">
{translate(
'settings.authentication.saml.form.provisioning_with_scim.description'
)}
</>
)}
</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>
- </RadioCard>
</div>
) : (
<Alert className="big-spacer-top" variant="info">
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' }),
+ providerName: byRole('textbox', { name: 'property.sonar.auth.saml.providerName.name' }),
+ providerId: byRole('textbox', { name: 'property.sonar.auth.saml.providerId.name' }),
+ providerCertificate: byRole('textbox', {
+ name: 'property.sonar.auth.saml.certificate.secured.name',
+ }),
+ loginUrl: byRole('textbox', { name: 'property.sonar.auth.saml.loginUrl.name' }),
+ userLoginAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.login.name' }),
+ userNameAttribute: byRole('textbox', { name: 'property.sonar.auth.saml.user.name.name' }),
saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
confirmProvisioningButton: byRole('button', { name: 'yes' }),
saveScim: byRole('button', { name: 'save' }),
tab: byRole('tab', { name: 'github GitHub' }),
noGithubConfiguration: byText('settings.authentication.github.form.not_configured'),
createConfigButton: byRole('button', { name: 'settings.authentication.form.create' }),
- clientId: byRole('textbox', { name: 'Client ID' }),
- clientSecret: byRole('textbox', { name: 'Client Secret' }),
- githubAppId: byRole('textbox', { name: 'GitHub App ID' }), // not working
- privateKey: byRole('textarea', { name: 'Private Key' }), // not working
- githubApiUrl: byRole('textbox', { name: 'The API url for a GitHub instance.' }),
- githubWebUrl: byRole('textbox', { name: 'The WEB url for a GitHub instance.' }),
+ clientId: byRole('textbox', { name: 'property.sonar.auth.github.clientId.secured.name' }),
+ clientSecret: byRole('textbox', {
+ name: 'property.sonar.auth.github.clientSecret.secured.name',
+ }),
+ githubApiUrl: byRole('textbox', { name: 'property.sonar.auth.github.apiUrl.name' }),
+ githubWebUrl: byRole('textbox', { name: 'property.sonar.auth.github.webUrl.name' }),
allowUserToSignUp: byRole('switch', {
name: 'sonar.auth.github.allowUsersToSignUp',
}),
- syncGroupsAsTeams: byRole('switch', { name: 'sonar.auth.github.groupsSync' }),
- organizations: byRole('textbox', { name: 'Organizations' }),
+ organizations: byRole('textbox', { name: 'property.sonar.auth.github.organizations.name' }),
saveConfigButton: byRole('button', { name: 'settings.almintegration.form.save' }),
confirmProvisioningButton: byRole('button', { name: 'yes' }),
saveGithubProvisioning: byRole('button', { name: 'save' }),
expect(github.saveGithubProvisioning.get()).toBeDisabled();
await user.click(github.allowUserToSignUp.get());
- await user.click(github.syncGroupsAsTeams.get());
expect(github.saveGithubProvisioning.get()).toBeEnabled();
await user.click(github.saveGithubProvisioning.get());
export const GITHUB_APP_ID_FIELD = 'sonar.auth.github.appId';
export const GITHUB_API_URL_FIELD = 'sonar.auth.github.apiUrl';
export const GITHUB_CLIENT_ID_FIELD = 'sonar.auth.github.clientId.secured';
-export const GITHUB_JIT_FIELDS = [
- 'sonar.auth.github.allowUsersToSignUp',
- 'sonar.auth.github.groupsSync',
-];
+export const GITHUB_JIT_FIELDS = ['sonar.auth.github.allowUsersToSignUp'];
export const OPTIONAL_FIELDS = [
GITHUB_ENABLED_FIELD,
...GITHUB_JIT_FIELDS,
'sonar.auth.github.organizations',
+ 'sonar.auth.github.groupsSync',
];
export interface SamlSettingValue {
}
.authentication-configuration .radio-card {
- width: 50%;
+ width: 100%;
+ min-height: 250px;
background-color: var(--neutral50);
border: 1px solid var(--neutral200);
}
justify-content: space-between;
}
-.authentication-configuration .radio-card-header {
- justify-content: space-around;
-}
-
.authentication-configuration .radio-card-body {
justify-content: flex-start;
}
}
.authentication-configuration .settings-definition-right {
- display: flex;
- align-items: center;
- width: 50%;
+ align-items: end;
+ width: 20%;
}
settings.authentication.github.form.provisioning_with_github_short.autoProvisioning=Automatic provisioning
settings.authentication.github.form.provisioning_with_github_short.jit=Just-in-Time provisioning
settings.authentication.github.form.provisioning_with_github.description=Users, groups and permissions are automatically provisioned from your GitHub organizations. Once activated, users and groups can only be created and modified from your GitHub organizations/teams. Existing local users will be kept and can only be deactivated.
-settings.authentication.github.form.provisioning_with_github.description.doc=For more details, see {documentation}.
+settings.authentication.github.form.description.doc=For more details, see {documentation}.
+settings.authentication.github.form.provisioning_at_login.description=User and group will be synchronise only when user log into Sonarqube.
settings.authentication.github.form.provisioning.disabled=Your current edition does not support provisioning with GitHub. See the {documentation} for more information.
settings.authentication.github.synchronize_now=Synchronize now
settings.authentication.github.synchronization_in_progress=Synchronization is in progress.