github.com/minio/console@v1.4.1/web-app/src/screens/Console/Users/UserDetails.tsx (about)

     1  // This file is part of MinIO Console Server
     2  // Copyright (c) 2021 MinIO, Inc.
     3  //
     4  // This program is free software: you can redistribute it and/or modify
     5  // it under the terms of the GNU Affero General Public License as published by
     6  // the Free Software Foundation, either version 3 of the License, or
     7  // (at your option) any later version.
     8  //
     9  // This program is distributed in the hope that it will be useful,
    10  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    11  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    12  // GNU Affero General Public License for more details.
    13  //
    14  // You should have received a copy of the GNU Affero General Public License
    15  // along with this program.  If not, see <http://www.gnu.org/licenses/>.
    16  
    17  import React, { Fragment, useCallback, useEffect, useState } from "react";
    18  import { useNavigate, useParams } from "react-router-dom";
    19  import {
    20    AddIcon,
    21    BackLink,
    22    Box,
    23    Button,
    24    DataTable,
    25    Grid,
    26    IAMPoliciesIcon,
    27    PageLayout,
    28    PasswordKeyIcon,
    29    ScreenTitle,
    30    SectionTitle,
    31    Switch,
    32    Tabs,
    33    TrashIcon,
    34    UsersIcon,
    35  } from "mds";
    36  import { IPolicyItem } from "./types";
    37  import { ErrorResponseHandler } from "../../../common/types";
    38  import { decodeURLString, encodeURLString } from "../../../common/utils";
    39  import { setHelpName, setModalErrorSnackMessage } from "../../../systemSlice";
    40  import {
    41    assignGroupPermissions,
    42    assignIAMPolicyPermissions,
    43    CONSOLE_UI_RESOURCE,
    44    deleteUserPermissions,
    45    disableUserPermissions,
    46    editServiceAccountPermissions,
    47    enableDisableUserPermissions,
    48    enableUserPermissions,
    49    getGroupPermissions,
    50    IAM_PAGES,
    51    permissionTooltipHelper,
    52  } from "../../../common/SecureComponent/permissions";
    53  import { hasPermission } from "../../../common/SecureComponent";
    54  import { useAppDispatch } from "../../../store";
    55  import { policyDetailsSort } from "../../../utils/sortFunctions";
    56  import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
    57  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    58  import HelpMenu from "../HelpMenu";
    59  import api from "../../../common/api";
    60  import ChangeUserGroups from "./ChangeUserGroups";
    61  import SetUserPolicies from "./SetUserPolicies";
    62  import UserServiceAccountsPanel from "./UserServiceAccountsPanel";
    63  import ChangeUserPasswordModal from "../Account/ChangeUserPasswordModal";
    64  import DeleteUser from "./DeleteUser";
    65  
    66  interface IGroupItem {
    67    group: string;
    68  }
    69  
    70  const UserDetails = () => {
    71    const dispatch = useAppDispatch();
    72    const params = useParams();
    73    const navigate = useNavigate();
    74  
    75    const [loading, setLoading] = useState<boolean>(false);
    76    const [addGroupOpen, setAddGroupOpen] = useState<boolean>(false);
    77    const [policyOpen, setPolicyOpen] = useState<boolean>(false);
    78    const [addLoading, setAddLoading] = useState<boolean>(false);
    79    const [enabled, setEnabled] = useState<boolean>(false);
    80    const [selectedGroups, setSelectedGroups] = useState<string[]>([]);
    81    const [currentGroups, setCurrentGroups] = useState<IGroupItem[]>([]);
    82    const [currentPolicies, setCurrentPolicies] = useState<IPolicyItem[]>([]);
    83    const [changeUserPasswordModalOpen, setChangeUserPasswordModalOpen] =
    84      useState<boolean>(false);
    85    const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
    86    const [hasPolicy, setHasPolicy] = useState<boolean>(false);
    87    const [selectedTab, setSelectedTab] = useState<string>("groups");
    88  
    89    const enableEnabled =
    90      hasPermission(CONSOLE_UI_RESOURCE, enableUserPermissions) && !enabled;
    91    const disableEnabled =
    92      hasPermission(CONSOLE_UI_RESOURCE, disableUserPermissions) && enabled;
    93  
    94    const userName = decodeURLString(params.userName || "");
    95  
    96    const changeUserPassword = () => {
    97      setChangeUserPasswordModalOpen(true);
    98    };
    99  
   100    const deleteUser = () => {
   101      setDeleteOpen(true);
   102    };
   103  
   104    const userLoggedIn = localStorage.getItem("userLoggedIn") || "";
   105    const canAssignPolicy = hasPermission(
   106      CONSOLE_UI_RESOURCE,
   107      assignIAMPolicyPermissions,
   108      true,
   109    );
   110    const canAssignGroup = hasPermission(
   111      CONSOLE_UI_RESOURCE,
   112      assignGroupPermissions,
   113      true,
   114    );
   115  
   116    const viewGroup = hasPermission(CONSOLE_UI_RESOURCE, getGroupPermissions);
   117  
   118    const getUserInformation = useCallback(() => {
   119      if (userName === "") {
   120        return null;
   121      }
   122      setLoading(true);
   123      api
   124        .invoke("GET", `/api/v1/user/${encodeURLString(userName)}`)
   125        .then((res) => {
   126          setAddLoading(false);
   127          const memberOf = res.memberOf || [];
   128          setSelectedGroups(memberOf);
   129  
   130          const currentGroups: IGroupItem[] = memberOf.map((group: string) => {
   131            return {
   132              group: group,
   133            };
   134          });
   135  
   136          setCurrentGroups(currentGroups);
   137          const currentPolicies: IPolicyItem[] = res.policy.map(
   138            (policy: string) => {
   139              return {
   140                policy: policy,
   141              };
   142            },
   143          );
   144  
   145          currentPolicies.sort(policyDetailsSort);
   146  
   147          setCurrentPolicies(currentPolicies);
   148          setEnabled(res.status === "enabled");
   149          setHasPolicy(res.hasPolicy);
   150          setLoading(false);
   151        })
   152        .catch((err: ErrorResponseHandler) => {
   153          setAddLoading(false);
   154          setLoading(false);
   155          dispatch(setModalErrorSnackMessage(err));
   156        });
   157    }, [userName, dispatch]);
   158  
   159    const saveRecord = (isEnabled: boolean) => {
   160      if (addLoading) {
   161        return;
   162      }
   163      setAddLoading(true);
   164      api
   165        .invoke("PUT", `/api/v1/user/${encodeURLString(userName)}`, {
   166          status: isEnabled ? "enabled" : "disabled",
   167          groups: selectedGroups,
   168        })
   169        .then((_) => {
   170          setAddLoading(false);
   171        })
   172        .catch((err: ErrorResponseHandler) => {
   173          setAddLoading(false);
   174          dispatch(setModalErrorSnackMessage(err));
   175        });
   176    };
   177  
   178    useEffect(() => {
   179      dispatch(setHelpName("user_details_groups"));
   180      // eslint-disable-next-line react-hooks/exhaustive-deps
   181    }, []);
   182  
   183    useEffect(() => {
   184      getUserInformation();
   185    }, [getUserInformation]);
   186  
   187    const closeDeleteModalAndRefresh = (refresh: boolean) => {
   188      setDeleteOpen(false);
   189      if (refresh) {
   190        getUserInformation();
   191      }
   192    };
   193  
   194    const groupViewAction = (group: any) => {
   195      navigate(`${IAM_PAGES.GROUPS}/${encodeURLString(group.group)}`);
   196    };
   197  
   198    const groupTableActions = [
   199      {
   200        type: "view",
   201        onClick: groupViewAction,
   202        disableButtonFunction: () => !viewGroup,
   203      },
   204    ];
   205  
   206    return (
   207      <Fragment>
   208        {addGroupOpen && (
   209          <ChangeUserGroups
   210            open={addGroupOpen}
   211            selectedUser={userName}
   212            closeModalAndRefresh={() => {
   213              setAddGroupOpen(false);
   214              getUserInformation();
   215            }}
   216          />
   217        )}
   218        {policyOpen && (
   219          <SetUserPolicies
   220            open={policyOpen}
   221            selectedUser={userName}
   222            currentPolicies={currentPolicies}
   223            closeModalAndRefresh={() => {
   224              setPolicyOpen(false);
   225              getUserInformation();
   226            }}
   227          />
   228        )}
   229        {deleteOpen && (
   230          <DeleteUser
   231            deleteOpen={deleteOpen}
   232            selectedUsers={[userName]}
   233            closeDeleteModalAndRefresh={(refresh: boolean) => {
   234              closeDeleteModalAndRefresh(refresh);
   235            }}
   236          />
   237        )}
   238        {changeUserPasswordModalOpen && (
   239          <ChangeUserPasswordModal
   240            open={changeUserPasswordModalOpen}
   241            userName={userName}
   242            closeModal={() => setChangeUserPasswordModalOpen(false)}
   243          />
   244        )}
   245        <PageHeaderWrapper
   246          label={
   247            <Fragment>
   248              <BackLink
   249                label={"Users"}
   250                onClick={() => navigate(IAM_PAGES.USERS)}
   251              />
   252            </Fragment>
   253          }
   254          actions={<HelpMenu />}
   255        />
   256        <PageLayout>
   257          <Grid container>
   258            <Grid item xs={12}>
   259              <ScreenTitle
   260                icon={<UsersIcon width={40} />}
   261                title={userName}
   262                subTitle={""}
   263                actions={
   264                  <Fragment>
   265                    <span
   266                      style={{
   267                        fontSize: ".8rem",
   268                        marginRight: ".5rem",
   269                      }}
   270                    >
   271                      User Status:
   272                    </span>
   273                    <span
   274                      style={{
   275                        fontWeight: "bold",
   276                        fontSize: ".9rem",
   277                        marginRight: ".5rem",
   278                      }}
   279                    >
   280                      {enabled ? "Enabled" : "Disabled"}
   281                    </span>
   282                    <TooltipWrapper
   283                      tooltip={
   284                        enableEnabled || disableEnabled
   285                          ? ""
   286                          : hasPermission(
   287                                CONSOLE_UI_RESOURCE,
   288                                enableUserPermissions,
   289                              )
   290                            ? permissionTooltipHelper(
   291                                disableUserPermissions,
   292                                "disable users",
   293                              )
   294                            : hasPermission(
   295                                  CONSOLE_UI_RESOURCE,
   296                                  disableUserPermissions,
   297                                )
   298                              ? permissionTooltipHelper(
   299                                  enableUserPermissions,
   300                                  "enable users",
   301                                )
   302                              : permissionTooltipHelper(
   303                                  enableDisableUserPermissions,
   304                                  "enable or disable users",
   305                                )
   306                      }
   307                    >
   308                      <Switch
   309                        indicatorLabels={["Enabled", "Disabled"]}
   310                        checked={enabled}
   311                        value={"group_enabled"}
   312                        id="group-status"
   313                        name="group-status"
   314                        onChange={() => {
   315                          setEnabled(!enabled);
   316                          saveRecord(!enabled);
   317                        }}
   318                        switchOnly
   319                        disabled={!enableEnabled && !disableEnabled}
   320                      />
   321                    </TooltipWrapper>
   322                    <TooltipWrapper
   323                      tooltip={
   324                        hasPermission(CONSOLE_UI_RESOURCE, deleteUserPermissions)
   325                          ? userLoggedIn === userName
   326                            ? "You cannot delete the currently logged in User"
   327                            : "Delete User"
   328                          : permissionTooltipHelper(
   329                              deleteUserPermissions,
   330                              "delete user",
   331                            )
   332                      }
   333                    >
   334                      <Button
   335                        id={"delete-user"}
   336                        onClick={deleteUser}
   337                        icon={<TrashIcon />}
   338                        variant={"secondary"}
   339                        disabled={
   340                          !hasPermission(
   341                            CONSOLE_UI_RESOURCE,
   342                            deleteUserPermissions,
   343                          ) || userLoggedIn === userName
   344                        }
   345                      />
   346                    </TooltipWrapper>
   347  
   348                    <TooltipWrapper tooltip={"Change Password"}>
   349                      <Button
   350                        id={"change-user-password"}
   351                        onClick={changeUserPassword}
   352                        icon={<PasswordKeyIcon />}
   353                        variant={"regular"}
   354                        disabled={userLoggedIn === userName}
   355                      />
   356                    </TooltipWrapper>
   357                  </Fragment>
   358                }
   359                sx={{ marginBottom: 15 }}
   360              />
   361            </Grid>
   362  
   363            <Grid item xs={12}>
   364              <Tabs
   365                currentTabOrPath={selectedTab}
   366                onTabClick={setSelectedTab}
   367                options={[
   368                  {
   369                    tabConfig: {
   370                      id: "groups",
   371                      label: "Groups",
   372                      disabled: !canAssignGroup,
   373                    },
   374                    content: (
   375                      <Fragment>
   376                        <Box
   377                          onMouseMove={() =>
   378                            dispatch(setHelpName("user_details_groups"))
   379                          }
   380                        >
   381                          <SectionTitle
   382                            separator
   383                            sx={{ marginBottom: 15 }}
   384                            actions={
   385                              <TooltipWrapper
   386                                tooltip={
   387                                  canAssignGroup
   388                                    ? "Assign groups"
   389                                    : permissionTooltipHelper(
   390                                        assignGroupPermissions,
   391                                        "add users to groups",
   392                                      )
   393                                }
   394                              >
   395                                <Button
   396                                  id={"add-groups"}
   397                                  label={"Add to Groups"}
   398                                  onClick={() => {
   399                                    setAddGroupOpen(true);
   400                                  }}
   401                                  icon={<AddIcon />}
   402                                  variant={"callAction"}
   403                                  disabled={!canAssignGroup}
   404                                />
   405                              </TooltipWrapper>
   406                            }
   407                          >
   408                            Groups
   409                          </SectionTitle>
   410                        </Box>
   411                        <Grid
   412                          item
   413                          xs={12}
   414                          onMouseMove={() =>
   415                            dispatch(setHelpName("user_details_groups"))
   416                          }
   417                        >
   418                          <DataTable
   419                            itemActions={groupTableActions}
   420                            columns={[{ label: "Name", elementKey: "group" }]}
   421                            isLoading={loading}
   422                            records={currentGroups}
   423                            entityName="Groups"
   424                            idField="group"
   425                          />
   426                        </Grid>
   427                      </Fragment>
   428                    ),
   429                  },
   430                  {
   431                    tabConfig: {
   432                      id: "service_accounts",
   433                      label: "Service Accounts",
   434                      disabled: !hasPermission(
   435                        CONSOLE_UI_RESOURCE,
   436                        editServiceAccountPermissions,
   437                      ),
   438                    },
   439                    content: (
   440                      <UserServiceAccountsPanel
   441                        user={userName}
   442                        hasPolicy={hasPolicy}
   443                      />
   444                    ),
   445                  },
   446                  {
   447                    tabConfig: {
   448                      id: "policies",
   449                      label: "Policies",
   450                      disabled: !canAssignPolicy,
   451                    },
   452                    content: (
   453                      <Fragment>
   454                        <Box
   455                          onMouseMove={() =>
   456                            dispatch(setHelpName("user_details_policies"))
   457                          }
   458                        >
   459                          <SectionTitle
   460                            separator
   461                            sx={{ marginBottom: 15 }}
   462                            actions={
   463                              <TooltipWrapper
   464                                tooltip={
   465                                  canAssignPolicy
   466                                    ? "Assign Policies"
   467                                    : permissionTooltipHelper(
   468                                        assignIAMPolicyPermissions,
   469                                        "assign policies",
   470                                      )
   471                                }
   472                              >
   473                                <Button
   474                                  id={"assign-policies"}
   475                                  label={"Assign Policies"}
   476                                  onClick={() => {
   477                                    setPolicyOpen(true);
   478                                  }}
   479                                  icon={<IAMPoliciesIcon />}
   480                                  variant={"callAction"}
   481                                  disabled={!canAssignPolicy}
   482                                />
   483                              </TooltipWrapper>
   484                            }
   485                          >
   486                            Policies
   487                          </SectionTitle>
   488                        </Box>
   489                        <Box>
   490                          <DataTable
   491                            itemActions={[
   492                              {
   493                                type: "view",
   494                                onClick: (policy: IPolicyItem) => {
   495                                  navigate(
   496                                    `${IAM_PAGES.POLICIES}/${encodeURLString(
   497                                      policy.policy,
   498                                    )}`,
   499                                  );
   500                                },
   501                              },
   502                            ]}
   503                            columns={[{ label: "Name", elementKey: "policy" }]}
   504                            isLoading={loading}
   505                            records={currentPolicies}
   506                            entityName="Policies"
   507                            idField="policy"
   508                          />
   509                        </Box>
   510                      </Fragment>
   511                    ),
   512                  },
   513                ]}
   514              />
   515            </Grid>
   516          </Grid>
   517        </PageLayout>
   518      </Fragment>
   519    );
   520  };
   521  
   522  export default UserDetails;