You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Authentication.tsx 5.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2022 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 * as React from 'react';
  21. import { FormattedMessage } from 'react-intl';
  22. import { useSearchParams } from 'react-router-dom';
  23. import Link from '../../../../components/common/Link';
  24. import ScreenPositionHelper from '../../../../components/common/ScreenPositionHelper';
  25. import BoxedTabs, { getTabId, getTabPanelId } from '../../../../components/controls/BoxedTabs';
  26. import { Alert } from '../../../../components/ui/Alert';
  27. import { translate } from '../../../../helpers/l10n';
  28. import { getBaseUrl } from '../../../../helpers/system';
  29. import { searchParamsToQuery } from '../../../../helpers/urls';
  30. import { AlmKeys } from '../../../../types/alm-settings';
  31. import { ExtendedSettingDefinition } from '../../../../types/settings';
  32. import { AUTHENTICATION_CATEGORY } from '../../constants';
  33. import CategoryDefinitionsList from '../CategoryDefinitionsList';
  34. import SamlAuthentication from './SamlAuthentication';
  35. interface Props {
  36. definitions: ExtendedSettingDefinition[];
  37. }
  38. // We substract the footer height with padding (80) and the main layout padding (20)
  39. const HEIGHT_ADJUSTMENT = 100;
  40. const SAML = 'saml';
  41. export type AuthenticationTabs =
  42. | typeof SAML
  43. | AlmKeys.GitHub
  44. | AlmKeys.GitLab
  45. | AlmKeys.BitbucketServer;
  46. const DOCUMENTATION_LINK_SUFFIXES = {
  47. [SAML]: 'saml/overview',
  48. [AlmKeys.GitHub]: 'github',
  49. [AlmKeys.GitLab]: 'gitlab',
  50. [AlmKeys.BitbucketServer]: 'bitbucket-cloud'
  51. };
  52. function renderDevOpsIcon(key: string) {
  53. return (
  54. <img
  55. alt={key}
  56. className="spacer-right"
  57. height={16}
  58. src={`${getBaseUrl()}/images/alm/${key}.svg`}
  59. />
  60. );
  61. }
  62. export default function Authentication(props: Props) {
  63. const { definitions } = props;
  64. const [query, setSearchParams] = useSearchParams();
  65. const currentTab = (query.get('tab') || SAML) as AuthenticationTabs;
  66. const tabs = [
  67. {
  68. key: SAML,
  69. label: 'SAML'
  70. },
  71. {
  72. key: AlmKeys.GitHub,
  73. label: (
  74. <>
  75. {renderDevOpsIcon(AlmKeys.GitHub)}
  76. GitHub
  77. </>
  78. )
  79. },
  80. {
  81. key: AlmKeys.BitbucketServer,
  82. label: (
  83. <>
  84. {renderDevOpsIcon(AlmKeys.BitbucketServer)}
  85. Bitbucket
  86. </>
  87. )
  88. },
  89. {
  90. key: AlmKeys.GitLab,
  91. label: (
  92. <>
  93. {renderDevOpsIcon(AlmKeys.GitLab)}
  94. GitLab
  95. </>
  96. )
  97. }
  98. ];
  99. return (
  100. <>
  101. <header className="page-header">
  102. <h1 className="page-title">{translate('settings.authentication.title')}</h1>
  103. </header>
  104. <div className="spacer-top huge-spacer-bottom">
  105. <p>{translate('settings.authentication.description')}</p>
  106. </div>
  107. <BoxedTabs
  108. onSelect={(tab: AuthenticationTabs) => {
  109. setSearchParams({ ...searchParamsToQuery(query), tab });
  110. }}
  111. selected={currentTab}
  112. tabs={tabs}
  113. />
  114. {/* Adding a key to force re-rendering of the tab container, so that it resets the scroll position */}
  115. <ScreenPositionHelper>
  116. {({ top }) => (
  117. <div
  118. style={{
  119. maxHeight: `calc(100vh - ${top + HEIGHT_ADJUSTMENT}px)`
  120. }}
  121. className="bordered overflow-y-auto tabbed-definitions"
  122. key={currentTab}
  123. role="tabpanel"
  124. aria-labelledby={getTabId(currentTab)}
  125. id={getTabPanelId(currentTab)}>
  126. <div className="big-padded-top big-padded-left big-padded-right">
  127. <Alert variant="info">
  128. <FormattedMessage
  129. id="settings.authentication.help"
  130. defaultMessage={translate('settings.authentication.help')}
  131. values={{
  132. link: (
  133. <Link
  134. to={`/documentation/instance-administration/authentication/${DOCUMENTATION_LINK_SUFFIXES[currentTab]}/`}
  135. rel="noopener noreferrer"
  136. target="_blank">
  137. {translate('settings.authentication.help.link')}
  138. </Link>
  139. )
  140. }}
  141. />
  142. </Alert>
  143. {currentTab === SAML && (
  144. <SamlAuthentication
  145. definitions={definitions.filter(def => def.subCategory === SAML)}
  146. />
  147. )}
  148. {currentTab !== SAML && (
  149. <CategoryDefinitionsList
  150. category={AUTHENTICATION_CATEGORY}
  151. definitions={definitions}
  152. subCategory={currentTab}
  153. displaySubCategoryTitle={false}
  154. />
  155. )}
  156. </div>
  157. </div>
  158. )}
  159. </ScreenPositionHelper>
  160. </>
  161. );
  162. }