]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-18654 Disable group creation when user auto provisioning is enable
authorMathieu Suen <mathieu.suen@sonarsource.com>
Fri, 10 Mar 2023 09:41:59 +0000 (10:41 +0100)
committersonartech <sonartech@sonarsource.com>
Wed, 22 Mar 2023 20:04:07 +0000 (20:04 +0000)
server/sonar-web/src/main/js/apps/groups/components/App.tsx
server/sonar-web/src/main/js/apps/groups/components/Header.tsx
server/sonar-web/src/main/js/apps/groups/components/__tests__/App-test.tsx
server/sonar-web/src/main/js/apps/groups/components/__tests__/__snapshots__/Header-test.tsx.snap
sonar-core/src/main/resources/org/sonar/l10n/core.properties

index e282044653b1502e1c248d0440aad7dadc52cd11..31cd060f6c9291925d6313914e04c6cda794fece 100644 (file)
 import { omit } from 'lodash';
 import * as React from 'react';
 import { Helmet } from 'react-helmet-async';
+import { getSystemInfo } from '../../../api/system';
 import { createGroup, deleteGroup, searchUsersGroups, updateGroup } from '../../../api/user_groups';
 import ListFooter from '../../../components/controls/ListFooter';
 import SearchBox from '../../../components/controls/SearchBox';
 import Suggestions from '../../../components/embed-docs-modal/Suggestions';
 import { translate } from '../../../helpers/l10n';
 import { omitNil } from '../../../helpers/request';
-import { Group, Paging } from '../../../types/types';
+import { Group, Paging, SysInfoCluster } from '../../../types/types';
 import DeleteForm from './DeleteForm';
 import Form from './Form';
 import Header from './Header';
