From 0ea21938782a89585c4190382adab17328cccda3 Mon Sep 17 00:00:00 2001 From: Lenin Alevski Date: Tue, 23 Nov 2021 20:09:36 -0800 Subject: [PATCH] Adding SecureComponent to policy details page (#1251) - Make Users & Policies clickeable only if user has permissions to view in bucket page - Add SecureComponent to policy detail page: api calls, menu options, raw policy editor, etc. - Add missing click action to groups in policy detail page - Fix NPE in list groups for policy endpoint - Added SecureComponent to ListPolicies page Signed-off-by: Lenin Alevski --- .../src/common/SecureComponent/permissions.ts | 4 + .../BucketDetails/AccessDetailsPanel.tsx | 16 +- .../screens/Console/Policies/ListPolicies.tsx | 125 ++++++--- .../Console/Policies/PolicyDetails.tsx | 245 ++++++++++++------ restapi/admin_policies.go | 2 +- 5 files changed, 268 insertions(+), 124 deletions(-) diff --git a/portal-ui/src/common/SecureComponent/permissions.ts b/portal-ui/src/common/SecureComponent/permissions.ts index c502d4d89..d6d7f9ff7 100644 --- a/portal-ui/src/common/SecureComponent/permissions.ts +++ b/portal-ui/src/common/SecureComponent/permissions.ts @@ -117,6 +117,10 @@ export const IAM_SCOPES = { ADMIN_GET_GROUP: "admin:GetGroup", ADMIN_ENABLE_GROUP: "admin:EnableGroup", ADMIN_DISABLE_GROUP: "admin:DisableGroup", + ADMIN_GET_USER: "admin:GetUser", + ADMIN_CREATE_POLICY: "admin:CreatePolicy", + ADMIN_DELETE_POLICY: "admin:DeletePolicy", + ADMIN_ATTACH_USER_OR_GROUP_POLICY: "admin:AttachUserOrGroupPolicy", S3_ALL_ACTIONS: "s3:*", ADMIN_ALL_ACTIONS: "admin:*", }; diff --git a/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx b/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx index a5d536f1e..9d0d2da6e 100644 --- a/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx +++ b/portal-ui/src/screens/Console/Buckets/BucketDetails/AccessDetailsPanel.tsx @@ -30,7 +30,10 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper"; import api from "../../../../common/api"; import history from "../../../../history"; import { BucketInfo } from "../types"; -import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions"; +import { + CONSOLE_UI_RESOURCE, + IAM_SCOPES, +} from "../../../../common/SecureComponent/permissions"; import PanelTitle from "../../Common/PanelTitle/PanelTitle"; import SecureComponent, { hasPermission, @@ -90,6 +93,15 @@ const AccessDetails = ({ true ); + const viewUser = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_USER, + ]); + const viewPolicy = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_POLICY, + IAM_SCOPES.ADMIN_LIST_USERS, + IAM_SCOPES.ADMIN_LIST_GROUPS, + ]); + useEffect(() => { if (loadingBucket) { setLoadingUsers(true); @@ -100,6 +112,7 @@ const AccessDetails = ({ const PolicyActions = [ { type: "view", + disableButtonFunction: () => !viewPolicy, onClick: (policy: any) => { history.push(`/policies/${policy.name}`); }, @@ -109,6 +122,7 @@ const AccessDetails = ({ const userTableActions = [ { type: "view", + disableButtonFunction: () => !viewUser, onClick: (user: any) => { history.push(`/users/${user}`); }, diff --git a/portal-ui/src/screens/Console/Policies/ListPolicies.tsx b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx index 39db456d5..2805b8127 100644 --- a/portal-ui/src/screens/Console/Policies/ListPolicies.tsx +++ b/portal-ui/src/screens/Console/Policies/ListPolicies.tsx @@ -42,6 +42,13 @@ import history from "../../../history"; import SearchIcon from "../../../icons/SearchIcon"; import HelpBox from "../../../common/HelpBox"; import PageLayout from "../Common/Layout/PageLayout"; +import { + CONSOLE_UI_RESOURCE, + IAM_SCOPES, +} from "../../../common/SecureComponent/permissions"; +import SecureComponent, { + hasPermission, +} from "../../../common/SecureComponent/SecureComponent"; const styles = (theme: Theme) => createStyles({ @@ -91,38 +98,54 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { const [filterPolicies, setFilterPolicies] = useState(""); const [policyEdit, setPolicyEdit] = useState(null); + const viewPolicy = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_POLICY, + ]); + + const deletePolicy = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_DELETE_POLICY, + ]); + + const displayPolicies = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_LIST_USER_POLICIES, + ]); + useEffect(() => { fetchRecords(); }, []); useEffect(() => { if (loading) { - api - .invoke("GET", `/api/v1/policies`) - .then((res: PolicyList) => { - const policies = get(res, "policies", []); + if (displayPolicies) { + api + .invoke("GET", `/api/v1/policies`) + .then((res: PolicyList) => { + const policies = get(res, "policies", []); - policies.sort((pa, pb) => { - if (pa.name > pb.name) { - return 1; - } + policies.sort((pa, pb) => { + if (pa.name > pb.name) { + return 1; + } - if (pa.name < pb.name) { - return -1; - } + if (pa.name < pb.name) { + return -1; + } - return 0; + return 0; + }); + + setLoading(false); + setRecords(policies); + }) + .catch((err: ErrorResponseHandler) => { + setLoading(false); + setErrorSnackMessage(err); }); - - setLoading(false); - setRecords(policies); - }) - .catch((err: ErrorResponseHandler) => { - setLoading(false); - setErrorSnackMessage(err); - }); + } else { + setLoading(false); + } } - }, [loading, setLoading, setRecords, setErrorSnackMessage]); + }, [loading, setLoading, setRecords, setErrorSnackMessage, displayPolicies]); const fetchRecords = () => { setLoading(true); @@ -154,8 +177,17 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { }; const tableActions = [ - { type: "view", onClick: viewAction }, - { type: "delete", onClick: confirmDeletePolicy, sendOnlyId: true }, + { + type: "view", + onClick: viewAction, + disableButtonFunction: () => !viewPolicy, + }, + { + type: "delete", + onClick: confirmDeletePolicy, + sendOnlyId: true, + disableButtonFunction: () => !deletePolicy, + }, ]; const filteredRecords = records.filter((elementItem) => @@ -199,30 +231,41 @@ const ListPolicies = ({ classes, setErrorSnackMessage }: IPoliciesProps) => { }} variant="standard" /> - + +
- + + + (true); const [deleteOpen, setDeleteOpen] = useState(false); + const displayGroups = hasPermission( + CONSOLE_UI_RESOURCE, + [IAM_SCOPES.ADMIN_LIST_GROUPS, IAM_SCOPES.ADMIN_GET_GROUP], + true + ); + + const viewGroup = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_GROUP, + ]); + + const displayUsers = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_LIST_GROUPS, + ]); + + const viewUser = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_USER, + ]); + + const displayPolicy = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_GET_POLICY, + ]); + + const editPolicy = hasPermission(CONSOLE_UI_RESOURCE, [ + IAM_SCOPES.ADMIN_CREATE_POLICY, + ]); + const saveRecord = (event: React.FormEvent) => { event.preventDefault(); if (addLoading) { return; } setAddLoading(true); - api - .invoke("POST", "/api/v1/policies", { - name: policyName, - policy: policyDefinition, - }) - .then((_) => { - setAddLoading(false); - setSnackBarMessage("Policy successfully updated"); - }) - .catch((err: ErrorResponseHandler) => { - setAddLoading(false); - setErrorSnackMessage(err); - }); + if (editPolicy) { + api + .invoke("POST", "/api/v1/policies", { + name: policyName, + policy: policyDefinition, + }) + .then((_) => { + setAddLoading(false); + setSnackBarMessage("Policy successfully updated"); + }) + .catch((err: ErrorResponseHandler) => { + setAddLoading(false); + setErrorSnackMessage(err); + }); + } else { + setAddLoading(false); + } }; useEffect(() => { const loadUsersForPolicy = () => { if (loadingUsers) { - api - .invoke( - "GET", - `/api/v1/policies/${encodeURIComponent(policyName)}/users` - ) - .then((result: any) => { - setUserList(result); - setLoadingUsers(false); - }) - .catch((err: ErrorResponseHandler) => { - setErrorSnackMessage(err); - setLoadingUsers(false); - }); + if (displayUsers) { + api + .invoke( + "GET", + `/api/v1/policies/${encodeURIComponent(policyName)}/users` + ) + .then((result: any) => { + setUserList(result); + setLoadingUsers(false); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + setLoadingUsers(false); + }); + } else { + setLoadingUsers(false); + } } }; + const loadGroupsForPolicy = () => { if (loadingGroups) { - api - .invoke( - "GET", - `/api/v1/policies/${encodeURIComponent(policyName)}/groups` - ) - .then((result: any) => { - setGroupList(result); - setLoadingGroups(false); - }) - .catch((err: ErrorResponseHandler) => { - setErrorSnackMessage(err); - setLoadingGroups(false); - }); + if (displayGroups) { + api + .invoke( + "GET", + `/api/v1/policies/${encodeURIComponent(policyName)}/groups` + ) + .then((result: any) => { + setGroupList(result); + setLoadingGroups(false); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + setLoadingGroups(false); + }); + } else { + setLoadingGroups(false); + } } }; const loadPolicyDetails = () => { if (loadingPolicy) { - api - .invoke( - "GET", - `/api/v1/policy?name=${encodeURIComponent(policyName)}` - ) - .then((result: any) => { - if (result) { - setPolicy(result); - setPolicyDefinition( - result ? JSON.stringify(JSON.parse(result.policy), null, 4) : "" - ); - const pol: IAMPolicy = JSON.parse(result.policy); - setPolicyStatements(pol.Statement); - } - setLoadingPolicy(false); - }) - .catch((err: ErrorResponseHandler) => { - setErrorSnackMessage(err); - setLoadingPolicy(false); - }); + if (displayPolicy) { + api + .invoke( + "GET", + `/api/v1/policy?name=${encodeURIComponent(policyName)}` + ) + .then((result: any) => { + if (result) { + setPolicy(result); + setPolicyDefinition( + result + ? JSON.stringify(JSON.parse(result.policy), null, 4) + : "" + ); + const pol: IAMPolicy = JSON.parse(result.policy); + setPolicyStatements(pol.Statement); + } + setLoadingPolicy(false); + }) + .catch((err: ErrorResponseHandler) => { + setErrorSnackMessage(err); + setLoadingPolicy(false); + }); + } else { + setLoadingPolicy(false); + } } }; @@ -307,6 +359,9 @@ const PolicyDetails = ({ setPolicy, setLoadingUsers, setLoadingGroups, + displayUsers, + displayGroups, + displayPolicy, ]); const resetForm = () => { @@ -328,12 +383,30 @@ const PolicyDetails = ({ const userViewAction = (user: any) => { history.push(`/users/${user}`); }; - const userTableActions = [{ type: "view", onClick: userViewAction }]; + const userTableActions = [ + { + type: "view", + onClick: userViewAction, + disableButtonFunction: () => !viewUser, + }, + ]; const filteredUsers = userList.filter((elementItem) => elementItem.includes(filterUsers) ); + const groupViewAction = (group: any) => { + history.push(`/groups/${group}`); + }; + + const groupTableActions = [ + { + type: "view", + onClick: groupViewAction, + disableButtonFunction: () => !viewGroup, + }, + ]; + const filteredGroups = groupList.filter((elementItem) => elementItem.includes(filterGroups) ); @@ -373,14 +446,20 @@ const PolicyDetails = ({ subTitle={IAM Policy} actions={ - - - + + + + + {{ - tabConfig: { label: "Summary" }, + tabConfig: { label: "Summary", disabled: !displayPolicy }, content: (
Policy Summary
@@ -479,7 +558,7 @@ const PolicyDetails = ({ ), }} {{ - tabConfig: { label: "Users" }, + tabConfig: { label: "Users", disabled: !displayUsers }, content: (
Users
@@ -520,7 +599,7 @@ const PolicyDetails = ({ ), }} {{ - tabConfig: { label: "Groups" }, + tabConfig: { label: "Groups", disabled: !displayGroups }, content: (
Groups
@@ -549,20 +628,19 @@ const PolicyDetails = ({
), }} {{ - tabConfig: { label: "Raw Policy" }, + tabConfig: { label: "Raw Policy", disabled: !displayPolicy }, content: (
Raw Policy
@@ -577,6 +655,7 @@ const PolicyDetails = ({ { setPolicyDefinition(value); @@ -596,15 +675,19 @@ const PolicyDetails = ({ Clear )} - - + + {addLoading && ( diff --git a/restapi/admin_policies.go b/restapi/admin_policies.go index 88c6d0d05..58c1bad6e 100644 --- a/restapi/admin_policies.go +++ b/restapi/admin_policies.go @@ -308,7 +308,7 @@ func getListGroupsForPolicyResponse(session *models.Principal, policy string) ([ for _, group := range groups { info, err := groupInfo(ctx, adminClient, group) if err != nil { - LogError("unable to fetch group info %s: %v", group, err) + return nil, prepareError(err) } if info.Policy == policy { filteredGroups = append(filteredGroups, group)