aboutsummaryrefslogtreecommitdiffstats
path: root/server/sonar-web/src/main/js/app/components/ProjectAdminContainer.tsx
blob: 379c0fc5ce5b2bf0d3e2182e5723d9011f0be9c1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
/*
 * SonarQube
 * Copyright (C) 2009-2024 SonarSource SA
 * mailto:info AT sonarsource DOT com
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 3 of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
import * as React from 'react';
import { Outlet } from 'react-router-dom';
import A11ySkipTarget from '../../components/a11y/A11ySkipTarget';
import { Component } from '../../types/types';
import handleRequiredAuthorization from '../utils/handleRequiredAuthorization';
import withComponentContext from './componentContext/withComponentContext';

interface Props {
  component: Component;
}

export class ProjectAdminContainer extends React.PureComponent<Props> {
  mounted = false;

  componentDidMount() {
    // We need to check permissions *after* the parent ComponentContainer is updated.
    // This is why we wrap checkPermission() in a setTimeout().
    //
    // We want to prevent an edge-case where if you navigate from an admin component page
    // to another component page where you have "read" access but no admin access, and
    // then hit the browser's Back button, you will get redirected to the login page.
    //
    // The reason is that this component will get mounted *before* the parent
    // ComponentContainer is *updated*. The parent component still has the last component
    // stored in state, the one where the user might not have admin access. It will detect
    // the change in URL and trigger a refresh of its state, but this comes too late; the
    // checkPermission() here will already have triggered (because mounts happen before updates)
    // and redirected the user. Wrapping it in a setTimeout() allows the parent component
    // to refresh, unmounting this component, and only triggering the redirect if it makes sense.
    //
    // See https://sonarsource.atlassian.net/browse/SONAR-19437
    setTimeout(this.checkPermissions, 0);
    this.mounted = true;
  }

  componentDidUpdate() {
    this.checkPermissions();
  }

  componentWillUnmount() {
    this.mounted = false;
  }

  checkPermissions = () => {
    if (this.mounted && !this.isProjectAdmin()) {
      handleRequiredAuthorization();
    }
  };

  isProjectAdmin = () => {
    const { configuration } = this.props.component;
    return configuration != null && configuration.showSettings;
  };

  render() {
    if (!this.isProjectAdmin()) {
      return null;
    }

    return (
      <main>
        <A11ySkipTarget anchor="admin_main" />
        <Outlet />
      </main>
    );
  }
}

export default withComponentContext(ProjectAdminContainer);