@@ -39,6 +40,7 @@ interface State {
   loading: boolean;
   paging?: Paging;
   query: string;
+  manageProvider?: string;
 }
 
 export default class App extends React.PureComponent<{}, State> {
@@ -48,6 +50,7 @@ export default class App extends React.PureComponent<{}, State> {
   componentDidMount() {
     this.mounted = true;
     this.fetchGroups();
+    this.fetchManageInstance();
   }
 
   componentWillUnmount() {
@@ -62,6 +65,15 @@ export default class App extends React.PureComponent<{}, State> {
     });
   };
 
+  async fetchManageInstance() {
+    const info = (await getSystemInfo()) as SysInfoCluster;
+    if (this.mounted) {
+      this.setState({
+        manageProvider: info.System['External Users and Groups Provisioning'],
+      });
+    }
+  }
+
   stopLoading = () => {
     if (this.mounted) {
       this.setState({ loading: false });
@@ -188,7 +200,8 @@ export default class App extends React.PureComponent<{}, State> {
   };
 
   render() {
-    const { editedGroup, groupToBeDeleted, groups, loading, paging, query } = this.state;
+    const { editedGroup, groupToBeDeleted, groups, loading, paging, query, manageProvider } =
+      this.state;
 
     const showAnyone = 'anyone'.includes(query.toLowerCase());
 
@@ -197,7 +210,7 @@ export default class App extends React.PureComponent<{}, State> {
         <Suggestions suggestions="user_groups" />
         <Helmet defer={false} title={translate('user_groups.page')} />
         <main className="page page-limited" id="groups-page">
-          <Header onCreate={this.handleCreate} />
+          <Header onCreate={this.handleCreate} manageProvider={manageProvider} />
 
           <SearchBox
             className="big-spacer-bottom"
index 678446455bd9eae8344414d6c06acc6f3ff6ba5b..81e71d6cd8df7682f2e7055bf182e52d9d9d1fb9 100644 (file)
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
 import * as React from 'react';
+import { FormattedMessage } from 'react-intl';
+import DocLink from '../../../components/common/DocLink';
 import { Button } from '../../../components/controls/buttons';
+import { Alert } from '../../../components/ui/Alert';
 import { translate } from '../../../helpers/l10n';
 import Form from './Form';
 
 interface Props {
   onCreate: (data: { description: string; name: string }) => Promise<void>;
+  manageProvider?: string;
 }
 
-interface State {
-  createModal: boolean;
-}
-
-export default class Header extends React.PureComponent<Props, State> {
-  mounted = false;
-  state: State = { createModal: false };
-
-  componentDidMount() {
-    this.mounted = true;
-  }
-
-  componentWillUnmount() {
-    this.mounted = false;
-  }
-
-  handleCreateClick = () => {
-    this.setState({ createModal: true });
-  };
-
-  handleClose = () => {
-    if (this.mounted) {
-      this.setState({ createModal: false });
-    }
-  };
-
-  handleSubmit = (data: { name: string; description: string }) => {
-    return this.props.onCreate(data);
-  };
+export default function Header(props: Props) {
+  const { manageProvider } = props;
+  const [createModal, setCreateModal] = React.useState(false);
 
-  render() {
-    return (
-      <>
-        <header className="page-header" id="groups-header">
-          <h1 className="page-title">{translate('user_groups.page')}</h1>
+  return (
+    <>
+      <div className="page-header" id="groups-header">
+        <h2 className="page-title">{translate('user_groups.page')}</h2>
 
-          <div className="page-actions">
-            <Button id="groups-create" onClick={this.handleCreateClick}>
-              {translate('groups.create_group')}
-            </Button>
-          </div>
+        <div className="page-actions">
+          <Button
+            id="groups-create"
+            disabled={manageProvider !== undefined}
+            onClick={() => setCreateModal(true)}
+          >
+            {translate('groups.create_group')}
+          </Button>
+        </div>
 
+        {manageProvider === undefined ? (
           <p className="page-description">{translate('user_groups.page.description')}</p>
-        </header>
-        {this.state.createModal && (
-          <Form
-            confirmButtonText={translate('create')}
-            header={translate('groups.create_group')}
-            onClose={this.handleClose}
-            onSubmit={this.handleSubmit}
-          />
+        ) : (
+          <Alert className="page-description max-width-100 width-100" variant="info">
+            <FormattedMessage
+              defaultMessage={translate('user_groups.page.managed_description')}
+              id="user_groups.page.managed_description"
+              values={{
+                provider: manageProvider,
+                link: (
+                  <DocLink to="/instance-administration/authentication/overview/">
+                    {translate('documentation')}
+                  </DocLink>
+                ),
+              }}
+            />
+          </Alert>
         )}
-      </>
-    );
-  }
+      </div>
+      {createModal && (
+        <Form
+          confirmButtonText={translate('create')}
+          header={translate('groups.create_group')}
+          onClose={() => setCreateModal(false)}
+          onSubmit={props.onCreate}
+        />
+      )}
+    </>
+  );
 }
index 3c64bcf3a03f343bf200ba1fd734ec835252d88a..8858be2fd5ea64a29b4447288edc8717e4c90856 100644 (file)
@@ -57,6 +57,10 @@ jest.mock('../../../../api/user_groups', () => ({
   updateGroup: jest.fn().mockResolvedValue({}),
 }));
 
+jest.mock('../../../../api/system', () => ({
+  getSystemInfo: jest.fn().mockResolvedValue({ System: {} }),
+}));
+
 beforeEach(() => {
   jest.clearAllMocks();
 });
index 3a12724c7c936f5cd66a46f71a203d28519c8630..ba792ca89fcfe6d597fd89a139645f35872fff9c 100644 (file)
@@ -2,19 +2,20 @@
 
 exports[`should create new group 1`] = `
 <Fragment>
-  <header
+  <div
     className="page-header"
     id="groups-header"
   >
-    <h1
+    <h2
       className="page-title"
     >
       user_groups.page
-    </h1>
+    </h2>
     <div
       className="page-actions"
     >
       <Button
+        disabled={false}
         id="groups-create"
         onClick={[Function]}
       >
@@ -26,25 +27,26 @@ exports[`should create new group 1`] = `
     >
       user_groups.page.description
     </p>
-  </header>
+  </div>
 </Fragment>
 `;
 
 exports[`should create new group 2`] = `
 <Fragment>
-  <header
+  <div
     className="page-header"
     id="groups-header"
   >
-    <h1
+    <h2
       className="page-title"
     >
       user_groups.page
-    </h1>
+    </h2>
     <div
       className="page-actions"
     >
       <Button
+        disabled={false}
         id="groups-create"
         onClick={[Function]}
       >
@@ -56,12 +58,12 @@ exports[`should create new group 2`] = `
     >
       user_groups.page.description
     </p>
-  </header>
+  </div>
   <Form
     confirmButtonText="create"
     header="groups.create_group"
     onClose={[Function]}
-    onSubmit={[Function]}
+    onSubmit={[MockFunction]}
   />
 </Fragment>
 `;
index f0c58f35917cfbaa94944b6fb42e47ee02d7cda6..5ea15bc32c38442add08b0e9ce896af123a314a7 100644 (file)
@@ -4392,6 +4392,7 @@ users.change_admin_password.form.continue_to_app=Continue to SonarQube
 #------------------------------------------------------------------------------
 user_groups.page=Groups
 user_groups.page.description=Create and administer groups of users.
+user_groups.page.managed_description=Your instance is managed by {provider}. No modification is allowed. You can still delete local groups. All other operations should be done on your identity provider. See {link} for help managing groups. 
 user_groups.anyone.description=Anybody who browses the application belongs to this group. If authentication is not enforced, assigned permissions also apply to non-authenticated users.
 groups.delete_group=Delete Group
 groups.delete_group.confirmation=Are you sure you want to delete "{0}"?