Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

GithubAuthenticationTab.tsx 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. import React, { useState } from 'react';
  21. import { FormattedMessage } from 'react-intl';
  22. import GitHubSynchronisationWarning from '../../../../app/components/GitHubSynchronisationWarning';
  23. import DocLink from '../../../../components/common/DocLink';
  24. import ConfirmModal from '../../../../components/controls/ConfirmModal';
  25. import RadioCard from '../../../../components/controls/RadioCard';
  26. import { Button, ResetButtonLink, SubmitButton } from '../../../../components/controls/buttons';
  27. import { Provider } from '../../../../components/hooks/useManageProvider';
  28. import DeleteIcon from '../../../../components/icons/DeleteIcon';
  29. import EditIcon from '../../../../components/icons/EditIcon';
  30. import { Alert } from '../../../../components/ui/Alert';
  31. import { translate, translateWithParameters } from '../../../../helpers/l10n';
  32. import {
  33. useCheckGitHubConfigQuery,
  34. useIdentityProviderQuery,
  35. useSyncWithGitHubNow,
  36. } from '../../../../queries/identity-provider';
  37. import { AlmKeys } from '../../../../types/alm-settings';
  38. import { ExtendedSettingDefinition } from '../../../../types/settings';
  39. import { AuthenticationTabs, DOCUMENTATION_LINK_SUFFIXES } from './Authentication';
  40. import AuthenticationFormField from './AuthenticationFormField';
  41. import AutoProvisioningConsent from './AutoProvisionningConsent';
  42. import ConfigurationForm from './ConfigurationForm';
  43. import GitHubConfigurationValidity from './GitHubConfigurationValidity';
  44. import useGithubConfiguration, { GITHUB_JIT_FIELDS } from './hook/useGithubConfiguration';
  45. interface GithubAuthenticationProps {
  46. definitions: ExtendedSettingDefinition[];
  47. currentTab: AuthenticationTabs;
  48. }
  49. const GITHUB_EXCLUDED_FIELD = [
  50. 'sonar.auth.github.enabled',
  51. 'sonar.auth.github.groupsSync',
  52. 'sonar.auth.github.allowUsersToSignUp',
  53. ];
  54. export default function GithubAuthenticationTab(props: GithubAuthenticationProps) {
  55. const { definitions, currentTab } = props;
  56. const { data } = useIdentityProviderQuery();
  57. const [showEditModal, setShowEditModal] = useState(false);
  58. const [showConfirmProvisioningModal, setShowConfirmProvisioningModal] = useState(false);
  59. const {
  60. hasConfiguration,
  61. hasGithubProvisioning,
  62. githubProvisioningStatus,
  63. isLoading,
  64. values,
  65. setNewValue,
  66. canBeSave,
  67. url,
  68. appId,
  69. enabled,
  70. newGithubProvisioningStatus,
  71. setNewGithubProvisioningStatus,
  72. hasGithubProvisioningTypeChange,
  73. hasGithubProvisioningConfigChange,
  74. resetJitSetting,
  75. saveGroup,
  76. changeProvisioning,
  77. toggleEnable,
  78. hasLegacyConfiguration,
  79. deleteMutation: { isLoading: isDeleting, mutate: deleteConfiguration },
  80. } = useGithubConfiguration(definitions);
  81. const hasDifferentProvider = data?.provider !== undefined && data.provider !== Provider.Github;
  82. const { canSyncNow, synchronizeNow } = useSyncWithGitHubNow();
  83. const { refetch } = useCheckGitHubConfigQuery(enabled);
  84. const handleCreateConfiguration = () => {
  85. setShowEditModal(true);
  86. };
  87. const handleCloseConfiguration = () => {
  88. refetch();
  89. setShowEditModal(false);
  90. };
  91. return (
  92. <div className="authentication-configuration">
  93. <div className="spacer-bottom display-flex-space-between display-flex-center">
  94. <h4>{translate('settings.authentication.github.configuration')}</h4>
  95. {!hasConfiguration && (
  96. <div>
  97. <Button onClick={handleCreateConfiguration}>
  98. {translate('settings.authentication.form.create')}
  99. </Button>
  100. </div>
  101. )}
  102. </div>
  103. {enabled && (
  104. <GitHubConfigurationValidity
  105. selectedOrganizations={
  106. (values['sonar.auth.github.organizations']?.value as string[]) ?? []
  107. }
  108. isAutoProvisioning={!!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
  109. />
  110. )}
  111. {!hasConfiguration && !hasLegacyConfiguration && (
  112. <div className="big-padded text-center huge-spacer-bottom authentication-no-config">
  113. {translate('settings.authentication.github.form.not_configured')}
  114. </div>
  115. )}
  116. {!hasConfiguration && hasLegacyConfiguration && (
  117. <div className="big-padded">
  118. <Alert variant="warning">
  119. <FormattedMessage
  120. id="settings.authentication.github.form.legacy_configured"
  121. defaultMessage={translate('settings.authentication.github.form.legacy_configured')}
  122. values={{
  123. documentation: (
  124. <DocLink to="/instance-administration/authentication/github">
  125. {translate('documentation')}
  126. </DocLink>
  127. ),
  128. }}
  129. />
  130. </Alert>
  131. </div>
  132. )}
  133. {hasConfiguration && (
  134. <>
  135. <div className="spacer-bottom big-padded bordered display-flex-space-between">
  136. <div>
  137. <h5>{translateWithParameters('settings.authentication.github.appid_x', appId)}</h5>
  138. <p>{url}</p>
  139. <Button
  140. className="spacer-top"
  141. onClick={toggleEnable}
  142. disabled={githubProvisioningStatus}
  143. >
  144. {enabled
  145. ? translate('settings.authentication.form.disable')
  146. : translate('settings.authentication.form.enable')}
  147. </Button>
  148. </div>
  149. <div>
  150. <Button className="spacer-right" onClick={handleCreateConfiguration}>
  151. <EditIcon />
  152. {translate('settings.authentication.form.edit')}
  153. </Button>
  154. <Button
  155. className="button-red"
  156. disabled={enabled || isDeleting}
  157. onClick={deleteConfiguration}
  158. >
  159. <DeleteIcon />
  160. {translate('settings.authentication.form.delete')}
  161. </Button>
  162. </div>
  163. </div>
  164. <div className="spacer-bottom big-padded bordered display-flex-space-between">
  165. <form
  166. onSubmit={async (e) => {
  167. e.preventDefault();
  168. if (hasGithubProvisioningTypeChange) {
  169. setShowConfirmProvisioningModal(true);
  170. } else {
  171. await saveGroup();
  172. }
  173. }}
  174. >
  175. <fieldset className="display-flex-column big-spacer-bottom">
  176. <label className="h5">
  177. {translate('settings.authentication.form.provisioning')}
  178. </label>
  179. {enabled ? (
  180. <div className="display-flex-row spacer-top">
  181. <RadioCard
  182. label={translate(
  183. 'settings.authentication.github.form.provisioning_with_github'
  184. )}
  185. title={translate(
  186. 'settings.authentication.github.form.provisioning_with_github'
  187. )}
  188. selected={newGithubProvisioningStatus ?? githubProvisioningStatus}
  189. onClick={() => setNewGithubProvisioningStatus(true)}
  190. disabled={!hasGithubProvisioning || hasDifferentProvider}
  191. >
  192. {hasGithubProvisioning ? (
  193. <>
  194. {hasDifferentProvider && (
  195. <p className="spacer-bottom text-bold">
  196. {translate('settings.authentication.form.other_provisioning_enabled')}
  197. </p>
  198. )}
  199. <p className="spacer-bottom">
  200. {translate(
  201. 'settings.authentication.github.form.provisioning_with_github.description'
  202. )}
  203. </p>
  204. <p className="spacer-bottom">
  205. <FormattedMessage
  206. id="settings.authentication.github.form.provisioning_with_github.description.doc"
  207. defaultMessage={translate(
  208. 'settings.authentication.github.form.provisioning_with_github.description.doc'
  209. )}
  210. values={{
  211. documentation: (
  212. <DocLink
  213. to={`/instance-administration/authentication/${
  214. DOCUMENTATION_LINK_SUFFIXES[AlmKeys.GitHub]
  215. }/`}
  216. >
  217. {translate('documentation')}
  218. </DocLink>
  219. ),
  220. }}
  221. />
  222. </p>
  223. {githubProvisioningStatus && <GitHubSynchronisationWarning />}
  224. <div className="sw-flex sw-flex-1 sw-items-end">
  225. <Button
  226. className="spacer-top width-30"
  227. onClick={synchronizeNow}
  228. disabled={!canSyncNow}
  229. >
  230. {translate('settings.authentication.github.synchronize_now')}
  231. </Button>
  232. </div>
  233. </>
  234. ) : (
  235. <p>
  236. <FormattedMessage
  237. id="settings.authentication.github.form.provisioning.disabled"
  238. defaultMessage={translate(
  239. 'settings.authentication.github.form.provisioning.disabled'
  240. )}
  241. values={{
  242. documentation: (
  243. // Documentation page not ready yet.
  244. <DocLink to="/instance-administration/authentication/github">
  245. {translate('documentation')}
  246. </DocLink>
  247. ),
  248. }}
  249. />
  250. </p>
  251. )}
  252. </RadioCard>
  253. <RadioCard
  254. label={translate('settings.authentication.form.provisioning_at_login')}
  255. title={translate('settings.authentication.form.provisioning_at_login')}
  256. selected={!(newGithubProvisioningStatus ?? githubProvisioningStatus)}
  257. onClick={() => setNewGithubProvisioningStatus(false)}
  258. >
  259. {Object.values(values).map((val) => {
  260. if (!GITHUB_JIT_FIELDS.includes(val.key)) {
  261. return null;
  262. }
  263. return (
  264. <div key={val.key}>
  265. <AuthenticationFormField
  266. settingValue={values[val.key]?.newValue ?? values[val.key]?.value}
  267. definition={val.definition}
  268. mandatory={val.mandatory}
  269. onFieldChange={setNewValue}
  270. isNotSet={val.isNotSet}
  271. />
  272. </div>
  273. );
  274. })}
  275. </RadioCard>
  276. </div>
  277. ) : (
  278. <Alert className="big-spacer-top" variant="info">
  279. {translate('settings.authentication.github.enable_first')}
  280. </Alert>
  281. )}
  282. </fieldset>
  283. {enabled && (
  284. <>
  285. <SubmitButton disabled={!hasGithubProvisioningConfigChange}>
  286. {translate('save')}
  287. </SubmitButton>
  288. <ResetButtonLink
  289. className="spacer-left"
  290. onClick={() => {
  291. setNewGithubProvisioningStatus(undefined);
  292. resetJitSetting();
  293. }}
  294. disabled={!hasGithubProvisioningConfigChange}
  295. >
  296. {translate('cancel')}
  297. </ResetButtonLink>
  298. </>
  299. )}
  300. {showConfirmProvisioningModal && (
  301. <ConfirmModal
  302. onConfirm={() => changeProvisioning()}
  303. header={translate(
  304. 'settings.authentication.github.confirm',
  305. newGithubProvisioningStatus ? 'auto' : 'jit'
  306. )}
  307. onClose={() => setShowConfirmProvisioningModal(false)}
  308. isDestructive={!newGithubProvisioningStatus}
  309. confirmButtonText={translate('yes')}
  310. >
  311. {translate(
  312. 'settings.authentication.github.confirm',
  313. newGithubProvisioningStatus ? 'auto' : 'jit',
  314. 'description'
  315. )}
  316. </ConfirmModal>
  317. )}
  318. </form>
  319. </div>
  320. </>
  321. )}
  322. {showEditModal && (
  323. <ConfigurationForm
  324. tab={AlmKeys.GitHub}
  325. excludedField={GITHUB_EXCLUDED_FIELD}
  326. loading={isLoading}
  327. values={values}
  328. setNewValue={setNewValue}
  329. canBeSave={canBeSave}
  330. onClose={handleCloseConfiguration}
  331. create={!hasConfiguration}
  332. hasLegacyConfiguration={hasLegacyConfiguration}
  333. />
  334. )}
  335. {currentTab === AlmKeys.GitHub && <AutoProvisioningConsent />}
  336. </div>
  337. );
  338. }