]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-19084 Warn when github authentication configuration has missing properties
authorMathieu Suen <mathieu.suen@sonarsource.com>
Mon, 8 May 2023 14:56:30 +0000 (16:56 +0200)
committersonartech <sonartech@sonarsource.com>
Thu, 11 May 2023 20:03:14 +0000 (20:03 +0000)
server/sonar-web/src/main/js/apps/settings/components/authentication/ConfigurationForm.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/GithubAuthenticationTab.tsx
server/sonar-web/src/main/js/apps/settings/components/authentication/hook/useGithubConfiguration.ts
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index 8f6406b86720d7645fa728a24e3fb183cf0bb8bf..e07e859adc6f1136812dc5295e4c1a1eceb908a7 100644 (file)
@@ -42,6 +42,7 @@ interface Props {
   onReload: () => Promise<void>;
   tab: AuthenticationTabs;
   excludedField: string[];
+  hasLegacyConfiguration?: boolean;
 }
 
 interface ErrorValue {
@@ -50,7 +51,16 @@ interface ErrorValue {
 }
 
 export default function ConfigurationForm(props: Props) {
-  const { create, loading, values, setNewValue, canBeSave, tab, excludedField } = props;
+  const {
+    create,
+    loading,
+    values,
+    setNewValue,
+    canBeSave,
+    tab,
+    excludedField,
+    hasLegacyConfiguration,
+  } = props;
   const [errors, setErrors] = React.useState<Dict<ErrorValue>>({});
 
   const headerLabel = translate('settings.authentication.form', create ? 'create' : 'edit', tab);
@@ -104,10 +114,13 @@ export default function ConfigurationForm(props: Props) {
             loading={loading}
             ariaLabel={translate('settings.authentication.form.loading')}
           >
-            <Alert variant="info">
+            <Alert variant={hasLegacyConfiguration ? 'warning' : 'info'}>
               <FormattedMessage
-                id="settings.authentication.help"
-                defaultMessage={translate('settings.authentication.help')}
+                id={`settings.authentication.${hasLegacyConfiguration ? 'legacy_help' : 'help'}`}
+                defaultMessage={translate(
+                  `settings.authentication.${hasLegacyConfiguration ? 'legacy_help' : 'help'}`,
+                  tab
+                )}
                 values={{
                   link: (
                     <DocLink
@@ -123,6 +136,8 @@ export default function ConfigurationForm(props: Props) {
               if (excludedField.includes(val.key)) {
                 return null;
               }
+
+              const isSet = hasLegacyConfiguration ? false : !val.isNotSet;
               return (
                 <div key={val.key}>
                   <AuthenticationFormField
@@ -130,7 +145,7 @@ export default function ConfigurationForm(props: Props) {
                     definition={val.definition}
                     mandatory={val.mandatory}
                     onFieldChange={setNewValue}
-                    isNotSet={val.isNotSet}
+                    isNotSet={!isSet}
                     error={errors[val.key]?.message}
                   />
                 </div>
index 88055fb19d14d540b1daa6203452e5ed6ec52b1b..ca32c00991bd2e6de547eb2d2c391bb8c4b196b7 100644 (file)
  * 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 } from 'lodash';
 import React, { useState } from 'react';
 import { FormattedMessage } from 'react-intl';
-import {
-  activateGithubProvisioning,
-  deactivateGithubProvisioning,
-  resetSettingValue,
-  setSettingValue,
-} from '../../../../api/settings';
 import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
 import DocLink from '../../../../components/common/DocLink';
 import ConfirmModal from '../../../../components/controls/ConfirmModal';
@@ -42,10 +35,7 @@ import { ExtendedSettingDefinition } from '../../../../types/settings';
 import { DOCUMENTATION_LINK_SUFFIXES } from './Authentication';
 import AuthenticationFormField from './AuthenticationFormField';
 import ConfigurationForm from './ConfigurationForm';
-import useGithubConfiguration, {
-  GITHUB_ENABLED_FIELD,
-  GITHUB_JIT_FIELDS,
-} from './hook/useGithubConfiguration';
+import useGithubConfiguration, { GITHUB_JIT_FIELDS } from './hook/useGithubConfiguration';
 
 interface GithubAuthenticationProps {
   definitions: ExtendedSettingDefinition[];
@@ -82,6 +72,10 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
     setNewGithubProvisioningStatus,
     hasGithubProvisioningConfigChange,
     resetJitSetting,
+    saveGroup,
+    changeProvisioning,
+    toggleEnable,
+    hasLegacyConfiguration,
   } = useGithubConfiguration(definitions, props.onReload);
 
   const hasDifferentProvider = provider !== undefined && provider !== Provider.Github;
@@ -94,40 +88,6 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
     setShowEditModal(false);
   };
 
-  const handleConfirmChangeProvisioning = async () => {
-    if (newGithubProvisioningStatus && newGithubProvisioningStatus !== githubProvisioningStatus) {
-      await activateGithubProvisioning();
-      await reload();
-    } else {
-      if (newGithubProvisioningStatus !== githubProvisioningStatus) {
-        await deactivateGithubProvisioning();
-      }
-      await handleSaveGroup();
-    }
-  };
-
-  const handleSaveGroup = async () => {
-    await Promise.all(
-      GITHUB_JIT_FIELDS.map(async (settingKey) => {
-        const value = values[settingKey];
-        if (value.newValue !== undefined) {
-          if (isEmpty(value.newValue) && typeof value.newValue !== 'boolean') {
-            await resetSettingValue({ keys: value.definition.key });
-          } else {
-            await setSettingValue(value.definition, value.newValue);
-          }
-        }
-      })
-    );
-    await reload();
-  };
-
-  const handleToggleEnable = async () => {
-    const value = values[GITHUB_ENABLED_FIELD];
-    await setSettingValue(value.definition, !enabled);
-    await reload();
-  };
-
   return (
     <div className="authentication-configuration">
       <div className="spacer-bottom display-flex-space-between display-flex-center">
@@ -141,11 +101,29 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
           </div>
         )}
       </div>
-      {!hasConfiguration ? (
+      {!hasConfiguration && !hasLegacyConfiguration && (
         <div className="big-padded text-center huge-spacer-bottom authentication-no-config">
           {translate('settings.authentication.github.form.not_configured')}
         </div>
-      ) : (
+      )}
+      {!hasConfiguration && hasLegacyConfiguration && (
+        <div className="big-padded">
+          <Alert variant="warning">
+            <FormattedMessage
+              id="settings.authentication.github.form.legacy_configured"
+              defaultMessage={translate('settings.authentication.github.form.legacy_configured')}
+              values={{
+                documentation: (
+                  <DocLink to="/instance-administration/authentication/github">
+                    {translate('documentation')}
+                  </DocLink>
+                ),
+              }}
+            />
+          </Alert>
+        </div>
+      )}
+      {hasConfiguration && (
         <>
           <div className="spacer-bottom big-padded bordered display-flex-space-between">
             <div>
@@ -163,7 +141,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
               </p>
               <Button
                 className="spacer-top"
-                onClick={handleToggleEnable}
+                onClick={toggleEnable}
                 disabled={githubProvisioningStatus}
               >
                 {enabled
@@ -189,7 +167,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
                 if (newGithubProvisioningStatus !== githubProvisioningStatus) {
                   setShowConfirmProvisioningModal(true);
                 } else {
-                  await handleSaveGroup();
+                  await saveGroup();
                 }
               }}
             >
@@ -312,7 +290,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
               )}
               {showConfirmProvisioningModal && (
                 <ConfirmModal
-                  onConfirm={() => handleConfirmChangeProvisioning()}
+                  onConfirm={() => changeProvisioning()}
                   header={translate(
                     'settings.authentication.github.confirm',
                     newGithubProvisioningStatus ? 'auto' : 'jit'
@@ -344,6 +322,7 @@ export default function GithubAuthenticationTab(props: GithubAuthenticationProps
           onClose={handleCancelConfiguration}
           create={!hasConfiguration}
           onReload={reload}
+          hasLegacyConfiguration={hasLegacyConfiguration}
         />
       )}
     </div>
index 168325e60b20ee2f173ee4925fd86c0280279556..dea619fd54ae78dbdee4d0bd744d6011476a612d 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-import { some } from 'lodash';
+import { isEmpty, some } from 'lodash';
 import { useCallback, useContext, useEffect, useState } from 'react';
-import { fetchIsGithubProvisioningEnabled } from '../../../../../api/settings';
+import {
+  activateGithubProvisioning,
+  deactivateGithubProvisioning,
+  fetchIsGithubProvisioningEnabled,
+  resetSettingValue,
+  setSettingValue,
+} from '../../../../../api/settings';
 import { AvailableFeaturesContext } from '../../../../../app/components/available-features/AvailableFeaturesContext';
 import { Feature } from '../../../../../types/features';
 import { ExtendedSettingDefinition } from '../../../../../types/settings';
@@ -28,6 +34,7 @@ import useConfiguration from './useConfiguration';
 export const GITHUB_ENABLED_FIELD = 'sonar.auth.github.enabled';
 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.organizations',
   'sonar.auth.github.allowUsersToSignUp',
@@ -76,6 +83,7 @@ export default function useGithubConfiguration(
   const enabled = values[GITHUB_ENABLED_FIELD]?.value === 'true';
   const appId = values[GITHUB_APP_ID_FIELD]?.value as string;
   const url = values[GITHUB_API_URL_FIELD]?.value;
+  const clientIdIsNotSet = values[GITHUB_CLIENT_ID_FIELD]?.isNotSet;
 
   const reload = useCallback(async () => {
     await reloadConfig();
@@ -83,6 +91,42 @@ export default function useGithubConfiguration(
     onReload();
   }, [reloadConfig, onReload]);
 
+  const changeProvisioning = async () => {
+    if (newGithubProvisioningStatus && newGithubProvisioningStatus !== githubProvisioningStatus) {
+      await activateGithubProvisioning();
+      await reload();
+    } else {
+      if (newGithubProvisioningStatus !== githubProvisioningStatus) {
+        await deactivateGithubProvisioning();
+      }
+      await saveGroup();
+    }
+  };
+
+  const saveGroup = async () => {
+    await Promise.all(
+      GITHUB_JIT_FIELDS.map(async (settingKey) => {
+        const value = values[settingKey];
+        if (value.newValue !== undefined) {
+          if (isEmpty(value.newValue) && typeof value.newValue !== 'boolean') {
+            await resetSettingValue({ keys: value.definition.key });
+          } else {
+            await setSettingValue(value.definition, value.newValue);
+          }
+        }
+      })
+    );
+    await reload();
+  };
+
+  const toggleEnable = async () => {
+    const value = values[GITHUB_ENABLED_FIELD];
+    await setSettingValue(value.definition, !enabled);
+    await reload();
+  };
+
+  const hasLegacyConfiguration = appId === undefined && !clientIdIsNotSet;
+
   return {
     ...config,
     reload,
@@ -95,6 +139,10 @@ export default function useGithubConfiguration(
     newGithubProvisioningStatus,
     setNewGithubProvisioningStatus,
     hasGithubProvisioningConfigChange,
+    changeProvisioning,
+    saveGroup,
     resetJitSetting,
+    toggleEnable,
+    hasLegacyConfiguration,
   };
 }
index 58f7a67c6dcd2e90381987a4a28b993152c48ab8..853047d3aca51f60c5c55e8cd92b5d20d2e643b7 100644 (file)
@@ -1320,6 +1320,7 @@ settings.authentication.custom_message_information=You can define a custom log-i
 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.legacy_help.github=Your configuration is no longer supported, please checkout the {link} on how to change your configuration.
 settings.authentication.help.link=documentation
 settings.authentication.form.create=Create configuration
 settings.authentication.form.edit=Edit
@@ -1343,6 +1344,7 @@ settings.authentication.github.confirm.auto.description=After you switch to auto
 settings.authentication.github.confirm.jit.description=Switching to Just-in-Time provisioning removes all information provided while automatic provisioning through SCIM was active. These changes cannot be reverted. Are you sure?
 settings.authentication.github.configuration=GitHub Configuration
 settings.authentication.github.form.not_configured=GitHub App is not configured
+settings.authentication.github.form.legacy_configured=You current configuration is no longer supported. It will continue working but with limitted support.See the {documentation} for more information.
 settings.authentication.github.enable_first=Enable your GitHub configuration for more provisioning options.
 settings.authentication.github.form.provisioning_with_github=Automatic user and group provisioning
 settings.authentication.github.form.provisioning_with_github.description=Users and groups are automatically provisioned from your GitHub organizations. Once activated, managed users and groups can only be modified from your GitHub organizations/teams. Existing local users and groups will be kept.