Re-implement policy handling in react (#1234)

Signed-off-by: Lenin Alevski <alevsk.8772@gmail.com>
This commit is contained in:
Lenin Alevski
2021-11-18 08:25:01 -08:00
committed by GitHub
parent f5234d2830
commit aae493ac82
30 changed files with 1098 additions and 1132 deletions

View File

@@ -40,18 +40,12 @@ type Bucket struct {
// access
Access *BucketAccess `json:"access,omitempty"`
// allowed actions
AllowedActions []string `json:"allowedActions"`
// creation date
CreationDate string `json:"creation_date,omitempty"`
// details
Details *BucketDetails `json:"details,omitempty"`
// manage
Manage bool `json:"manage,omitempty"`
// name
// Required: true
// Min Length: 3

View File

@@ -49,8 +49,8 @@ type SessionResponse struct {
// pages
Pages []string `json:"pages"`
// policy
Policy *IamPolicy `json:"policy,omitempty"`
// permissions
Permissions map[string][]string `json:"permissions,omitempty"`
// status
// Enum: [ok]
@@ -61,10 +61,6 @@ type SessionResponse struct {
func (m *SessionResponse) Validate(formats strfmt.Registry) error {
var res []error
if err := m.validatePolicy(formats); err != nil {
res = append(res, err)
}
if err := m.validateStatus(formats); err != nil {
res = append(res, err)
}
@@ -75,23 +71,6 @@ func (m *SessionResponse) Validate(formats strfmt.Registry) error {
return nil
}
func (m *SessionResponse) validatePolicy(formats strfmt.Registry) error {
if swag.IsZero(m.Policy) { // not required
return nil
}
if m.Policy != nil {
if err := m.Policy.Validate(formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("policy")
}
return err
}
}
return nil
}
var sessionResponseTypeStatusPropEnum []interface{}
func init() {
@@ -131,31 +110,8 @@ func (m *SessionResponse) validateStatus(formats strfmt.Registry) error {
return nil
}
// ContextValidate validate this session response based on the context it is used
// ContextValidate validates this session response based on context it is used
func (m *SessionResponse) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
var res []error
if err := m.contextValidatePolicy(ctx, formats); err != nil {
res = append(res, err)
}
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
return nil
}
func (m *SessionResponse) contextValidatePolicy(ctx context.Context, formats strfmt.Registry) error {
if m.Policy != nil {
if err := m.Policy.ContextValidate(ctx, formats); err != nil {
if ve, ok := err.(*errors.Validation); ok {
return ve.ValidateName("policy")
}
return err
}
}
return nil
}

View File

@@ -1,82 +0,0 @@
// This file is part of MinIO Orchestrator
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
package acl
import iampolicy "github.com/minio/pkg/iam/policy"
var BucketViewerRole = iampolicy.NewActionSet(
iampolicy.ListBucketAction,
iampolicy.GetObjectAction,
)
var BucketEditorRole = iampolicy.NewActionSet(
iampolicy.ListBucketAction,
iampolicy.GetObjectAction,
iampolicy.DeleteObjectAction,
iampolicy.PutObjectAction,
)
var BucketAdminRole = iampolicy.NewActionSet(
iampolicy.AbortMultipartUploadAction,
iampolicy.CreateBucketAction,
iampolicy.DeleteBucketAction,
iampolicy.ForceDeleteBucketAction,
iampolicy.DeleteBucketPolicyAction,
iampolicy.GetBucketLocationAction,
iampolicy.GetBucketNotificationAction,
iampolicy.GetBucketPolicyAction,
iampolicy.HeadBucketAction,
iampolicy.ListAllMyBucketsAction,
iampolicy.GetBucketPolicyStatusAction,
iampolicy.ListBucketVersionsAction,
iampolicy.ListBucketMultipartUploadsAction,
iampolicy.ListenNotificationAction,
iampolicy.ListenBucketNotificationAction,
iampolicy.ListMultipartUploadPartsAction,
iampolicy.PutBucketLifecycleAction,
iampolicy.GetBucketLifecycleAction,
iampolicy.PutBucketNotificationAction,
iampolicy.PutBucketPolicyAction,
iampolicy.BypassGovernanceRetentionAction,
iampolicy.PutObjectRetentionAction,
iampolicy.GetObjectRetentionAction,
iampolicy.GetObjectLegalHoldAction,
iampolicy.PutObjectLegalHoldAction,
iampolicy.GetBucketObjectLockConfigurationAction,
iampolicy.PutBucketObjectLockConfigurationAction,
iampolicy.GetBucketTaggingAction,
iampolicy.PutBucketTaggingAction,
iampolicy.GetObjectVersionAction,
iampolicy.GetObjectVersionTaggingAction,
iampolicy.DeleteObjectVersionAction,
iampolicy.DeleteObjectVersionTaggingAction,
iampolicy.PutObjectVersionTaggingAction,
iampolicy.GetObjectTaggingAction,
iampolicy.PutObjectTaggingAction,
iampolicy.DeleteObjectTaggingAction,
iampolicy.PutBucketEncryptionAction,
iampolicy.GetBucketEncryptionAction,
iampolicy.PutBucketVersioningAction,
iampolicy.GetBucketVersioningAction,
iampolicy.GetReplicationConfigurationAction,
iampolicy.PutReplicationConfigurationAction,
iampolicy.ReplicateObjectAction,
iampolicy.ReplicateDeleteAction,
iampolicy.ReplicateTagsAction,
iampolicy.GetObjectVersionForReplicationAction,
iampolicy.AllActions,
iampolicy.AllAdminActions,
)

View File

@@ -0,0 +1,69 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
import React, { cloneElement } from "react";
import { store } from "../../store";
import { hasAccessToResource } from "./permissions";
export const hasPermission = (
resource: string | undefined,
scopes: string[],
matchAll?: boolean
) => {
if (!resource) {
return false;
}
const state = store.getState();
const sessionGrants = state.console.session.permissions;
const resourceGrants =
sessionGrants[resource] ||
sessionGrants[`arn:aws:s3:::${resource}/*`] ||
[];
const globalGrants = sessionGrants["arn:aws:s3:::*"] || [];
return hasAccessToResource(
[...resourceGrants, ...globalGrants],
scopes,
matchAll
);
};
interface ISecureComponentProps {
errorProps?: any;
RenderError?: any;
matchAll?: boolean;
children: any;
scopes: string[];
resource: string;
}
const SecureComponent = ({
children,
RenderError = () => <></>,
errorProps = null,
matchAll = false,
scopes = [],
resource,
}: ISecureComponentProps) => {
const permissionGranted = hasPermission(resource, scopes, matchAll);
if (!permissionGranted && !errorProps) return <RenderError />;
if (!permissionGranted && errorProps) {
return cloneElement(children, { ...errorProps });
}
return <>{children}</>;
};
export default SecureComponent;

View File

@@ -0,0 +1,177 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
// hasAccessToResource receives a list of user permissions to perform on a specific resource, then compares those permissions against
// a list of required permissions and return true or false depending of the level of required access (match all permissions,
// match some of the permissions)
export const hasAccessToResource = (
userPermissionsOnBucket: string[] | null | undefined,
requiredPermissions: string[],
matchAll?: boolean
) => {
if (!userPermissionsOnBucket) {
return false;
}
const s3All = userPermissionsOnBucket.includes(IAM_SCOPES.S3_ALL_ACTIONS);
const AdminAll = userPermissionsOnBucket.includes(
IAM_SCOPES.ADMIN_ALL_ACTIONS
);
const permissions = requiredPermissions.filter(function (n) {
return (
userPermissionsOnBucket.indexOf(n) !== -1 ||
(n.indexOf("s3:") !== -1 && s3All) ||
(n.indexOf("admin:") !== -1 && AdminAll)
);
});
return matchAll
? permissions.length === requiredPermissions.length
: permissions.length > 0;
};
export const IAM_ROLES = {
viewer: "VIEWER",
editor: "EDITOR",
owner: "OWNER",
admin: "ADMIN",
};
export const IAM_SCOPES = {
S3_LIST_BUCKET: "s3:ListBucket",
S3_GET_BUCKET_POLICY: "s3:GetBucketPolicy",
S3_PUT_BUCKET_POLICY: "s3:PutBucketPolicy",
S3_GET_OBJECT: "s3:GetObject",
S3_PUT_OBJECT: "s3:PutObject",
S3_GET_OBJECT_LEGAL_HOLD: "s3:GetObjectLegalHold",
S3_PUT_OBJECT_LEGAL_HOLD: "s3:PutObjectLegalHold",
S3_DELETE_OBJECT: "s3:DeleteObject",
S3_GET_BUCKET_VERSIONING: "s3:GetBucketVersioning",
S3_PUT_BUCKET_VERSIONING: "s3:PutBucketVersioning",
S3_GET_OBJECT_RETENTION: "s3:GetObjectRetention",
S3_PUT_OBJECT_RETENTION: "s3:PutObjectRetention",
S3_GET_OBJECT_TAGGING: "s3:GetObjectTagging",
S3_PUT_OBJECT_TAGGING: "s3:PutObjectTagging",
S3_DELETE_OBJECT_TAGGING: "s3:DeleteObjectTagging",
S3_GET_BUCKET_ENCRYPTION_CONFIGURATION: "s3:GetEncryptionConfiguration",
S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION: "s3:PutEncryptionConfiguration",
S3_CREATE_BUCKET: "s3:CreateBucket",
S3_DELETE_BUCKET: "s3:DeleteBucket",
S3_FORCE_DELETE_BUCKET: "s3:ForceDeleteBucket",
S3_GET_BUCKET_NOTIFICATIONS: "s3:GetBucketNotification",
S3_LISTEN_BUCKET_NOTIFICATIONS: "s3:ListenBucketNotification",
S3_PUT_BUCKET_NOTIFICATIONS: "s3:PutBucketNotification",
S3_GET_REPLICATION_CONFIGURATION: "s3:GetReplicationConfiguration",
S3_PUT_REPLICATION_CONFIGURATION: "s3:PutReplicationConfiguration",
S3_GET_LIFECYCLE_CONFIGURATION: "s3:GetLifecycleConfiguration",
S3_PUT_LIFECYCLE_CONFIGURATION: "s3:PutLifecycleConfiguration",
S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION:
"s3:GetBucketObjectLockConfiguration",
S3_PUT_BUCKET_OBJECT_LOCK_CONFIGURATION:
"s3:PutBucketObjectLockConfiguration",
ADMIN_GET_POLICY: "admin:GetPolicy",
ADMIN_LIST_USERS: "admin:ListUsers",
ADMIN_LIST_USER_POLICIES: "admin:ListUserPolicies",
ADMIN_SERVER_INFO: "admin:ServerInfo",
ADMIN_GET_BUCKET_QUOTA: "admin:GetBucketQuota",
ADMIN_SET_BUCKET_QUOTA: "admin:SetBucketQuota",
ADMIN_LIST_TIERS: "admin:ListTier",
ADMIN_LIST_GROUPS: "admin:ListGroups",
S3_GET_OBJECT_VERSION_FOR_REPLICATION: "s3:GetObjectVersionForReplication",
S3_REPLICATE_TAGS: "s3:ReplicateTags",
S3_REPLICATE_DELETE: "s3:ReplicateDelete",
S3_REPLICATE_OBJECT: "s3:ReplicateObject",
S3_PUT_OBJECT_VERSION_TAGGING: "s3:PutObjectVersionTagging",
S3_DELETE_OBJECT_VERSION_TAGGING: "s3:DeleteObjectVersionTagging",
S3_DELETE_OBJECT_VERSION: "s3:DeleteObjectVersion",
S3_GET_OBJECT_VERSION_TAGGING: "s3:GetObjectVersionTagging",
S3_GET_OBJECT_VERSION: "s3:GetObjectVersion",
S3_PUT_BUCKET_TAGGING: "s3:PutBucketTagging",
S3_GET_BUCKET_TAGGING: "s3:GetBucketTagging",
S3_BYPASS_GOVERNANCE_RETENTION: "s3:BypassGovernanceRetention",
S3_LIST_MULTIPART_UPLOAD_PARTS: "s3:ListMultipartUploadParts",
S3_LISTEN_NOTIFICATIONS: "s3:ListenNotification",
S3_LIST_BUCKET_MULTIPART_UPLOADS: "s3:ListBucketMultipartUploads",
S3_LIST_BUCKET_VERSIONS: "s3:ListBucketVersions",
S3_GET_BUCKET_POLICY_STATUS: "s3:GetBucketPolicyStatus",
S3_LIST_ALL_MY_BUCKETS: "s3:ListAllMyBuckets",
S3_HEAD_BUCKET: "s3:HeadBucket",
S3_GET_BUCKET_LOCATION: "s3:GetBucketLocation",
S3_DELETE_BUCKET_POLICY: "s3:DeleteBucketPolicy",
S3_ABORT_MULTIPART_UPLOAD: "s3:AbortMultipartUpload",
S3_ALL_ACTIONS: "s3:*",
ADMIN_ALL_ACTIONS: "admin:*",
};
export const IAM_PERMISSIONS = {
[IAM_ROLES.admin]: [
IAM_SCOPES.S3_ALL_ACTIONS,
IAM_SCOPES.ADMIN_ALL_ACTIONS,
IAM_SCOPES.S3_REPLICATE_OBJECT,
IAM_SCOPES.S3_REPLICATE_DELETE,
IAM_SCOPES.S3_REPLICATE_TAGS,
IAM_SCOPES.S3_GET_OBJECT_VERSION_FOR_REPLICATION,
IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_GET_BUCKET_VERSIONING,
IAM_SCOPES.S3_PUT_BUCKET_VERSIONING,
IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
IAM_SCOPES.S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
IAM_SCOPES.S3_DELETE_OBJECT_TAGGING,
IAM_SCOPES.S3_PUT_OBJECT_TAGGING,
IAM_SCOPES.S3_GET_OBJECT_TAGGING,
IAM_SCOPES.S3_PUT_OBJECT_VERSION_TAGGING,
IAM_SCOPES.S3_DELETE_OBJECT_VERSION_TAGGING,
IAM_SCOPES.S3_DELETE_OBJECT_VERSION,
IAM_SCOPES.S3_GET_OBJECT_VERSION_TAGGING,
IAM_SCOPES.S3_GET_OBJECT_VERSION,
IAM_SCOPES.S3_PUT_BUCKET_TAGGING,
IAM_SCOPES.S3_GET_BUCKET_TAGGING,
IAM_SCOPES.S3_PUT_BUCKET_OBJECT_LOCK_CONFIGURATION,
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD,
IAM_SCOPES.S3_GET_OBJECT_LEGAL_HOLD,
IAM_SCOPES.S3_GET_OBJECT_RETENTION,
IAM_SCOPES.S3_PUT_OBJECT_RETENTION,
IAM_SCOPES.S3_BYPASS_GOVERNANCE_RETENTION,
IAM_SCOPES.S3_PUT_BUCKET_POLICY,
IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_LIST_MULTIPART_UPLOAD_PARTS,
IAM_SCOPES.S3_LISTEN_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_LISTEN_NOTIFICATIONS,
IAM_SCOPES.S3_LIST_BUCKET_MULTIPART_UPLOADS,
IAM_SCOPES.S3_LIST_BUCKET_VERSIONS,
IAM_SCOPES.S3_GET_BUCKET_POLICY_STATUS,
IAM_SCOPES.S3_LIST_ALL_MY_BUCKETS,
IAM_SCOPES.S3_HEAD_BUCKET,
IAM_SCOPES.S3_GET_BUCKET_POLICY,
IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_GET_BUCKET_LOCATION,
IAM_SCOPES.S3_DELETE_BUCKET_POLICY,
IAM_SCOPES.S3_FORCE_DELETE_BUCKET,
IAM_SCOPES.S3_DELETE_BUCKET,
IAM_SCOPES.S3_CREATE_BUCKET,
IAM_SCOPES.S3_ABORT_MULTIPART_UPLOAD,
IAM_SCOPES.ADMIN_GET_POLICY,
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
IAM_SCOPES.ADMIN_LIST_USERS,
],
};
export const S3_ALL_RESOURCES = "arn:aws:s3:::*";
export const CONSOLE_UI_RESOURCE = "console-ui";

View File

@@ -30,14 +30,11 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import api from "../../../../common/api";
import history from "../../../../history";
import { BucketInfo } from "../types";
import { displayComponent } from "../../../../utils/permissions";
import {
ADMIN_GET_POLICY,
ADMIN_LIST_GROUPS,
ADMIN_LIST_USER_POLICIES,
ADMIN_LIST_USERS,
} from "../../../../types";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
const mapState = (state: AppState) => ({
session: state.console.session,
@@ -79,13 +76,17 @@ const AccessDetails = ({
const bucketName = match.params["bucketName"];
const displayPoliciesList = displayComponent(bucketInfo?.allowedActions, [
ADMIN_LIST_USER_POLICIES,
const displayPoliciesList = hasPermission(bucketName, [
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
]);
const displayUsersList = displayComponent(
bucketInfo?.allowedActions,
[ADMIN_GET_POLICY, ADMIN_LIST_USERS, ADMIN_LIST_GROUPS],
const displayUsersList = hasPermission(
bucketName,
[
IAM_SCOPES.ADMIN_GET_POLICY,
IAM_SCOPES.ADMIN_LIST_USERS,
IAM_SCOPES.ADMIN_LIST_GROUPS,
],
true
);
@@ -135,7 +136,6 @@ const AccessDetails = ({
useEffect(() => {
if (loadingPolicies) {
console.log("displayPoliciesList", displayPoliciesList);
if (displayPoliciesList) {
api
.invoke("GET", `/api/v1/bucket-policy/${bucketName}`)
@@ -167,24 +167,37 @@ const AccessDetails = ({
variant="scrollable"
scrollButtons="auto"
>
<Tab label="Policies" {...a11yProps(0)} />
{displayPoliciesList && <Tab label="Policies" {...a11yProps(0)} />}
{displayUsersList && <Tab label="Users" {...a11yProps(1)} />}
</Tabs>
<Paper>
<TabPanel index={0} value={curTab}>
<TableWrapper
disabled={!displayPoliciesList}
noBackground={true}
itemActions={PolicyActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingPolicies}
records={bucketPolicy}
entityName="Policies"
idField="name"
/>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_LIST_USER_POLICIES]}
resource={bucketName}
>
<TableWrapper
noBackground={true}
itemActions={PolicyActions}
columns={[{ label: "Name", elementKey: "name" }]}
isLoading={loadingPolicies}
records={bucketPolicy}
entityName="Policies"
idField="name"
/>
</SecureComponent>
</TabPanel>
{displayUsersList && (
<TabPanel index={1} value={curTab}>
<TabPanel index={1} value={curTab}>
<SecureComponent
scopes={[
IAM_SCOPES.ADMIN_GET_POLICY,
IAM_SCOPES.ADMIN_LIST_USERS,
IAM_SCOPES.ADMIN_LIST_GROUPS,
]}
resource={bucketName}
matchAll
>
<TableWrapper
noBackground={true}
itemActions={userTableActions}
@@ -194,8 +207,8 @@ const AccessDetails = ({
entityName="Users"
idField="accessKey"
/>
</TabPanel>
)}
</SecureComponent>
</TabPanel>
</Paper>
</Fragment>
);

View File

@@ -38,9 +38,11 @@ import {
searchField,
} from "../../Common/FormComponents/common/styleLibrary";
import { BucketInfo } from "../types";
import { displayComponent } from "../../../../utils/permissions";
import { S3_GET_BUCKET_POLICY, S3_PUT_BUCKET_POLICY } from "../../../../types";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
const styles = (theme: Theme) =>
createStyles({
@@ -146,15 +148,17 @@ const AccessRule = ({
const bucketName = match.params["bucketName"];
const displayAccessRules = displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_POLICY,
const displayAccessRules = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_POLICY,
]);
const displayAddAccessRules = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_BUCKET_POLICY, S3_PUT_BUCKET_POLICY],
true
);
const deleteAccessRules = hasPermission(bucketName, [
IAM_SCOPES.S3_DELETE_BUCKET_POLICY,
]);
const editAccessRules = hasPermission(bucketName, [
IAM_SCOPES.S3_PUT_BUCKET_POLICY,
]);
useEffect(() => {
if (loadingBucket) {
@@ -165,6 +169,7 @@ const AccessRule = ({
const AccessRuleActions = [
{
type: "delete",
disableButtonFunction: () => !deleteAccessRules,
onClick: (accessRule: any) => {
setDeleteAccessRuleOpen(true);
setAccessRuleToDelete(accessRule.prefix);
@@ -172,6 +177,7 @@ const AccessRule = ({
},
{
type: "view",
disableButtonFunction: () => !editAccessRules,
onClick: (accessRule: any) => {
setAccessRuleToEdit(accessRule.prefix);
setInitialAccess(accessRule.access);
@@ -247,7 +253,14 @@ const AccessRule = ({
)}
<Grid item xs={12} className={classes.actionsTray}>
<PanelTitle>Access Rules</PanelTitle>
{displayAddAccessRules && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_POLICY,
IAM_SCOPES.S3_PUT_BUCKET_POLICY,
]}
resource={bucketName}
matchAll
>
<Button
variant="contained"
color="primary"
@@ -260,22 +273,27 @@ const AccessRule = ({
>
Add Access Rule
</Button>
)}
</SecureComponent>
</Grid>
<Paper>
<TableWrapper
disabled={!displayAccessRules}
noBackground={true}
itemActions={AccessRuleActions}
columns={[
{ label: "Prefix", elementKey: "prefix" },
{ label: "Access", elementKey: "access" },
]}
isLoading={loadingAccessRules}
records={accessRules}
entityName="Access Rules"
idField="prefix"
/>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<TableWrapper
noBackground={true}
itemActions={AccessRuleActions}
columns={[
{ label: "Prefix", elementKey: "prefix" },
{ label: "Access", elementKey: "access" },
]}
isLoading={loadingAccessRules}
records={accessRules}
entityName="Access Rules"
idField="prefix"
/>
</SecureComponent>
</Paper>
</Fragment>
);

View File

@@ -31,6 +31,11 @@ import PageHeader from "../../Common/PageHeader/PageHeader";
import { SettingsIcon } from "../../../../icons";
import { BucketInfo } from "../types";
import { setErrorSnackMessage } from "../../../../actions";
import SecureComponent from "../../../../common/SecureComponent/SecureComponent";
import {
IAM_PERMISSIONS,
IAM_ROLES,
} from "../../../../common/SecureComponent/permissions";
interface IBrowserHandlerProps {
fileMode: boolean;
@@ -82,21 +87,22 @@ const BrowserHandler = ({
</Fragment>
}
actions={
bucketInfo?.manage && (
<Fragment>
<Tooltip title={"Configure Bucket"}>
<IconButton
color="primary"
aria-label="Configure Bucket"
component="span"
onClick={openBucketConfiguration}
size="large"
>
<SettingsIcon />
</IconButton>
</Tooltip>
</Fragment>
)
<SecureComponent
scopes={IAM_PERMISSIONS[IAM_ROLES.admin]}
resource={bucketName}
>
<Tooltip title={"Configure Bucket"}>
<IconButton
color="primary"
aria-label="Configure Bucket"
component="span"
onClick={openBucketConfiguration}
size="large"
>
<SettingsIcon />
</IconButton>
</Tooltip>
</SecureComponent>
}
/>
<Grid>{fileMode ? <ObjectDetails /> : <ListObjects />}</Grid>

View File

@@ -52,24 +52,13 @@ import DeleteBucket from "../ListBuckets/DeleteBucket";
import AccessRulePanel from "./AccessRulePanel";
import RefreshIcon from "../../../../icons/RefreshIcon";
import BoxIconButton from "../../Common/BoxIconButton/BoxIconButton";
import {
ADMIN_GET_POLICY,
ADMIN_LIST_USER_POLICIES,
ADMIN_LIST_USERS,
S3_DELETE_BUCKET,
S3_FORCE_DELETE_BUCKET,
S3_GET_BUCKET_NOTIFICATIONS,
S3_GET_BUCKET_POLICY,
S3_GET_LIFECYCLE_CONFIGURATION,
S3_GET_REPLICATION_CONFIGURATION,
S3_PUT_BUCKET_NOTIFICATIONS,
S3_PUT_LIFECYCLE_CONFIGURATION,
S3_PUT_REPLICATION_CONFIGURATION,
} from "../../../../types";
import { displayComponent } from "../../../../utils/permissions";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import PageLayout from "../../Common/Layout/PageLayout";
import VerticalTabs from "../../Common/VerticalTabs/VerticalTabs";
import BackLink from "../../../../common/BackLink";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
const styles = (theme: Theme) =>
createStyles({
@@ -337,25 +326,25 @@ const BucketDetails = ({
}
title={bucketName}
subTitle={
displayComponent(
bucketInfo?.allowedActions,
[S3_GET_BUCKET_POLICY],
false
) && (
<Fragment>
Access:{" "}
<span className={classes.capitalize}>
{bucketInfo?.access.toLowerCase()}
</span>
</Fragment>
)
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY]}
resource={bucketName}
>
Access:{" "}
<span className={classes.capitalize}>
{bucketInfo?.access.toLowerCase()}
</span>
</SecureComponent>
}
actions={
<Fragment>
{displayComponent(bucketInfo?.allowedActions, [
S3_DELETE_BUCKET,
S3_FORCE_DELETE_BUCKET,
]) && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_DELETE_BUCKET,
IAM_SCOPES.S3_FORCE_DELETE_BUCKET,
]}
resource={bucketName}
>
<BoxIconButton
tooltip={"Delete"}
color="primary"
@@ -367,7 +356,7 @@ const BucketDetails = ({
>
<DeleteIcon />
</BoxIconButton>
)}
</SecureComponent>
<BoxIconButton
tooltip={"Refresh"}
color="primary"
@@ -450,9 +439,9 @@ const BucketDetails = ({
label: "Events",
value: "events",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_NOTIFICATIONS,
S3_PUT_BUCKET_NOTIFICATIONS,
disabled: !hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS,
IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
]),
to: getRoutePath("events"),
},
@@ -464,9 +453,9 @@ const BucketDetails = ({
component: Link,
disabled:
!distributedSetup ||
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_REPLICATION_CONFIGURATION,
S3_PUT_REPLICATION_CONFIGURATION,
!hasPermission(bucketName, [
IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION,
]),
to: getRoutePath("replication"),
},
@@ -478,9 +467,9 @@ const BucketDetails = ({
component: Link,
disabled:
!distributedSetup ||
!displayComponent(bucketInfo?.allowedActions, [
S3_GET_LIFECYCLE_CONFIGURATION,
S3_PUT_LIFECYCLE_CONFIGURATION,
!hasPermission(bucketName, [
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
]),
to: getRoutePath("lifecycle"),
},
@@ -490,10 +479,10 @@ const BucketDetails = ({
label: "Access Audit",
value: "access",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
ADMIN_GET_POLICY,
ADMIN_LIST_USER_POLICIES,
ADMIN_LIST_USERS,
disabled: !hasPermission(bucketName, [
IAM_SCOPES.ADMIN_GET_POLICY,
IAM_SCOPES.ADMIN_LIST_USER_POLICIES,
IAM_SCOPES.ADMIN_LIST_USERS,
]),
to: getRoutePath("access"),
},
@@ -503,8 +492,8 @@ const BucketDetails = ({
label: "Access Rules",
value: "prefix",
component: Link,
disabled: !displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_POLICY,
disabled: !hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_POLICY,
]),
to: getRoutePath("prefix"),
},

View File

@@ -36,13 +36,11 @@ import api from "../../../../common/api";
import DeleteEvent from "./DeleteEvent";
import AddEvent from "./AddEvent";
import HelpBox from "../../../../common/HelpBox";
import { displayComponent } from "../../../../utils/permissions";
import {
ADMIN_SERVER_INFO,
S3_GET_BUCKET_NOTIFICATIONS,
S3_PUT_BUCKET_NOTIFICATIONS,
} from "../../../../types";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
const styles = (theme: Theme) =>
createStyles({
@@ -76,16 +74,10 @@ const BucketEventsPanel = ({
const bucketName = match.params["bucketName"];
const displayEvents = displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_NOTIFICATIONS,
const displayEvents = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS,
]);
const displaySubscribeToEvents = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_BUCKET_NOTIFICATIONS, ADMIN_SERVER_INFO],
true
);
useEffect(() => {
if (loadingBucket) {
setLoadingEvents(true);
@@ -156,7 +148,14 @@ const BucketEventsPanel = ({
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<PanelTitle>Events</PanelTitle>
{displaySubscribeToEvents && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_BUCKET_NOTIFICATIONS,
IAM_SCOPES.ADMIN_SERVER_INFO,
]}
resource={bucketName}
matchAll
>
<Button
variant="contained"
color="primary"
@@ -168,28 +167,33 @@ const BucketEventsPanel = ({
>
Subscribe to Event
</Button>
)}
</SecureComponent>
</Grid>
<Grid item xs={12}>
<TableWrapper
disabled={!displayEvents}
itemActions={tableActions}
columns={[
{ label: "SQS", elementKey: "arn" },
{
label: "Events",
elementKey: "events",
renderFunction: eventsDisplay,
},
{ label: "Prefix", elementKey: "prefix" },
{ label: "Suffix", elementKey: "suffix" },
]}
isLoading={loadingEvents}
records={records}
entityName="Events"
idField="id"
customPaperHeight={classes.twHeight}
/>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_NOTIFICATIONS]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<TableWrapper
itemActions={tableActions}
columns={[
{ label: "SQS", elementKey: "arn" },
{
label: "Events",
elementKey: "events",
renderFunction: eventsDisplay,
},
{ label: "Prefix", elementKey: "prefix" },
{ label: "Suffix", elementKey: "suffix" },
]}
isLoading={loadingEvents}
records={records}
entityName="Events"
idField="id"
customPaperHeight={classes.twHeight}
/>
</SecureComponent>
</Grid>
{!loadingEvents && (
<Grid item xs={12}>

View File

@@ -37,13 +37,11 @@ import EditLifecycleConfiguration from "./EditLifecycleConfiguration";
import AddLifecycleModal from "./AddLifecycleModal";
import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import HelpBox from "../../../../common/HelpBox";
import { displayComponent } from "../../../../utils/permissions";
import {
ADMIN_LIST_TIERS,
S3_GET_LIFECYCLE_CONFIGURATION,
S3_PUT_LIFECYCLE_CONFIGURATION,
} from "../../../../types";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
const styles = (theme: Theme) =>
createStyles({
@@ -76,16 +74,10 @@ const BucketLifecyclePanel = ({
const bucketName = match.params["bucketName"];
const displayLifeCycleRules = displayComponent(bucketInfo?.allowedActions, [
S3_GET_LIFECYCLE_CONFIGURATION,
const displayLifeCycleRules = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION,
]);
const displayAddLifeCycleRules = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_LIFECYCLE_CONFIGURATION, ADMIN_LIST_TIERS],
true
);
useEffect(() => {
if (loadingBucket) {
setLoadingLifecycle(true);
@@ -212,7 +204,14 @@ const BucketLifecyclePanel = ({
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<PanelTitle>Lifecycle Rules</PanelTitle>
{displayAddLifeCycleRules && (
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_LIFECYCLE_CONFIGURATION,
IAM_SCOPES.ADMIN_LIST_TIERS,
]}
resource={bucketName}
matchAll
>
<Button
variant="contained"
color="primary"
@@ -224,19 +223,25 @@ const BucketLifecyclePanel = ({
>
Add Lifecycle Rule
</Button>
)}
</SecureComponent>
</Grid>
<Grid item xs={12}>
<TableWrapper
itemActions={[]}
columns={lifecycleColumns}
isLoading={loadingLifecycle}
records={lifecycleRecords}
entityName="Lifecycle"
customEmptyMessage="There are no Lifecycle rules yet"
idField="id"
customPaperHeight={classes.twHeight}
/>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_LIFECYCLE_CONFIGURATION]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<TableWrapper
itemActions={[]}
columns={lifecycleColumns}
isLoading={loadingLifecycle}
records={lifecycleRecords}
entityName="Lifecycle"
customEmptyMessage="There are no Lifecycle rules yet"
idField="id"
customPaperHeight={classes.twHeight}
/>
</SecureComponent>
</Grid>
{!loadingLifecycle && (
<Grid item xs={12}>

View File

@@ -40,12 +40,11 @@ import TableWrapper from "../../Common/TableWrapper/TableWrapper";
import AddReplicationModal from "./AddReplicationModal";
import DeleteReplicationRule from "./DeleteReplicationRule";
import HelpBox from "../../../../common/HelpBox";
import { displayComponent } from "../../../../utils/permissions";
import {
S3_GET_REPLICATION_CONFIGURATION,
S3_PUT_REPLICATION_CONFIGURATION,
} from "../../../../types";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
interface IBucketReplicationProps {
classes: any;
@@ -82,16 +81,10 @@ const BucketReplicationPanel = ({
const bucketName = match.params["bucketName"];
const displayReplicationRules = displayComponent(bucketInfo?.allowedActions, [
S3_GET_REPLICATION_CONFIGURATION,
const displayReplicationRules = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION,
]);
const displayAddReplicationRules = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_REPLICATION_CONFIGURATION],
true
);
useEffect(() => {
if (loadingBucket) {
setLoadingReplication(true);
@@ -182,7 +175,11 @@ const BucketReplicationPanel = ({
<Grid container>
<Grid item xs={12} className={classes.actionsTray}>
<PanelTitle>Replication</PanelTitle>
{displayAddReplicationRules && (
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_REPLICATION_CONFIGURATION]}
resource={bucketName}
matchAll
>
<Button
variant="contained"
color="primary"
@@ -194,39 +191,44 @@ const BucketReplicationPanel = ({
>
Add Replication Rule
</Button>
)}
</SecureComponent>
</Grid>
<Grid item xs={12}>
<TableWrapper
disabled={!displayReplicationRules}
itemActions={replicationTableActions}
columns={[
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Prefix",
elementKey: "prefix",
},
{
label: "Tags",
elementKey: "tags",
renderFunction: tagDisplay,
},
{ label: "Status", elementKey: "status" },
]}
isLoading={loadingReplication}
records={replicationRules}
entityName="Replication Rules"
idField="id"
customPaperHeight={classes.twHeight}
/>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<TableWrapper
itemActions={replicationTableActions}
columns={[
{
label: "Priority",
elementKey: "priority",
},
{
label: "Destination",
elementKey: "destination",
renderFunction: ruleDestDisplay,
},
{
label: "Prefix",
elementKey: "prefix",
},
{
label: "Tags",
elementKey: "tags",
renderFunction: tagDisplay,
},
{ label: "Status", elementKey: "status" },
]}
isLoading={loadingReplication}
records={replicationRules}
entityName="Replication Rules"
idField="id"
customPaperHeight={classes.twHeight}
/>
</SecureComponent>
</Grid>
<Grid item xs={12}>
<HelpBox

View File

@@ -53,21 +53,7 @@ import GavelIcon from "@mui/icons-material/Gavel";
import EnableQuota from "./EnableQuota";
import { setBucketDetailsLoad } from "../actions";
import ReportedUsageIcon from "../../../../icons/ReportedUsageIcon";
import { displayComponent } from "../../../../utils/permissions";
import {
ADMIN_GET_BUCKET_QUOTA,
ADMIN_SET_BUCKET_QUOTA,
S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
S3_GET_BUCKET_POLICY,
S3_GET_BUCKET_VERSIONING,
S3_GET_OBJECT_RETENTION,
S3_GET_REPLICATION_CONFIGURATION,
S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
S3_PUT_BUCKET_POLICY,
S3_PUT_BUCKET_VERSIONING,
S3_PUT_OBJECT_RETENTION,
} from "../../../../types";
import { IAM_SCOPES } from "../../../../common/SecureComponent/permissions";
import PanelTitle from "../../Common/PanelTitle/PanelTitle";
import Chip from "@mui/material/Chip";
@@ -75,6 +61,9 @@ import AddIcon from "@mui/icons-material/Add";
import CloseIcon from "@mui/icons-material/Close";
import AddBucketTagModal from "./AddBucketTagModal";
import DeleteBucketTagModal from "./DeleteBucketTagModal";
import SecureComponent, {
hasPermission,
} from "../../../../common/SecureComponent/SecureComponent";
interface IBucketSummaryProps {
classes: any;
@@ -185,62 +174,18 @@ const BucketSummary = ({
accessPolicy = bucketInfo.access;
}
const displayGetBucketPolicy = displayComponent(bucketInfo?.allowedActions, [
S3_GET_BUCKET_POLICY,
const displayGetBucketObjectLockConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
]);
const displayPutBucketPolicy = displayComponent(bucketInfo?.allowedActions, [
S3_PUT_BUCKET_POLICY,
const displayGetBucketEncryptionConfiguration = hasPermission(bucketName, [
IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION,
]);
const displayGetReplicationConfiguration = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_REPLICATION_CONFIGURATION]
);
const displayGetBucketObjectLockConfiguration = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION]
);
const displayGetBucketEncryptionConfiguration = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_BUCKET_ENCRYPTION_CONFIGURATION]
);
const displayPutBucketEncryptionConfiguration = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION]
);
const displayGetBucketVersioning = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_BUCKET_VERSIONING]
);
const displayPutBucketVersioning = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_BUCKET_VERSIONING]
);
const displayGetBucketQuota = displayComponent(bucketInfo?.allowedActions, [
ADMIN_GET_BUCKET_QUOTA,
const displayGetBucketQuota = hasPermission(bucketName, [
IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA,
]);
const displaySetBucketQuota = displayComponent(bucketInfo?.allowedActions, [
ADMIN_SET_BUCKET_QUOTA,
]);
const displayGetObjectRetention = displayComponent(
bucketInfo?.allowedActions,
[S3_GET_OBJECT_RETENTION]
);
const displayPutObjectRetention = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_OBJECT_RETENTION]
);
useEffect(() => {
if (loadingBucket) {
setBucketLoading(true);
@@ -576,34 +521,45 @@ const BucketSummary = ({
<Grid item xs={8}>
<table width={"100%"}>
<tbody>
{displayGetBucketPolicy && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_POLICY]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Access Policy:</td>
<td className={classes.capitalizeFirst}>
<Button
disabled={!displayPutBucketPolicy}
color="primary"
className={classes.anchorButton}
onClick={() => {
setAccessPolicyScreenOpen(true);
}}
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_BUCKET_POLICY]}
resource={bucketName}
errorProps={{ disabled: true }}
>
{bucketLoading ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</Button>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setAccessPolicyScreenOpen(true);
}}
>
{bucketLoading ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
accessPolicy.toLowerCase()
)}
</Button>
</SecureComponent>
</td>
</tr>
)}
</SecureComponent>
{distributedSetup && (
<Fragment>
{displayGetReplicationConfiguration && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_REPLICATION_CONFIGURATION]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Replication:</td>
<td className={classes.doubleElement}>
@@ -612,16 +568,24 @@ const BucketSummary = ({
</span>
</td>
</tr>
)}
{displayGetBucketObjectLockConfiguration && (
</SecureComponent>
<SecureComponent
scopes={[
IAM_SCOPES.S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION,
]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Object Locking:</td>
<td>{!hasObjectLocking ? "Disabled" : "Enabled"}</td>
</tr>
)}
</SecureComponent>
</Fragment>
)}
{displayGetBucketEncryptionConfiguration && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_ENCRYPTION_CONFIGURATION]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Encryption:</td>
<td>
@@ -632,20 +596,27 @@ const BucketSummary = ({
variant="indeterminate"
/>
) : (
<Button
disabled={!displayPutBucketEncryptionConfiguration}
color="primary"
className={classes.anchorButton}
onClick={() => {
setEnableEncryptionScreenOpen(true);
}}
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION,
]}
resource={bucketName}
errorProps={{ disabled: true }}
>
{encryptionEnabled ? "Enabled" : "Disabled"}
</Button>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setEnableEncryptionScreenOpen(true);
}}
>
{encryptionEnabled ? "Enabled" : "Disabled"}
</Button>
</SecureComponent>
)}
</td>
</tr>
)}
</SecureComponent>
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
@@ -705,39 +676,48 @@ const BucketSummary = ({
</Paper>
<br />
<br />
{distributedSetup && displayGetBucketVersioning && (
<Fragment>
<Paper className={classes.paperContainer} elevation={1}>
<Grid container>
<Grid item xs={quotaEnabled ? 9 : 12}>
<h2>Versioning</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr>
<td className={classes.titleCol}>Versioning:</td>
<td>
{loadingVersioning ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<Fragment>
<Button
disabled={!displayPutBucketVersioning}
{distributedSetup && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_BUCKET_VERSIONING]}
resource={bucketName}
>
<Fragment>
<Paper className={classes.paperContainer} elevation={1}>
<Grid container>
<Grid item xs={quotaEnabled ? 9 : 12}>
<h2>Versioning</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr>
<td className={classes.titleCol}>Versioning:</td>
<td>
{loadingVersioning ? (
<CircularProgress
color="primary"
className={classes.anchorButton}
onClick={setBucketVersioning}
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_BUCKET_VERSIONING]}
resource={bucketName}
errorProps={{ disabled: true }}
>
{isVersioned ? "Enabled" : "Disabled"}
</Button>
</Fragment>
)}
</td>
{displayGetBucketQuota && (
<Fragment>
<Button
color="primary"
className={classes.anchorButton}
onClick={setBucketVersioning}
>
{isVersioned ? "Enabled" : "Disabled"}
</Button>
</SecureComponent>
)}
</td>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_GET_BUCKET_QUOTA]}
resource={bucketName}
>
<td className={classes.titleCol}>Quota:</td>
<td>
{loadingQuota ? (
@@ -747,112 +727,123 @@ const BucketSummary = ({
variant="indeterminate"
/>
) : (
<Fragment>
<SecureComponent
scopes={[IAM_SCOPES.ADMIN_SET_BUCKET_QUOTA]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
disabled={!displaySetBucketQuota}
color="primary"
className={classes.anchorButton}
onClick={setBucketQuota}
>
{quotaEnabled ? "Enabled" : "Disabled"}
</Button>
</Fragment>
</SecureComponent>
)}
</td>
</SecureComponent>
</tr>
</tbody>
</table>
</Grid>
{quotaEnabled && quota && (
<Grid item xs={3} className={classes.reportedUsage}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon} xs={2}>
<GavelIcon />
</Grid>
<Grid item xs={10}>
<Typography className={classes.elementTitle}>
{cap(quota?.type)} Quota
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{niceBytes(`${quota?.quota}`)}
</Typography>
</Grid>
)}
</Grid>
</Paper>
<br />
<br />
</Fragment>
</SecureComponent>
)}
{hasObjectLocking && (
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
resource={bucketName}
>
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<h2>Retention</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr className={classes.gridContainer}>
<td className={classes.titleCol}>Status:</td>
<td>
{loadingRetention ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<Button
color="primary"
className={classes.anchorButton}
onClick={() => {
setRetentionConfigOpen(true);
}}
>
{!retentionEnabled ? "Disabled" : "Enabled"}
</Button>
</SecureComponent>
)}
</td>
{retentionConfig === null ? (
<td colSpan={2}>&nbsp;</td>
) : (
<Fragment>
<td className={classes.titleCol}>Mode:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.mode}
</td>
</Fragment>
)}
</tr>
<tr className={classes.gridContainer}>
{retentionConfig === null ? (
<td colSpan={2}></td>
) : (
<Fragment>
<td className={classes.titleCol}>Valitidy:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.validity}{" "}
{retentionConfig &&
(retentionConfig.validity === 1
? retentionConfig.unit.slice(0, -1)
: retentionConfig.unit)}
</td>
</Fragment>
)}
</tr>
</tbody>
</table>
</Grid>
{quotaEnabled && quota && (
<Grid item xs={3} className={classes.reportedUsage}>
<Grid container direction="row" alignItems="center">
<Grid item className={classes.icon} xs={2}>
<GavelIcon />
</Grid>
<Grid item xs={10}>
<Typography className={classes.elementTitle}>
{cap(quota?.type)} Quota
</Typography>
</Grid>
</Grid>
<Typography className={classes.consumptionValue}>
{niceBytes(`${quota?.quota}`)}
</Typography>
</Grid>
)}
</Grid>
</Paper>
<br />
<br />
</Fragment>
)}
{hasObjectLocking && displayGetObjectRetention && (
<Paper className={classes.paperContainer}>
<Grid container>
<Grid item xs={12}>
<h2>Retention</h2>
<hr className={classes.hrClass} />
<table width={"100%"}>
<tbody>
<tr className={classes.gridContainer}>
<td className={classes.titleCol}>Status:</td>
<td>
{loadingRetention ? (
<CircularProgress
color="primary"
size={16}
variant="indeterminate"
/>
) : (
<Fragment>
<Button
disabled={!displayPutObjectRetention}
color="primary"
className={classes.anchorButton}
onClick={() => {
setRetentionConfigOpen(true);
}}
>
{!retentionEnabled ? "Disabled" : "Enabled"}
</Button>
</Fragment>
)}
</td>
{retentionConfig === null ? (
<td colSpan={2}>&nbsp;</td>
) : (
<Fragment>
<td className={classes.titleCol}>Mode:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.mode}
</td>
</Fragment>
)}
</tr>
<tr className={classes.gridContainer}>
{retentionConfig === null ? (
<td colSpan={2}></td>
) : (
<Fragment>
<td className={classes.titleCol}>Valitidy:</td>
<td className={classes.capitalizeFirst}>
{retentionConfig && retentionConfig.validity}{" "}
{retentionConfig &&
(retentionConfig.validity === 1
? retentionConfig.unit.slice(0, -1)
: retentionConfig.unit)}
</td>
</Fragment>
)}
</tr>
</tbody>
</table>
</Grid>
</Grid>
</Paper>
</SecureComponent>
)}
</Fragment>
);

View File

@@ -29,6 +29,11 @@ import { Box, Button, Grid, Typography } from "@mui/material";
import { niceBytes, prettyNumber } from "../../../../common/utils";
import CheckboxWrapper from "../../Common/FormComponents/CheckboxWrapper/CheckboxWrapper";
import { Link } from "react-router-dom";
import {
IAM_PERMISSIONS,
IAM_ROLES,
} from "../../../../common/SecureComponent/permissions";
import SecureComponent from "../../../../common/SecureComponent/SecureComponent";
const styles = (theme: Theme) =>
createStyles({
@@ -213,7 +218,10 @@ const BucketListItem = ({
</Grid>
</Grid>
<Grid item xs={12} sm={4} textAlign={"right"}>
{bucket.manage && (
<SecureComponent
scopes={IAM_PERMISSIONS[IAM_ROLES.admin]}
resource={bucket.name}
>
<Link
to={`/buckets/${bucket.name}/admin`}
style={{ textDecoration: "none" }}
@@ -226,7 +234,7 @@ const BucketListItem = ({
Manage
</Button>
</Link>
)}
</SecureComponent>
<Link
to={`/buckets/${bucket.name}/browse`}
style={{ textDecoration: "none" }}

View File

@@ -21,7 +21,7 @@ import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { Button, LinearProgress } from "@mui/material";
import Grid from "@mui/material/Grid";
import { Bucket, BucketList, HasPermissionResponse } from "../types";
import { Bucket, BucketList } from "../types";
import { AddIcon, BucketsIcon } from "../../../../icons";
import { AppState } from "../../../../store";
import { setErrorSnackMessage } from "../../../../actions";
@@ -45,6 +45,11 @@ import RefreshIcon from "../../../../icons/RefreshIcon";
import AButton from "../../Common/AButton/AButton";
import MultipleBucketsIcon from "../../../../icons/MultipleBucketsIcon";
import SelectMultipleIcon from "../../../../icons/SelectMultipleIcon";
import SecureComponent from "../../../../common/SecureComponent/SecureComponent";
import {
CONSOLE_UI_RESOURCE,
IAM_SCOPES,
} from "../../../../common/SecureComponent/permissions";
const styles = (theme: Theme) =>
createStyles({
@@ -150,49 +155,12 @@ const ListBuckets = ({
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [selectedBucket, setSelectedBucket] = useState<string>("");
const [filterBuckets, setFilterBuckets] = useState<string>("");
const [loadingPerms, setLoadingPerms] = useState<boolean>(true);
const [canCreateBucket, setCanCreateBucket] = useState<boolean>(false);
const [selectedBuckets, setSelectedBuckets] = useState<string[]>([]);
const [replicationModalOpen, setReplicationModalOpen] =
useState<boolean>(false);
const [bulkSelect, setBulkSelect] = useState<boolean>(false);
// check the permissions for creating bucket
useEffect(() => {
if (loadingPerms) {
api
.invoke("POST", `/api/v1/has-permission`, {
actions: [
{
id: "createBucket",
action: "s3:CreateBucket",
},
],
})
.then((res: HasPermissionResponse) => {
setLoadingPerms(false);
if (!res.permissions) {
return;
}
const actions = res.permissions ? res.permissions : [];
let canCreate = actions.find((s) => s.id === "createBucket");
if (canCreate && canCreate.can) {
setCanCreateBucket(true);
} else {
setCanCreateBucket(false);
}
setLoadingPerms(false);
})
.catch((err: ErrorResponseHandler) => {
setLoadingPerms(false);
setErrorSnackMessage(err);
});
}
}, [loadingPerms, setErrorSnackMessage]);
useEffect(() => {
if (loading) {
const fetchRecords = () => {
@@ -340,7 +308,10 @@ const ListBuckets = ({
>
<RefreshIcon />
</BoxIconButton>
{canCreateBucket && (
<SecureComponent
scopes={[IAM_SCOPES.S3_CREATE_BUCKET]}
resource={CONSOLE_UI_RESOURCE}
>
<Button
variant="contained"
color="primary"
@@ -352,7 +323,7 @@ const ListBuckets = ({
>
Create Bucket
</Button>
)}
</SecureComponent>
</Grid>
</Grid>
</Grid>

View File

@@ -94,16 +94,14 @@ import {
FileZipIcon,
} from "../../../../../../icons";
import ShareFile from "../ObjectDetails/ShareFile";
import { displayComponent } from "../../../../../../utils/permissions";
import {
S3_DELETE_OBJECT,
S3_LIST_BUCKET,
S3_PUT_OBJECT,
} from "../../../../../../types";
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
import { setBucketDetailsLoad, setBucketInfo } from "../../../actions";
import { AppState } from "../../../../../../store";
import PageLayout from "../../../../Common/Layout/PageLayout";
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
import SecureComponent, {
hasPermission,
} from "../../../../../../common/SecureComponent/SecureComponent";
const commonIcon = {
backgroundRepeat: "no-repeat",
@@ -291,16 +289,12 @@ const ListObjects = ({
const fileUpload = useRef<HTMLInputElement>(null);
const displayPutObject = displayComponent(bucketInfo?.allowedActions, [
S3_PUT_OBJECT,
const displayDeleteObject = hasPermission(bucketName, [
IAM_SCOPES.S3_DELETE_OBJECT,
]);
const displayDeleteObject = displayComponent(bucketInfo?.allowedActions, [
S3_DELETE_OBJECT,
]);
const displayListObjects = displayComponent(bucketInfo?.allowedActions, [
S3_LIST_BUCKET,
const displayListObjects = hasPermission(bucketName, [
IAM_SCOPES.S3_LIST_BUCKET,
]);
const updateMessage = () => {
@@ -339,11 +333,7 @@ const ListObjects = ({
}, 1000);
useEffect(() => {
if (
loadingVersioning &&
bucketInfo?.allowedActions &&
bucketInfo?.name === bucketName
) {
if (loadingVersioning) {
if (displayListObjects) {
api
.invoke("GET", `/api/v1/buckets/${bucketName}/versioning`)
@@ -359,14 +349,7 @@ const ListObjects = ({
setLoadingVersioning(false);
}
}
}, [
bucketName,
loadingVersioning,
setErrorSnackMessage,
bucketInfo?.allowedActions,
bucketInfo?.name,
displayListObjects,
]);
}, [bucketName, loadingVersioning, setErrorSnackMessage, displayListObjects]);
// Rewind
useEffect(() => {
@@ -423,11 +406,7 @@ const ListObjects = ({
}, [internalPaths]);
useEffect(() => {
if (
loading &&
bucketInfo?.allowedActions &&
bucketInfo?.name === bucketName
) {
if (loading) {
if (displayListObjects) {
let pathPrefix = "";
if (internalPaths) {
@@ -1085,44 +1064,45 @@ const ListObjects = ({
}
actions={
<Fragment>
{displayPutObject && (
<Fragment>
<BoxIconButton
tooltip={"Choose or create a new path"}
color="primary"
aria-label="Add a new folder"
onClick={() => {
setCreateFolderOpen(true);
}}
disabled={rewindEnabled}
size="large"
>
<AddFolderIcon />
</BoxIconButton>
<BoxIconButton
tooltip={"Upload file"}
color="primary"
aria-label="Refresh List"
onClick={() => {
if (fileUpload && fileUpload.current) {
fileUpload.current.click();
}
}}
disabled={rewindEnabled}
size="large"
>
<UploadIcon />
</BoxIconButton>
<input
type="file"
multiple={true}
onChange={(e) => uploadObject(e)}
id="file-input"
style={{ display: "none" }}
ref={fileUpload}
/>
</Fragment>
)}
<SecureComponent
resource={bucketName}
scopes={[IAM_SCOPES.S3_PUT_OBJECT]}
>
<BoxIconButton
tooltip={"Choose or create a new path"}
color="primary"
aria-label="Add a new folder"
onClick={() => {
setCreateFolderOpen(true);
}}
disabled={rewindEnabled}
size="large"
>
<AddFolderIcon />
</BoxIconButton>
<BoxIconButton
tooltip={"Upload file"}
color="primary"
aria-label="Refresh List"
onClick={() => {
if (fileUpload && fileUpload.current) {
fileUpload.current.click();
}
}}
disabled={rewindEnabled}
size="large"
>
<UploadIcon />
</BoxIconButton>
<input
type="file"
multiple={true}
onChange={(e) => uploadObject(e)}
id="file-input"
style={{ display: "none" }}
ref={fileUpload}
/>
</SecureComponent>
<Badge
badgeContent=" "
color="secondary"
@@ -1161,7 +1141,10 @@ const ListObjects = ({
/>
</Grid>
<Grid item xs={12} className={classes.actionsTray}>
{displayListObjects && (
<SecureComponent
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
resource={bucketName}
>
<TextField
placeholder="Search Objects"
className={classes.searchField}
@@ -1180,9 +1163,11 @@ const ListObjects = ({
}}
variant="standard"
/>
)}
{displayDeleteObject && (
</SecureComponent>
<SecureComponent
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
resource={bucketName}
>
<Button
variant="contained"
color="primary"
@@ -1194,33 +1179,38 @@ const ListObjects = ({
>
Delete Selected
</Button>
)}
</SecureComponent>
</Grid>
<Grid item xs={12}>
<br />
</Grid>
<Grid item xs={12}>
<TableWrapper
disabled={!displayListObjects}
itemActions={tableActions}
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
isLoading={rewindEnabled ? loadingRewind : loading}
loadingMessage={loadingMessage}
entityName="Objects"
idField="name"
records={payload}
customPaperHeight={classes.browsePaper}
selectedItems={selectedObjects}
onSelect={selectListObjects}
customEmptyMessage={`This location is empty${
!rewindEnabled ? ", please try uploading a new file" : ""
}`}
sortConfig={{
currentSort: currentSortField,
currentDirection: sortDirection,
triggerSort: sortChange,
}}
/>
<SecureComponent
scopes={[IAM_SCOPES.S3_LIST_BUCKET]}
resource={bucketName}
errorProps={{ disabled: true }}
>
<TableWrapper
itemActions={tableActions}
columns={rewindEnabled ? rewindModeColumns : listModeColumns}
isLoading={rewindEnabled ? loadingRewind : loading}
loadingMessage={loadingMessage}
entityName="Objects"
idField="name"
records={payload}
customPaperHeight={classes.browsePaper}
selectedItems={selectedObjects}
onSelect={selectListObjects}
customEmptyMessage={`This location is empty${
!rewindEnabled ? ", please try uploading a new file" : ""
}`}
sortConfig={{
currentSort: currentSortField,
currentDirection: sortDirection,
triggerSort: sortChange,
}}
/>
</SecureComponent>
</Grid>
</PageLayout>
</React.Fragment>

View File

@@ -63,18 +63,7 @@ import {
setSnackBarMessage,
} from "../../../../../../actions";
import { decodeFileName, encodeFileName } from "../../../../../../common/utils";
import { BucketInfo } from "../../../types";
import { displayComponent } from "../../../../../../utils/permissions";
import {
S3_DELETE_OBJECT,
S3_DELETE_OBJECT_TAGGING,
S3_GET_OBJECT_LEGAL_HOLD,
S3_GET_OBJECT_RETENTION,
S3_GET_OBJECT_TAGGING,
S3_PUT_OBJECT_LEGAL_HOLD,
S3_PUT_OBJECT_RETENTION,
S3_PUT_OBJECT_TAGGING,
} from "../../../../../../types";
import { IAM_SCOPES } from "../../../../../../common/SecureComponent/permissions";
import SetRetention from "./SetRetention";
import BrowserBreadcrumbs from "../../../../ObjectBrowser/BrowserBreadcrumbs";
import DeleteObject from "../ListObjects/DeleteObject";
@@ -91,6 +80,7 @@ import PageLayout from "../../../../Common/Layout/PageLayout";
import VerticalTabs from "../../../../Common/VerticalTabs/VerticalTabs";
import BoxIconButton from "../../../../Common/BoxIconButton/BoxIconButton";
import { RecoverIcon } from "../../../../../../icons";
import SecureComponent from "../../../../../../common/SecureComponent/SecureComponent";
const styles = (theme: Theme) =>
createStyles({
@@ -271,8 +261,6 @@ const ObjectDetails = ({
const [deleteOpen, setDeleteOpen] = useState<boolean>(false);
const [metadataLoad, setMetadataLoad] = useState<boolean>(true);
const [metadata, setMetadata] = useState<any>({});
const [loadingBucket, setLoadingBucket] = useState<boolean>(false);
const [bucketInfo, setBucketInfo] = useState<any>(null);
const [restoreVersionOpen, setRestoreVersionOpen] = useState<boolean>(false);
const [restoreVersion, setRestoreVersion] = useState<string>("");
@@ -288,64 +276,6 @@ const ObjectDetails = ({
objectNameArray = actualInfo.name.split("/");
}
const displayObjectLegalHold = displayComponent(bucketInfo?.allowedActions, [
S3_GET_OBJECT_LEGAL_HOLD,
]);
const displayEditObjectLegalHold = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_OBJECT_LEGAL_HOLD],
true
);
const displayObjectRetention = displayComponent(bucketInfo?.allowedActions, [
S3_GET_OBJECT_RETENTION,
]);
const displayEditObjectRetention = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_OBJECT_RETENTION],
true
);
const displayObjectTag = displayComponent(bucketInfo?.allowedActions, [
S3_GET_OBJECT_TAGGING,
]);
const displayEditObjectTagging = displayComponent(
bucketInfo?.allowedActions,
[S3_PUT_OBJECT_TAGGING],
true
);
const displayRemoveObjectTagging = displayComponent(
bucketInfo?.allowedActions,
[S3_DELETE_OBJECT_TAGGING],
true
);
const displayDeleteObject = displayComponent(
bucketInfo?.allowedActions,
[S3_DELETE_OBJECT],
true
);
// bucket info
useEffect(() => {
if (!loadingBucket) {
setLoadingBucket(true);
api
.invoke("GET", `/api/v1/buckets/${bucketName}`)
.then((res: BucketInfo) => {
setBucketInfo(res);
})
.catch((err: ErrorResponseHandler) => {
setLoadingBucket(false);
setErrorSnackMessage(err);
});
}
}, [bucketName, loadingBucket, setBucketInfo, setErrorSnackMessage]);
useEffect(() => {
if (loadObjectData) {
api
@@ -682,7 +612,11 @@ const ObjectDetails = ({
<DownloadIcon />
</BoxIconButton>
)}
{displayDeleteObject && (
<SecureComponent
scopes={[IAM_SCOPES.S3_DELETE_OBJECT]}
resource={bucketName}
matchAll
>
<BoxIconButton
tooltip={"Delete Object"}
color="primary"
@@ -695,7 +629,7 @@ const ObjectDetails = ({
>
<DeleteIcon />
</BoxIconButton>
)}
</SecureComponent>
</Fragment>
}
/>
@@ -711,80 +645,105 @@ const ObjectDetails = ({
<h1 className={classes.sectionTitle}>Details</h1>
</div>
<br />
{(displayObjectLegalHold ||
displayObjectRetention ||
displayObjectTag) && (
<Grid item xs={12}>
<table width={"100%"}>
<tbody>
{displayObjectLegalHold && (
<tr>
<td className={classes.titleCol}>
Legal Hold:
</td>
<td className={classes.capitalizeFirst}>
{actualInfo.version_id &&
actualInfo.version_id !== "null" ? (
<Fragment>
{actualInfo.legal_hold_status
? actualInfo.legal_hold_status.toLowerCase()
: "Off"}
{displayEditObjectLegalHold && (
<IconButton
color="primary"
aria-label="legal-hold"
size="small"
className={classes.propertiesIcon}
onClick={() => {
setLegalholdOpen(true);
<Grid item xs={12}>
<table width={"100%"}>
<tbody>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_OBJECT_LEGAL_HOLD]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Legal Hold:</td>
<td className={classes.capitalizeFirst}>
{actualInfo.version_id &&
actualInfo.version_id !== "null" ? (
<Fragment>
{actualInfo.legal_hold_status
? actualInfo.legal_hold_status.toLowerCase()
: "Off"}
<SecureComponent
scopes={[
IAM_SCOPES.S3_PUT_OBJECT_LEGAL_HOLD,
]}
resource={bucketName}
matchAll
>
<IconButton
color="primary"
aria-label="legal-hold"
size="small"
className={classes.propertiesIcon}
onClick={() => {
setLegalholdOpen(true);
}}
>
<EditIcon />
</IconButton>
</SecureComponent>
</Fragment>
) : (
"Disabled"
)}
</td>
</tr>
</SecureComponent>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_OBJECT_RETENTION]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Retention:</td>
<td className={classes.capitalizeFirst}>
{actualInfo.retention_mode
? actualInfo.retention_mode.toLowerCase()
: "None"}
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_OBJECT_RETENTION]}
resource={bucketName}
matchAll
>
<IconButton
color="primary"
aria-label="retention"
size="small"
className={classes.propertiesIcon}
onClick={() => {
openRetentionModal();
}}
>
<EditIcon />
</IconButton>
</SecureComponent>
</td>
</tr>
</SecureComponent>
<SecureComponent
scopes={[IAM_SCOPES.S3_GET_OBJECT_TAGGING]}
resource={bucketName}
>
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
{tagKeys &&
tagKeys.map((tagKey, index) => {
const tag = get(
actualInfo,
`tags.${tagKey}`,
""
);
if (tag !== "") {
return (
<SecureComponent
scopes={[
IAM_SCOPES.S3_DELETE_OBJECT_TAGGING,
]}
resource={bucketName}
matchAll
errorProps={{
deleteIcon: null,
onDelete: null,
}}
>
<EditIcon />
</IconButton>
)}
</Fragment>
) : (
"Disabled"
)}
</td>
</tr>
)}
{displayObjectRetention && (
<tr>
<td className={classes.titleCol}>Retention:</td>
<td className={classes.capitalizeFirst}>
{actualInfo.retention_mode
? actualInfo.retention_mode.toLowerCase()
: "None"}
{displayEditObjectRetention && (
<IconButton
color="primary"
aria-label="retention"
size="small"
className={classes.propertiesIcon}
onClick={() => {
openRetentionModal();
}}
>
<EditIcon />
</IconButton>
)}
</td>
</tr>
)}
{displayObjectTag && (
<tr>
<td className={classes.titleCol}>Tags:</td>
<td>
{tagKeys &&
tagKeys.map((tagKey, index) => {
const tag = get(
actualInfo,
`tags.${tagKey}`,
""
);
if (tag !== "") {
return displayRemoveObjectTagging ? (
<Chip
key={`chip-${index}`}
className={classes.tag}
@@ -796,39 +755,35 @@ const ObjectDetails = ({
deleteTag(tagKey, tag);
}}
/>
) : (
<Chip
key={`chip-${index}`}
className={classes.tag}
size="small"
label={`${tagKey} : ${tag}`}
color="primary"
/>
);
}
return null;
})}
{displayEditObjectTagging && (
<Chip
className={classes.tag}
icon={<AddIcon />}
clickable
size="small"
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
)}
</td>
</tr>
)}
</tbody>
</table>
</Grid>
)}
</SecureComponent>
);
}
return null;
})}
<SecureComponent
scopes={[IAM_SCOPES.S3_PUT_OBJECT_TAGGING]}
resource={bucketName}
matchAll
>
<Chip
className={classes.tag}
icon={<AddIcon />}
clickable
size="small"
label="Add tag"
color="primary"
variant="outlined"
onClick={() => {
setTagModalOpen(true);
}}
/>
</SecureComponent>
</td>
</tr>
</SecureComponent>
</tbody>
</table>
</Grid>
<Grid item xs={12}>
<Grid item xs={12}>
<h2>Object Metadata</h2>

View File

@@ -25,7 +25,6 @@ export interface Bucket {
size?: number;
objects?: number;
rw_access?: RwAccess;
allowedActions?: string[];
manage: boolean;
details?: Details;
}
@@ -42,8 +41,6 @@ export interface Details {
export interface BucketInfo {
name: string;
access: string;
allowedActions?: string[];
manage?: boolean;
}
export interface BucketList {

View File

@@ -533,12 +533,21 @@ const TableWrapper = ({
const clickAction = (rowItem: any) => {
if (findView) {
const valueClick = findView.sendOnlyId ? rowItem[idField] : rowItem;
if (findView.to) {
let disabled = false;
if (findView.disableButtonFunction) {
if (findView.disableButtonFunction(valueClick)) {
disabled = true;
}
}
if (findView.to && !disabled) {
history.push(`${findView.to}/${valueClick}`);
return;
}
if (findView.onClick) {
if (findView.onClick && !disabled) {
findView.onClick(valueClick);
}
}

View File

@@ -28,10 +28,7 @@ const initialState: ConsoleState = {
pages: [],
features: [],
distributedMode: false,
policy: {
version: "",
statement: [],
},
permissions: {},
},
};

View File

@@ -14,15 +14,8 @@
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
export interface ISessionPolicyStatement {
action: string[];
condition: any;
effect: string;
resource: string[];
}
export interface ISessionPolicy {
version: string;
statement: ISessionPolicyStatement[];
export interface ISessionPermissions {
[key: string]: string[];
}
export interface ISessionResponse {
@@ -31,5 +24,5 @@ export interface ISessionResponse {
features: string[];
operator: boolean;
distributedMode: boolean;
policy: ISessionPolicy;
permissions: ISessionPermissions;
}

View File

@@ -52,6 +52,11 @@ const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
export type AppState = ReturnType<typeof globalReducer>;
export const store = createStore(
globalReducer,
composeEnhancers(applyMiddleware(thunk))
);
export default function configureStore() {
return createStore(globalReducer, composeEnhancers(applyMiddleware(thunk)));
return store;
}

View File

@@ -122,55 +122,3 @@ export type SystemActionTypes =
| SetModalSnackMessage
| SetModalErrorMessage
| SetDistributedSetup;
// S3 Actions
export const S3_LIST_BUCKET = "s3:ListBucket";
export const S3_GET_BUCKET_POLICY = "s3:GetBucketPolicy";
export const S3_PUT_BUCKET_POLICY = "s3:PutBucketPolicy";
export const S3_GET_OBJECT = "s3:GetObject";
export const S3_PUT_OBJECT = "s3:PutObject";
export const S3_GET_OBJECT_LEGAL_HOLD = "s3:GetObjectLegalHold";
export const S3_PUT_OBJECT_LEGAL_HOLD = "s3:PutObjectLegalHold";
export const S3_DELETE_OBJECT = "s3:DeleteObject";
export const S3_GET_BUCKET_VERSIONING = "s3:GetBucketVersioning";
export const S3_PUT_BUCKET_VERSIONING = "s3:PutBucketVersioning";
export const S3_GET_OBJECT_RETENTION = "s3:GetObjectRetention";
export const S3_PUT_OBJECT_RETENTION = "s3:PutObjectRetention";
export const S3_GET_OBJECT_TAGGING = "s3:GetObjectTagging";
export const S3_PUT_OBJECT_TAGGING = "s3:PutObjectTagging";
export const S3_DELETE_OBJECT_TAGGING = "s3:DeleteObjectTagging";
export const S3_GET_BUCKET_ENCRYPTION_CONFIGURATION =
"s3:GetEncryptionConfiguration";
export const S3_PUT_BUCKET_ENCRYPTION_CONFIGURATION =
"s3:PutEncryptionConfiguration";
export const S3_DELETE_BUCKET = "s3:DeleteBucket";
export const S3_FORCE_DELETE_BUCKET = "s3:ForceDeleteBucket";
export const S3_GET_BUCKET_NOTIFICATIONS = "s3:GetBucketNotification";
export const S3_LISTEN_BUCKET_NOTIFICATIONS = "s3:ListenBucketNotification";
export const S3_PUT_BUCKET_NOTIFICATIONS = "s3:PutBucketNotification";
export const S3_GET_REPLICATION_CONFIGURATION =
"s3:GetReplicationConfiguration";
export const S3_PUT_REPLICATION_CONFIGURATION =
"s3:PutReplicationConfiguration";
export const S3_GET_LIFECYCLE_CONFIGURATION = "s3:GetLifecycleConfiguration";
export const S3_PUT_LIFECYCLE_CONFIGURATION = "s3:PutLifecycleConfiguration";
export const S3_GET_BUCKET_OBJECT_LOCK_CONFIGURATION =
"s3:GetBucketObjectLockConfiguration";
export const S3_PUT_BUCKET_OBJECT_LOCK_CONFIGURATION =
"s3:PutBucketObjectLockConfiguration";
// Admin Actions
export const ADMIN_GET_POLICY = "admin:GetPolicy";
export const ADMIN_LIST_USERS = "admin:ListUsers";
export const ADMIN_LIST_USER_POLICIES = "admin:ListUserPolicies";
export const ADMIN_SERVER_INFO = "admin:ServerInfo";
export const ADMIN_GET_BUCKET_QUOTA = "admin:GetBucketQuota";
export const ADMIN_SET_BUCKET_QUOTA = "admin:SetBucketQuota";
export const ADMIN_LIST_TIERS = "admin:ListTier";
export const ADMIN_LIST_GROUPS = "admin:ListGroups";
export const S3_ALL_ACTIONS = "s3:*";
export const ADMIN_ALL_ACTIONS = "admin:*";

View File

@@ -1,44 +0,0 @@
// This file is part of MinIO Console Server
// Copyright (c) 2021 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 <http://www.gnu.org/licenses/>.
import { ADMIN_ALL_ACTIONS, S3_ALL_ACTIONS } from "../types";
// displayComponent receives a list of user permissions to perform on a specific resource, then compares those permissions against
// a list of required permissions and return true or false depending of the level of required access (match all permissions,
// match some of the permissions)
export const displayComponent = (
userPermissionsOnBucket: string[] | null | undefined,
requiredPermissions: string[],
matchAll?: boolean
) => {
if (!userPermissionsOnBucket) {
return false;
}
const s3All = userPermissionsOnBucket.includes(S3_ALL_ACTIONS);
const AdminAll = userPermissionsOnBucket.includes(ADMIN_ALL_ACTIONS);
const permissions = requiredPermissions.filter(function (n) {
return (
userPermissionsOnBucket.indexOf(n) !== -1 ||
(n.indexOf("s3:") !== -1 && s3All) ||
(n.indexOf("admin:") !== -1 && AdminAll)
);
});
return matchAll
? permissions.length === requiredPermissions.length
: permissions.length > 0;
};

View File

@@ -458,7 +458,7 @@ func listExternalBucketsResponse(params user_api.ListExternalBucketsParams) (*mo
// create a minioClient interface implementation
// defining the client to be used
remoteClient := AdminClient{Client: remoteAdmin}
buckets, err := getAccountBuckets(ctx, remoteClient, *params.Body.AccessKey)
buckets, err := getAccountBuckets(ctx, remoteClient)
if err != nil {
return nil, prepareError(err)
}

View File

@@ -3661,12 +3661,6 @@ func init() {
"access": {
"$ref": "#/definitions/bucketAccess"
},
"allowedActions": {
"type": "array",
"items": {
"type": "string"
}
},
"creation_date": {
"type": "string"
},
@@ -3709,9 +3703,6 @@ func init() {
}
}
},
"manage": {
"type": "boolean"
},
"name": {
"type": "string",
"minLength": 3
@@ -5204,9 +5195,14 @@ func init() {
"type": "string"
}
},
"policy": {
"permissions": {
"type": "object",
"$ref": "#/definitions/iamPolicy"
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"status": {
"type": "string",
@@ -9482,12 +9478,6 @@ func init() {
"access": {
"$ref": "#/definitions/bucketAccess"
},
"allowedActions": {
"type": "array",
"items": {
"type": "string"
}
},
"creation_date": {
"type": "string"
},
@@ -9530,9 +9520,6 @@ func init() {
}
}
},
"manage": {
"type": "boolean"
},
"name": {
"type": "string",
"minLength": 3
@@ -11025,9 +11012,14 @@ func init() {
"type": "string"
}
},
"policy": {
"permissions": {
"type": "object",
"$ref": "#/definitions/iamPolicy"
"additionalProperties": {
"type": "array",
"items": {
"type": "string"
}
}
},
"status": {
"type": "string",

View File

@@ -24,10 +24,6 @@ import (
"strings"
"time"
"github.com/minio/pkg/bucket/policy/condition"
"github.com/minio/console/pkg/acl"
"github.com/minio/mc/cmd"
"github.com/minio/mc/pkg/probe"
"github.com/minio/minio-go/v7"
@@ -290,25 +286,13 @@ func getBucketVersionedResponse(session *models.Principal, bucketName string) (*
}
// getAccountBuckets fetches a list of all buckets allowed to that particular client from MinIO Servers
func getAccountBuckets(ctx context.Context, client MinioAdmin, accessKey string) ([]*models.Bucket, error) {
func getAccountBuckets(ctx context.Context, client MinioAdmin) ([]*models.Bucket, error) {
info, err := client.AccountInfo(ctx)
if err != nil {
return []*models.Bucket{}, err
}
policyInfo, err := getAccountPolicy(ctx, client)
if err != nil {
return nil, err
}
var bucketInfos []*models.Bucket
for _, bucket := range info.Buckets {
var bucketAdminRole bool
conditionValues := map[string][]string{
condition.AWSUsername.Name(): {accessKey},
}
bucketActions := policyInfo.IsAllowedActions(bucket.Name, "", conditionValues)
bucketAdminRoleActions := bucketActions.Intersection(acl.BucketAdminRole)
bucketAdminRole = len(bucketAdminRoleActions) > 0
bucketElem := &models.Bucket{
CreationDate: bucket.Created.Format(time.RFC3339),
Details: &models.BucketDetails{
@@ -321,7 +305,6 @@ func getAccountBuckets(ctx context.Context, client MinioAdmin, accessKey string)
Name: swag.String(bucket.Name),
Objects: int64(bucket.Objects),
Size: int64(bucket.Size),
Manage: bucketAdminRole,
}
if bucket.Details != nil {
@@ -358,7 +341,7 @@ func getListBucketsResponse(session *models.Principal) (*models.ListBucketsRespo
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
buckets, err := getAccountBuckets(ctx, adminClient, session.AccountAccessKey)
buckets, err := getAccountBuckets(ctx, adminClient)
if err != nil {
return nil, prepareError(err)
}
@@ -493,21 +476,12 @@ func getBucketSetPolicyResponse(session *models.Principal, bucketName string, re
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
// set bucket access policy
if err := setBucketAccessPolicy(ctx, minioClient, bucketName, *req.Access); err != nil {
return nil, prepareError(err)
}
// get updated bucket details and return it
bucket, err := getBucketInfo(ctx, minioClient, adminClient, bucketName, session.AccountAccessKey)
bucket, err := getBucketInfo(ctx, minioClient, bucketName)
if err != nil {
return nil, prepareError(err)
}
@@ -566,29 +540,7 @@ func getDeleteBucketResponse(session *models.Principal, params user_api.DeleteBu
}
// getBucketInfo return bucket information including name, policy access, size and creation date
func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdmin, bucketName string, accountName string) (*models.Bucket, error) {
// Get Account Policy
policyInfo, err := getAccountPolicy(ctx, adminClient)
if err != nil {
return nil, err
}
var bucketAdminRole bool
// Retrieve list of allowed bucketActionsArray on the bucket
// TODO: Add all the possible variables
conditionValues := map[string][]string{
condition.AWSUsername.Name(): {accountName},
}
bucketActions := policyInfo.IsAllowedActions(bucketName, "", conditionValues)
// Check if one of these bucketActionsArray belongs to administrative bucketActionsArray
bucketAdminRoleActions := bucketActions.Intersection(acl.BucketAdminRole)
bucketAdminRole = len(bucketAdminRoleActions) > 0
var bucketActionsArray []string
for _, action := range bucketActions.ToSlice() {
bucketActionsArray = append(bucketActionsArray, string(action))
}
func getBucketInfo(ctx context.Context, client MinioClient, bucketName string) (*models.Bucket, error) {
var bucketAccess models.BucketAccess
policyStr, err := client.getBucketPolicy(context.Background(), bucketName)
if err != nil {
@@ -611,29 +563,21 @@ func getBucketInfo(ctx context.Context, client MinioClient, adminClient MinioAdm
bucketAccess = models.BucketAccessCUSTOM
}
bucketTags, err := client.GetBucketTagging(ctx, bucketName)
var bucket *models.Bucket
if err == nil && bucketTags != nil {
bucket = &models.Bucket{
Name: &bucketName,
Access: &bucketAccess,
CreationDate: "", // to be implemented
Size: 0, // to be implemented
AllowedActions: bucketActionsArray,
Manage: bucketAdminRole,
Details: &models.BucketDetails{Tags: bucketTags.ToMap()},
}
} else {
bucket = &models.Bucket{
Name: &bucketName,
Access: &bucketAccess,
CreationDate: "", // to be implemented
Size: 0, // to be implemented
AllowedActions: bucketActionsArray,
Manage: bucketAdminRole,
Details: &models.BucketDetails{},
}
if err != nil {
// we can tolerate this error
LogError("error getting bucket tags: %v", err)
}
return bucket, nil
bucketDetails := &models.BucketDetails{}
if bucketTags != nil {
bucketDetails.Tags = bucketTags.ToMap()
}
return &models.Bucket{
Name: &bucketName,
Access: &bucketAccess,
CreationDate: "", // to be implemented
Size: 0, // to be implemented
Details: bucketDetails,
}, nil
}
// getBucketInfoResponse calls getBucketInfo() to get the bucket's info
@@ -647,16 +591,7 @@ func getBucketInfoResponse(session *models.Principal, params user_api.BucketInfo
// create a minioClient interface implementation
// defining the client to be used
minioClient := minioClient{client: mClient}
mAdmin, err := NewMinioAdminClient(session)
if err != nil {
return nil, prepareError(err)
}
// create a minioClient interface implementation
// defining the client to be used
adminClient := AdminClient{Client: mAdmin}
bucket, err := getBucketInfo(ctx, minioClient, adminClient, params.Name, session.AccountAccessKey)
bucket, err := getBucketInfo(ctx, minioClient, params.Name)
if err != nil {
return nil, prepareError(err)
}

View File

@@ -180,7 +180,7 @@ func TestListBucket(t *testing.T) {
// get list buckets response this response should have Name, CreationDate, Size and Access
// as part of of each bucket
function := "getaAcountUsageInfo()"
bucketList, err := getAccountBuckets(ctx, adminClient, "")
bucketList, err := getAccountBuckets(ctx, adminClient)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
@@ -197,7 +197,7 @@ func TestListBucket(t *testing.T) {
minioAccountInfoMock = func(ctx context.Context) (madmin.AccountInfo, error) {
return madmin.AccountInfo{}, errors.New("error")
}
_, err = getAccountBuckets(ctx, adminClient, "")
_, err = getAccountBuckets(ctx, adminClient)
if assert.Error(err) {
assert.Equal("error", err.Error())
}
@@ -257,7 +257,6 @@ func TestBucketInfo(t *testing.T) {
// mock minIO client
minClient := minioClientMock{}
ctx := context.Background()
adminClient := adminClientMock{}
function := "getBucketInfo()"
// Test-1: getBucketInfo() get a bucket with PRIVATE access
@@ -308,7 +307,7 @@ func TestBucketInfo(t *testing.T) {
return mockBucketList, nil
}
bucketInfo, err := getBucketInfo(ctx, minClient, adminClient, bucketToSet, "user1")
bucketInfo, err := getBucketInfo(ctx, minClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
@@ -330,7 +329,7 @@ func TestBucketInfo(t *testing.T) {
CreationDate: "", // to be implemented
Size: 0, // to be implemented
}
bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet, "bucket1")
bucketInfo, err = getBucketInfo(ctx, minClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
@@ -352,7 +351,7 @@ func TestBucketInfo(t *testing.T) {
CreationDate: "", // to be implemented
Size: 0, // to be implemented
}
bucketInfo, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet, "bucket1")
bucketInfo, err = getBucketInfo(ctx, minClient, bucketToSet)
if err != nil {
t.Errorf("Failed on %s:, error occurred: %s", function, err.Error())
}
@@ -373,7 +372,7 @@ func TestBucketInfo(t *testing.T) {
CreationDate: "", // to be implemented
Size: 0, // to be implemented
}
_, err = getBucketInfo(ctx, minClient, adminClient, bucketToSet, "bucket1")
_, err = getBucketInfo(ctx, minClient, bucketToSet)
if assert.Error(err) {
assert.Equal("invalid character 'p' looking for beginning of value", err.Error())
}

View File

@@ -23,6 +23,10 @@ import (
"net/url"
"time"
"github.com/minio/pkg/bucket/policy/condition"
minioIAMPolicy "github.com/minio/pkg/iam/policy"
"github.com/go-openapi/runtime/middleware"
"github.com/minio/console/models"
"github.com/minio/console/pkg/acl"
@@ -91,14 +95,82 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
}
// by default every user starts with an empty array of available actions
// by default every user starts with an empty array of available val
// therefore we would have access only to pages that doesn't require any privilege
// ie: service-account page
var actions []string
// if a policy is assigned to this user we parse the actions from there
// if a policy is assigned to this user we parse the val from there
if policy != nil {
actions = acl.GetActionsStringFromPolicy(policy)
}
// This actions will be global, meaning has to be attached to all resources
conditionValues := map[string][]string{
condition.AWSUsername.Name(): {session.AccountAccessKey},
}
defaultActions := policy.IsAllowedActions("", "", conditionValues)
consoleResourceName := "console-ui"
permissions := map[string]minioIAMPolicy.ActionSet{
consoleResourceName: defaultActions,
}
deniedActions := map[string]minioIAMPolicy.ActionSet{}
for _, statement := range policy.Statements {
for _, resource := range statement.Resources.ToSlice() {
resourceName := resource.String()
statementActions := statement.Actions.ToSlice()
if statement.Effect == "Allow" {
// check if val are denied before adding them to the map
var allowedActions []minioIAMPolicy.Action
if dActions, ok := deniedActions[resourceName]; ok {
for _, action := range statementActions {
if len(dActions.Intersection(minioIAMPolicy.NewActionSet(action))) == 0 {
// It's ok to allow this action
allowedActions = append(allowedActions, action)
}
}
} else {
allowedActions = statementActions
}
// Add validated actions
if resourceActions, ok := permissions[resourceName]; ok {
mergedActions := append(resourceActions.ToSlice(), allowedActions...)
permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
} else {
mergedActions := append(defaultActions.ToSlice(), allowedActions...)
permissions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
}
} else {
// Add new banned actions to the map
if resourceActions, ok := deniedActions[resourceName]; ok {
mergedActions := append(resourceActions.ToSlice(), statementActions...)
deniedActions[resourceName] = minioIAMPolicy.NewActionSet(mergedActions...)
} else {
deniedActions[resourceName] = statement.Actions
}
// Remove existing val from key if necessary
if currentResourceActions, ok := permissions[resourceName]; ok {
var newAllowedActions []minioIAMPolicy.Action
for _, action := range currentResourceActions.ToSlice() {
if len(deniedActions[resourceName].Intersection(minioIAMPolicy.NewActionSet(action))) == 0 {
// It's ok to allow this action
newAllowedActions = append(newAllowedActions, action)
}
}
permissions[resourceName] = minioIAMPolicy.NewActionSet(newAllowedActions...)
}
}
}
}
resourcePermissions := map[string][]string{}
for key, val := range permissions {
var resourceActions []string
for _, action := range val.ToSlice() {
resourceActions = append(resourceActions, string(action))
}
resourcePermissions[key] = resourceActions
}
rawPolicy, err := json.Marshal(policy)
if err != nil {
return nil, prepareError(err, errorGenericInvalidSession)
@@ -114,7 +186,7 @@ func getSessionResponse(session *models.Principal) (*models.SessionResponse, *mo
Status: models.SessionResponseStatusOk,
Operator: false,
DistributedMode: isErasureMode(),
Policy: sessionPolicy,
Permissions: resourcePermissions,
}
return sessionResp, nil
}

View File

@@ -2363,8 +2363,6 @@ definitions:
required:
- name
properties:
manage:
type: boolean
name:
type: string
minLength: 3
@@ -2411,10 +2409,6 @@ definitions:
- hard
creation_date:
type: string
allowedActions:
type: array
items:
type: string
bucketEncryptionRequest:
type: object
@@ -3128,9 +3122,12 @@ definitions:
type: boolean
distributedMode:
type: boolean
policy:
permissions:
type: object
$ref: "#/definitions/iamPolicy"
additionalProperties:
type: array
items:
type: string
widgetResult:
type: object