From 75b30821726da2427926be061029ad911c163da0 Mon Sep 17 00:00:00 2001 From: adfost Date: Tue, 8 Feb 2022 22:52:55 -0800 Subject: [PATCH] Service account policy UI (#1519) * saving * service account policy UI * fixing warning * Update portal-ui/src/screens/Console/Account/ServiceAccountPolicy.tsx Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> * fixing comment Co-authored-by: Daniel Valdivia <18384552+dvaldivia@users.noreply.github.com> Co-authored-by: Lenin Alevski --- .../src/screens/Console/Account/Account.tsx | 21 ++- .../Console/Account/ServiceAccountPolicy.tsx | 131 ++++++++++++++++++ restapi/user_service_accounts.go | 6 + 3 files changed, 157 insertions(+), 1 deletion(-) create mode 100644 portal-ui/src/screens/Console/Account/ServiceAccountPolicy.tsx diff --git a/portal-ui/src/screens/Console/Account/Account.tsx b/portal-ui/src/screens/Console/Account/Account.tsx index 375797785..aeaa83396 100644 --- a/portal-ui/src/screens/Console/Account/Account.tsx +++ b/portal-ui/src/screens/Console/Account/Account.tsx @@ -51,8 +51,9 @@ import { } from "../../../common/SecureComponent/permissions"; import SecureComponent from "../../../common/SecureComponent/SecureComponent"; import RBIconButton from "../Buckets/BucketDetails/SummaryItems/RBIconButton"; -import { selectSAs } from "../../Console/Configurations/utils"; +import { selectSAs } from "../Configurations/utils"; import DeleteMultipleServiceAccounts from "../Users/DeleteMultipleServiceAccounts"; +import ServiceAccountPolicy from "./ServiceAccountPolicy"; const AddServiceAccount = withSuspense( React.lazy(() => import("./AddServiceAccount")) @@ -98,6 +99,7 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { useState(false); const [selectedSAs, setSelectedSAs] = useState([]); const [deleteMultipleOpen, setDeleteMultipleOpen] = useState(false); + const [policyOpen, setPolicyOpen] = useState(false); useEffect(() => { fetchRecords(); @@ -157,6 +159,11 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { } }; + const policyModalOpen = (selectedServiceAccount: string) => { + setSelectedServiceAccount(selectedServiceAccount); + setPolicyOpen(true); + }; + const selectAllItems = () => { if (selectedSAs.length === records.length) { setSelectedSAs([]); @@ -170,12 +177,17 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { setNewServiceAccount(null); }; + const closePolicyModal = () => { + setPolicyOpen(false); + }; + const confirmDeleteServiceAccount = (selectedServiceAccount: string) => { setSelectedServiceAccount(selectedServiceAccount); setDeleteOpen(true); }; const tableActions = [ + { type: "view", onClick: policyModalOpen }, { type: "delete", onClick: confirmDeleteServiceAccount }, ]; @@ -219,6 +231,13 @@ const Account = ({ classes, displayErrorMessage }: IServiceAccountsProps) => { entity="Service Account" /> )} + {policyOpen && ( + + )} setChangePasswordModalOpen(false)} diff --git a/portal-ui/src/screens/Console/Account/ServiceAccountPolicy.tsx b/portal-ui/src/screens/Console/Account/ServiceAccountPolicy.tsx new file mode 100644 index 000000000..0b3a9c71a --- /dev/null +++ b/portal-ui/src/screens/Console/Account/ServiceAccountPolicy.tsx @@ -0,0 +1,131 @@ +// This file is part of MinIO Console Server +// Copyright (c) 2022 MinIO, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero 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 Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +import React, { useEffect, useState } from "react"; +import { connect } from "react-redux"; +import { Button } from "@mui/material"; +import { Theme } from "@mui/material/styles"; +import createStyles from "@mui/styles/createStyles"; +import withStyles from "@mui/styles/withStyles"; +import Grid from "@mui/material/Grid"; +import { + formFieldStyles, + modalStyleUtils, + spacingUtils, +} from "../Common/FormComponents/common/styleLibrary"; +import { setModalErrorSnackMessage } from "../../../actions"; +import { ErrorResponseHandler } from "../../../common/types"; +import api from "../../../common/api"; +import ModalWrapper from "../Common/ModalWrapper/ModalWrapper"; +import { ChangeAccessPolicyIcon } from "../../../icons"; +import CodeMirrorWrapper from "../Common/FormComponents/CodeMirrorWrapper/CodeMirrorWrapper"; + +const styles = (theme: Theme) => + createStyles({ + codeMirrorContainer: { + marginBottom: 20, + "& label": { + marginBottom: ".5rem", + }, + "& label + div": { + display: "none", + }, + }, + ...formFieldStyles, + ...modalStyleUtils, + ...spacingUtils, + }); +createStyles({ + ...modalStyleUtils, + ...spacingUtils, +}); + +interface IServiceAccountPolicyProps { + classes: any; + open: boolean; + selectedAccessKey: string | null; + closeModalAndRefresh: () => void; + setModalErrorSnackMessage: typeof setModalErrorSnackMessage; +} + +const ServiceAccountPolicy = ({ + classes, + open, + selectedAccessKey, + closeModalAndRefresh, + setModalErrorSnackMessage, +}: IServiceAccountPolicyProps) => { + const [loading, setLoading] = useState(true); + const [policyDefinition, setPolicyDefinition] = useState(""); + useEffect(() => { + if (loading) { + api + .invoke("GET", `/api/v1/service-accounts/${selectedAccessKey}/policy`) + .then((res) => { + setLoading(false); + setPolicyDefinition(res); + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + setModalErrorSnackMessage(err); + }); + } + }, [loading, setLoading, setModalErrorSnackMessage, selectedAccessKey]); + + return ( + { + closeModalAndRefresh(); + }} + titleIcon={} + > + + + { + setPolicyDefinition(value); + }} + editorHeight={"350px"} + readOnly={true} + /> + + + + + + + ); +}; + +const connector = connect(null, { + setModalErrorSnackMessage, +}); + +export default withStyles(styles)(connector(ServiceAccountPolicy)); diff --git a/restapi/user_service_accounts.go b/restapi/user_service_accounts.go index c518dda6f..98d4a4d10 100644 --- a/restapi/user_service_accounts.go +++ b/restapi/user_service_accounts.go @@ -19,6 +19,7 @@ package restapi import ( "bytes" "context" + "encoding/json" "errors" "fmt" "strings" @@ -366,6 +367,11 @@ func getServiceAccountPolicy(ctx context.Context, userClient MinioAdmin, accessK if err != nil { return "", err } + var policy iampolicy.Policy + json.Unmarshal([]byte(serviceAccountInfo.Policy), &policy) + if policy.Statements == nil { + return "", nil + } return serviceAccountInfo.Policy, nil }