github.com/minio/console@v1.4.1/web-app/src/screens/Console/Users/ListUsers.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, useEffect, useState } from "react";
    18  import { useNavigate } from "react-router-dom";
    19  import {
    20    AddIcon,
    21    Button,
    22    DeleteIcon,
    23    GroupsIcon,
    24    HelpBox,
    25    PageLayout,
    26    UsersIcon,
    27    DataTable,
    28    Grid,
    29    ProgressBar,
    30    ActionLink,
    31  } from "mds";
    32  
    33  import { User, UsersList } from "./types";
    34  import { usersSort } from "../../../utils/sortFunctions";
    35  import { actionsTray } from "../Common/FormComponents/common/styleLibrary";
    36  import { ErrorResponseHandler } from "../../../common/types";
    37  import { encodeURLString } from "../../../common/utils";
    38  import {
    39    addUserToGroupPermissions,
    40    CONSOLE_UI_RESOURCE,
    41    deleteUserPermissions,
    42    IAM_PAGES,
    43    IAM_SCOPES,
    44    listUsersPermissions,
    45    permissionTooltipHelper,
    46    S3_ALL_RESOURCES,
    47    viewUserPermissions,
    48  } from "../../../common/SecureComponent/permissions";
    49  import api from "../../../common/api";
    50  import SearchBox from "../Common/SearchBox";
    51  import withSuspense from "../Common/Components/withSuspense";
    52  
    53  import {
    54    hasPermission,
    55    SecureComponent,
    56  } from "../../../common/SecureComponent";
    57  import { setErrorSnackMessage, setHelpName } from "../../../systemSlice";
    58  import { useAppDispatch } from "../../../store";
    59  import TooltipWrapper from "../Common/TooltipWrapper/TooltipWrapper";
    60  import PageHeaderWrapper from "../Common/PageHeaderWrapper/PageHeaderWrapper";
    61  import HelpMenu from "../HelpMenu";
    62  
    63  const DeleteUser = withSuspense(React.lazy(() => import("./DeleteUser")));
    64  const AddToGroup = withSuspense(React.lazy(() => import("./BulkAddToGroup")));
    65  
    66  const ListUsers = () => {
    67    const dispatch = useAppDispatch();
    68    const navigate = useNavigate();
    69  
    70    const [records, setRecords] = useState<User[]>([]);
    71    const [loading, setLoading] = useState<boolean>(true);
    72    const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
    73    const [addGroupOpen, setAddGroupOpen] = useState<boolean>(false);
    74    const [filter, setFilter] = useState<string>("");
    75    const [checkedUsers, setCheckedUsers] = useState<string[]>([]);
    76  
    77    const displayListUsers = hasPermission(
    78      CONSOLE_UI_RESOURCE,
    79      listUsersPermissions,
    80    );
    81  
    82    const viewUser = hasPermission(CONSOLE_UI_RESOURCE, viewUserPermissions);
    83  
    84    const addUserToGroup = hasPermission(
    85      CONSOLE_UI_RESOURCE,
    86      addUserToGroupPermissions,
    87    );
    88  
    89    const deleteUser = hasPermission(CONSOLE_UI_RESOURCE, deleteUserPermissions);
    90  
    91    const closeDeleteModalAndRefresh = (refresh: boolean) => {
    92      setDeleteOpen(false);
    93      if (refresh) {
    94        setLoading(true);
    95        setCheckedUsers([]);
    96      }
    97    };
    98  
    99    const closeAddGroupBulk = (unCheckAll: boolean = false) => {
   100      setAddGroupOpen(false);
   101      if (unCheckAll) {
   102        setCheckedUsers([]);
   103      }
   104    };
   105  
   106    useEffect(() => {
   107      if (loading) {
   108        if (displayListUsers) {
   109          api
   110            .invoke("GET", `/api/v1/users`)
   111            .then((res: UsersList) => {
   112              const users = res.users === null ? [] : res.users;
   113  
   114              setLoading(false);
   115              setRecords(users.sort(usersSort));
   116            })
   117            .catch((err: ErrorResponseHandler) => {
   118              setLoading(false);
   119              dispatch(setErrorSnackMessage(err));
   120            });
   121        } else {
   122          setLoading(false);
   123        }
   124      }
   125    }, [loading, dispatch, displayListUsers]);
   126  
   127    const filteredRecords = records.filter((elementItem) =>
   128      elementItem.accessKey.includes(filter),
   129    );
   130  
   131    const selectionChanged = (e: React.ChangeEvent<HTMLInputElement>) => {
   132      const { target: { value = "", checked = false } = {} } = e;
   133  
   134      let elements: string[] = [...checkedUsers]; // We clone the checkedUsers array
   135  
   136      if (checked) {
   137        // If the user has checked this field we need to push this to checkedUsersList
   138        elements.push(value);
   139      } else {
   140        // User has unchecked this field, we need to remove it from the list
   141        elements = elements.filter((element) => element !== value);
   142      }
   143  
   144      setCheckedUsers(elements);
   145  
   146      return elements;
   147    };
   148  
   149    const viewAction = (selectionElement: any): void => {
   150      navigate(
   151        `${IAM_PAGES.USERS}/${encodeURLString(selectionElement.accessKey)}`,
   152      );
   153    };
   154  
   155    const tableActions = [
   156      {
   157        type: "view",
   158        onClick: viewAction,
   159        disableButtonFunction: () => !viewUser,
   160      },
   161      {
   162        type: "edit",
   163        onClick: viewAction,
   164        disableButtonFunction: () => !viewUser,
   165      },
   166    ];
   167  
   168    useEffect(() => {
   169      dispatch(setHelpName("list_users"));
   170      // eslint-disable-next-line react-hooks/exhaustive-deps
   171    }, []);
   172  
   173    return (
   174      <Fragment>
   175        {deleteOpen && (
   176          <DeleteUser
   177            deleteOpen={deleteOpen}
   178            selectedUsers={checkedUsers}
   179            closeDeleteModalAndRefresh={(refresh: boolean) => {
   180              closeDeleteModalAndRefresh(refresh);
   181            }}
   182          />
   183        )}
   184        {addGroupOpen && (
   185          <AddToGroup
   186            open={addGroupOpen}
   187            checkedUsers={checkedUsers}
   188            closeModalAndRefresh={(close: boolean) => {
   189              closeAddGroupBulk(close);
   190            }}
   191          />
   192        )}
   193        <PageHeaderWrapper label={"Users"} actions={<HelpMenu />} />
   194  
   195        <PageLayout>
   196          <Grid container>
   197            <Grid item xs={12} sx={actionsTray.actionsTray}>
   198              <SearchBox
   199                placeholder={"Search Users"}
   200                onChange={setFilter}
   201                value={filter}
   202                sx={{
   203                  marginRight: "auto",
   204                  maxWidth: 380,
   205                }}
   206              />
   207              <SecureComponent
   208                resource={CONSOLE_UI_RESOURCE}
   209                scopes={[IAM_SCOPES.ADMIN_DELETE_USER]}
   210                matchAll
   211                errorProps={{ disabled: true }}
   212              >
   213                <TooltipWrapper
   214                  tooltip={
   215                    hasPermission("console", [IAM_SCOPES.ADMIN_DELETE_USER])
   216                      ? checkedUsers.length === 0
   217                        ? "Select Users to delete"
   218                        : "Delete Selected"
   219                      : permissionTooltipHelper(
   220                          [IAM_SCOPES.ADMIN_DELETE_USER],
   221                          "delete users",
   222                        )
   223                  }
   224                >
   225                  <Button
   226                    id={"delete-selected-users"}
   227                    onClick={() => {
   228                      setDeleteOpen(true);
   229                    }}
   230                    label={"Delete Selected"}
   231                    icon={<DeleteIcon />}
   232                    disabled={checkedUsers.length === 0}
   233                    variant={"secondary"}
   234                    aria-label="delete-selected-users"
   235                  />
   236                </TooltipWrapper>
   237              </SecureComponent>
   238              <SecureComponent
   239                scopes={[IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP]}
   240                resource={CONSOLE_UI_RESOURCE}
   241                errorProps={{ disabled: true }}
   242              >
   243                <TooltipWrapper
   244                  tooltip={
   245                    hasPermission("console", [IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP])
   246                      ? checkedUsers.length === 0
   247                        ? "Select Users to group"
   248                        : "Add to Group"
   249                      : permissionTooltipHelper(
   250                          [IAM_SCOPES.ADMIN_ADD_USER_TO_GROUP],
   251                          "add users to groups",
   252                        )
   253                  }
   254                >
   255                  <Button
   256                    id={"add-to-group"}
   257                    label={"Add to Group"}
   258                    icon={<GroupsIcon />}
   259                    disabled={checkedUsers.length <= 0}
   260                    onClick={() => {
   261                      if (checkedUsers.length > 0) {
   262                        setAddGroupOpen(true);
   263                      }
   264                    }}
   265                    variant={"regular"}
   266                  />
   267                </TooltipWrapper>
   268              </SecureComponent>
   269              <SecureComponent
   270                scopes={[
   271                  IAM_SCOPES.ADMIN_CREATE_USER,
   272                  IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
   273                  IAM_SCOPES.ADMIN_LIST_GROUPS,
   274                ]}
   275                resource={S3_ALL_RESOURCES}
   276                matchAll
   277                errorProps={{ disabled: true }}
   278              >
   279                <TooltipWrapper
   280                  tooltip={
   281                    hasPermission(
   282                      "console-ui",
   283                      [
   284                        IAM_SCOPES.ADMIN_CREATE_USER,
   285                        IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
   286                        IAM_SCOPES.ADMIN_LIST_GROUPS,
   287                        IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY,
   288                      ],
   289                      true,
   290                    )
   291                      ? "Create User"
   292                      : permissionTooltipHelper(
   293                          [
   294                            IAM_SCOPES.ADMIN_CREATE_USER,
   295                            IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
   296                            IAM_SCOPES.ADMIN_LIST_GROUPS,
   297                            IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY,
   298                          ],
   299                          "create users",
   300                        )
   301                  }
   302                >
   303                  <Button
   304                    id={"create-user"}
   305                    label={"Create User"}
   306                    icon={<AddIcon />}
   307                    onClick={() => {
   308                      navigate(`${IAM_PAGES.USER_ADD}`);
   309                    }}
   310                    variant={"callAction"}
   311                    disabled={
   312                      !hasPermission(
   313                        "console-ui",
   314                        [
   315                          IAM_SCOPES.ADMIN_CREATE_USER,
   316                          IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
   317                          IAM_SCOPES.ADMIN_LIST_GROUPS,
   318                          IAM_SCOPES.ADMIN_ATTACH_USER_OR_GROUP_POLICY,
   319                        ],
   320                        true,
   321                      )
   322                    }
   323                  />
   324                </TooltipWrapper>
   325              </SecureComponent>
   326            </Grid>
   327  
   328            {loading && <ProgressBar />}
   329            {!loading && (
   330              <Fragment>
   331                {records.length > 0 && (
   332                  <Fragment>
   333                    <Grid item xs={12} sx={{ marginBottom: 15 }}>
   334                      <SecureComponent
   335                        scopes={[IAM_SCOPES.ADMIN_LIST_USERS]}
   336                        resource={CONSOLE_UI_RESOURCE}
   337                        errorProps={{ disabled: true }}
   338                      >
   339                        <DataTable
   340                          itemActions={tableActions}
   341                          columns={[
   342                            { label: "Access Key", elementKey: "accessKey" },
   343                          ]}
   344                          onSelect={
   345                            addUserToGroup || deleteUser
   346                              ? selectionChanged
   347                              : undefined
   348                          }
   349                          selectedItems={checkedUsers}
   350                          isLoading={loading}
   351                          records={filteredRecords}
   352                          entityName="Users"
   353                          idField="accessKey"
   354                        />
   355                      </SecureComponent>
   356                    </Grid>
   357                    <HelpBox
   358                      title={"Users"}
   359                      iconComponent={<UsersIcon />}
   360                      help={
   361                        <Fragment>
   362                          A MinIO user consists of a unique access key (username)
   363                          and corresponding secret key (password). Clients must
   364                          authenticate their identity by specifying both a valid
   365                          access key (username) and the corresponding secret key
   366                          (password) of an existing MinIO user.
   367                          <br />
   368                          Groups provide a simplified method for managing shared
   369                          permissions among users with common access patterns and
   370                          workloads.
   371                          <br />
   372                          <br />
   373                          Users inherit access permissions to data and resources
   374                          through the groups they belong to.
   375                          <br />
   376                          MinIO uses Policy-Based Access Control (PBAC) to define
   377                          the authorized actions and resources to which an
   378                          authenticated user has access. Each policy describes one
   379                          or more actions and conditions that outline the
   380                          permissions of a user or group of users.
   381                          <br />
   382                          <br />
   383                          Each user can access only those resources and operations
   384                          which are explicitly granted by the built-in role. MinIO
   385                          denies access to any other resource or action by
   386                          default.
   387                          <br />
   388                          <br />
   389                          You can learn more at our{" "}
   390                          <a
   391                            href="https://min.io/docs/minio/kubernetes/upstream/administration/identity-access-management/minio-user-management.html?ref=con"
   392                            target="_blank"
   393                            rel="noopener"
   394                          >
   395                            documentation
   396                          </a>
   397                          .
   398                        </Fragment>
   399                      }
   400                    />
   401                  </Fragment>
   402                )}
   403                {records.length === 0 && (
   404                  <Grid container>
   405                    <Grid item xs={8}>
   406                      <HelpBox
   407                        title={"Users"}
   408                        iconComponent={<UsersIcon />}
   409                        help={
   410                          <Fragment>
   411                            A MinIO user consists of a unique access key
   412                            (username) and corresponding secret key (password).
   413                            Clients must authenticate their identity by specifying
   414                            both a valid access key (username) and the
   415                            corresponding secret key (password) of an existing
   416                            MinIO user.
   417                            <br />
   418                            Groups provide a simplified method for managing shared
   419                            permissions among users with common access patterns
   420                            and workloads.
   421                            <br />
   422                            <br />
   423                            Users inherit access permissions to data and resources
   424                            through the groups they belong to.
   425                            <br />
   426                            MinIO uses Policy-Based Access Control (PBAC) to
   427                            define the authorized actions and resources to which
   428                            an authenticated user has access. Each policy
   429                            describes one or more actions and conditions that
   430                            outline the permissions of a user or group of users.
   431                            <br />
   432                            <br />
   433                            Each user can access only those resources and
   434                            operations which are explicitly granted by the
   435                            built-in role. MinIO denies access to any other
   436                            resource or action by default.
   437                            <SecureComponent
   438                              scopes={[
   439                                IAM_SCOPES.ADMIN_CREATE_USER,
   440                                IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
   441                                IAM_SCOPES.ADMIN_LIST_GROUPS,
   442                              ]}
   443                              matchAll
   444                              resource={CONSOLE_UI_RESOURCE}
   445                            >
   446                              <br />
   447                              <br />
   448                              To get started,{" "}
   449                              <ActionLink
   450                                onClick={() => {
   451                                  navigate(`${IAM_PAGES.USER_ADD}`);
   452                                }}
   453                              >
   454                                Create a User
   455                              </ActionLink>
   456                              .
   457                            </SecureComponent>
   458                          </Fragment>
   459                        }
   460                      />
   461                    </Grid>
   462                  </Grid>
   463                )}
   464              </Fragment>
   465            )}
   466          </Grid>
   467        </PageLayout>
   468      </Fragment>
   469    );
   470  };
   471  
   472  export default ListUsers;