Multiple fixes in resources browsing (#2019)

- Fixed subpaths search & browsing
- Fixed a regression in object browser for object details panel reset
- Added a test for these cases

Signed-off-by: Benjamin Perez <benjamin@bexsoft.net>
This commit is contained in:
Alex
2022-05-20 20:30:39 -05:00
committed by GitHub
parent d876bebf28
commit 41e0fce068
17 changed files with 352 additions and 26 deletions

View File

@@ -808,6 +808,75 @@ jobs:
with:
args: '"chrome:headless" portal-ui/tests/permissions-6/ --skip-js-errors'
all-permissions-7:
name: Permissions Tests Part 7
needs:
- lint-job
- no-warnings-and-make-assets
- reuse-golang-dependencies
- vulnerable-dependencies-checks
- semgrep-static-code-analysis
runs-on: ${{ matrix.os }}
strategy:
matrix:
go-version: [ 1.17.x ]
os: [ ubuntu-latest ]
steps:
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
uses: actions/setup-go@v2
with:
go-version: ${{ matrix.go-version }}
id: go
- uses: actions/setup-node@v2
with:
node-version: '16'
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
id: yarn-cache
name: Yarn Cache
with:
path: |
${{ steps.yarn-cache-dir-path.outputs.dir }}
./portal-ui/node_modules/
./portal-ui/build/
key: ${{ runner.os }}-yarn-${{ hashFiles('./portal-ui/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v2
id: assets-cache
name: Assets Cache
with:
path: |
./portal-ui/build/
key: ${{ runner.os }}-assets-${{ github.run_id }}
restore-keys: |
${{ runner.os }}-assets-
- uses: actions/cache@v2
name: Go Mod Cache
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ github.run_id }}
- name: Build Console on ${{ matrix.os }}
env:
GO111MODULE: on
GOOS: linux
run: |
make console
- name: Start Console, front-end app and initialize users/policies
run: |
(./console server) & (make initialize-permissions)
- name: Run TestCafe Tests
timeout-minutes: 5
uses: DevExpress/testcafe-action@latest
with:
args: '"chrome:headless" portal-ui/tests/permissions-7/ --skip-js-errors'
all-operator-tests:
name: Operator UI Tests
needs:

View File

@@ -40,7 +40,7 @@
"kbar": "^0.1.0-beta.27",
"local-storage-fallback": "^4.1.1",
"lodash": "^4.17.21",
"minio": "^7.0.26",
"minio": "^7.0.28",
"moment": "^2.29.2",
"react": "^17.0.2",
"react-chartjs-2": "^2.9.0",

View File

@@ -496,7 +496,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
dispatch(setObjectDetailsView(false));
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
dispatch(
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
);
@@ -1113,7 +1113,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
elements = elements.filter((element) => element !== value);
}
setSelectedObjects(elements);
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
return elements;
};
@@ -1140,7 +1140,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
}
const selectAllItems = () => {
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
if (selectedObjects.length === payload.length) {
setSelectedObjects([]);
@@ -1171,7 +1171,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
}
const onClosePanel = (forceRefresh: boolean) => {
dispatch(setSelectedObjectView(""));
dispatch(setSelectedObjectView(null));
dispatch(setVersionsModeEnabled({ status: false }));
if (detailsOpen && selectedInternalPaths !== null) {
// We change URL to be the contained folder

View File

@@ -71,7 +71,6 @@ import { displayFileIconName } from "./utils";
import TagsModal from "../ObjectDetails/TagsModal";
import InspectObject from "./InspectObject";
import Loader from "../../../../Common/Loader/Loader";
import { setErrorSnackMessage } from "../../../../../../systemSlice";
import {
makeid,
storeCallForObjectWithID,
@@ -239,7 +238,7 @@ const ObjectDetailPanel = ({
dispatch(setLoadingObjectInfo(false));
})
.catch((error: ErrorResponseHandler) => {
dispatch(setErrorSnackMessage(error));
console.error("Error loading object details", error);
dispatch(setLoadingObjectInfo(false));
});
}

View File

@@ -194,15 +194,15 @@ export const permissionItems = (
// We split ARN & get the last item to check the URL
const splitARN = permissionElement.resource.split(":");
const url = splitARN.pop() || "";
const urlARN = splitARN.pop() || "";
// We split the paths of the URL & compare against current location to see if there are more items to include. In case current level is a wildcard or is the last one, we omit this validation
const splitURL = url.split("/");
const splitURLARN = urlARN.split("/");
// splitURL has more items than bucket name, we can continue validating
if (splitURL.length > 1) {
splitURL.every((currentElementInPath, index) => {
if (splitURLARN.length > 1) {
splitURLARN.every((currentElementInPath, index) => {
// It is a wildcard element. We can stor the verification as value should be included (?)
if (currentElementInPath === "*") {
return false;
@@ -240,17 +240,25 @@ export const permissionItems = (
if (prefixItem !== "") {
const splitItems = prefixItem.split("/");
let pathToRouteElements: string[] = [];
splitItems.every((splitElement, index) => {
if (!splitElement.includes("*")) {
if (splitElement !== splitURL[index]) {
if (!splitElement.includes("*") && splitElement !== "") {
if (splitElement !== splitCurrentPath[index]) {
returnElements.push({
name: `${splitElement}/`,
name: `${pathToRouteElements.join("/")}${
pathToRouteElements.length > 0 ? "/" : ""
}${splitElement}/`,
size: 0,
last_modified: new Date(),
version_id: "",
});
return false;
}
if (splitElement !== "") {
pathToRouteElements.push(splitElement);
}
return true;
}
return false;

View File

@@ -195,7 +195,7 @@ export const objectBrowserSlice = createSlice({
? state.selectedInternalPaths
: null;
},
setSelectedObjectView: (state, action: PayloadAction<string>) => {
setSelectedObjectView: (state, action: PayloadAction<string | null>) => {
state.selectedInternalPaths = action.payload;
},
setSimplePathHandler: (state, action: PayloadAction<string>) => {

View File

@@ -1 +1 @@
1652244779
1653008276

View File

@@ -0,0 +1,134 @@
// This file is part of MinIO Console Server
// Copyright (c) 2022 MinIO, Inc.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
import * as roles from "../utils/roles";
import { Selector } from "testcafe";
import * as functions from "../utils/functions";
import {
cleanUpNamedBucketAndUploads,
namedTestBucketBrowseButtonFor,
} from "../utils/functions";
fixture("Test resources policy").page("http://localhost:9090/");
const bucket1 = "testcondition";
const test1BucketBrowseButton = namedTestBucketBrowseButtonFor(bucket1);
export const file = Selector(".ReactVirtualized__Table__rowColumn").withText(
"test.txt"
);
test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucket1);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/thirdlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
})(
"User can only see permitted files in last path as expected",
async (t) => {
await t
.useRole(roles.conditions2)
.navigateTo(`http://localhost:9090/buckets`)
.click(test1BucketBrowseButton)
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
)
.expect(file.exists)
.notOk()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText(
"secondlevel"
)
)
.expect(file.exists)
.notOk();
}
)
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
});
test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucket1);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
await functions.uploadNamedObjectToBucket(
t,
bucket1,
"firstlevel/secondlevel/thirdlevel/test.txt",
"portal-ui/tests/uploads/test.txt"
);
})("User can browse from first level as policy has wildcard", async (t) => {
await t
.useRole(roles.conditions1)
.navigateTo(`http://localhost:9090/buckets`)
.click(test1BucketBrowseButton)
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
)
.expect(file.exists)
.ok()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("secondlevel")
)
.expect(file.exists)
.ok()
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("thirdlevel")
)
.expect(file.exists)
.ok();
})
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
});

View File

@@ -0,0 +1,28 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "read-only",
"Effect": "Allow",
"Action": ["s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::testcondition"]
},
{
"Sid": "read",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::testcondition"],
"Condition": {
"StringLike": {
"s3:prefix": ["firstlevel/*"]
}
}
}
]
}

View File

@@ -0,0 +1,28 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "read-only",
"Effect": "Allow",
"Action": ["s3:GetBucketLocation"],
"Resource": ["arn:aws:s3:::testcondition"]
},
{
"Sid": "read",
"Effect": "Allow",
"Action": ["s3:GetObject"],
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
},
{
"Sid": "statement2",
"Effect": "Allow",
"Action": ["s3:ListBucket"],
"Resource": ["arn:aws:s3:::testcondition"],
"Condition": {
"StringLike": {
"s3:prefix": ["firstlevel/secondlevel/thirdlevel/*"]
}
}
}
]
}

View File

@@ -28,6 +28,8 @@ remove_users() {
mc admin user remove minio inspect-allowed-$TIMESTAMP
mc admin user remove minio inspect-not-allowed-$TIMESTAMP
mc admin user remove minio prefix-policy-ui-crash-$TIMESTAMP
mc admin user remove minio conditions-$TIMESTAMP
mc admin user remove minio conditions-2-$TIMESTAMP
}
remove_policies() {
@@ -50,6 +52,8 @@ remove_policies() {
mc admin policy remove minio inspect-allowed-$TIMESTAMP
mc admin policy remove minio inspect-not-allowed-$TIMESTAMPmc
mc admin policy remove minio fix-prefix-policy-ui-crash-$TIMESTAMP
mc admin policy remove minio conditions-policy-$TIMESTAMP
mc admin policy remove minio conditions-policy-2-$TIMESTAMP
}
__init__() {

7
portal-ui/tests/scripts/common.sh Normal file → Executable file
View File

@@ -46,6 +46,8 @@ create_policies() {
mc admin policy add minio inspect-not-allowed-$TIMESTAMP portal-ui/tests/policies/inspect-not-allowed.json
mc admin policy add minio fix-prefix-policy-ui-crash-$TIMESTAMP portal-ui/tests/policies/fix-prefix-policy-ui-crash.json
mc admin policy add minio delete-object-with-prefix-$TIMESTAMP portal-ui/tests/policies/deleteObjectWithPrefix.json
mc admin policy add minio conditions-policy-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy.json
mc admin policy add minio conditions-policy-2-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy2.json
}
create_users() {
@@ -73,11 +75,14 @@ create_users() {
mc admin user add minio inspect-not-allowed-$TIMESTAMP insnotallowed1234
mc admin user add minio prefix-policy-ui-crash-$TIMESTAMP poluicrashfix1234
mc admin user add minio delete-object-with-prefix-$TIMESTAMP deleteobjectwithprefix1234
mc admin user add minio conditions-$TIMESTAMP conditions1234
mc admin user add minio conditions-2-$TIMESTAMP conditions1234
}
create_buckets() {
mc mb minio/testcafe && mc cp ./portal-ui/tests/uploads/test.txt minio/testcafe/write/test.txt
mc mb minio/test && mc cp ./portal-ui/tests/uploads/test.txt minio/test/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/test/digitalinsights/xref_cust_guid_actd-v1.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/test/digitalinsights/test.txt
mc mb minio/testcondition && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/test.txt && mc cp ./portal-ui/tests2/uploads/test.txt minio/testcondition/firstlevel/xref_cust_guid_actd-v1.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/secondlevel/test.txt && mc cp ./portal-ui/tests/uploads/test.txt minio/testcondition/firstlevel/secondlevel/thirdlevel/test.txt
}
assign_policies() {
@@ -104,4 +109,6 @@ assign_policies() {
mc admin policy set minio inspect-allowed-$TIMESTAMP user=inspect-allowed-$TIMESTAMP
mc admin policy set minio inspect-not-allowed-$TIMESTAMP user=inspect-not-allowed-$TIMESTAMP
mc admin policy set minio delete-object-with-prefix-$TIMESTAMP user=delete-object-with-prefix-$TIMESTAMP
mc admin policy set minio conditions-policy-$TIMESTAMP user=conditions-$TIMESTAMP
mc admin policy set minio conditions-policy-2-$TIMESTAMP user=conditions-2-$TIMESTAMP
}

0
portal-ui/tests/scripts/initialize-env.sh Normal file → Executable file
View File

View File

@@ -36,6 +36,8 @@ remove_users() {
mc admin user remove minio inspect-not-allowed-"$TIMESTAMP"
mc admin user remove minio prefix-policy-ui-crash-"$TIMESTAMP"
mc admin user remove minio delete-object-with-prefix-"$TIMESTAMP"
mc admin user remove minio conditions-"$TIMESTAMP"
mc admin user remove minio conditions-2-"$TIMESTAMP"
}
remove_policies() {
@@ -59,11 +61,14 @@ remove_policies() {
mc admin policy remove minio inspect-not-allowed-"$TIMESTAMP"
mc admin policy remove minio fix-prefix-policy-ui-crash-"$TIMESTAMP"
mc admin policy remove minio delete-object-with-prefix-"$TIMESTAMP"
mc admin policy remove conditions-policy-"$TIMESTAMP"
mc admin policy remove conditions-policy-2-"$TIMESTAMP"
}
remove_buckets() {
mc rm minio/testcafe/write/test.txt && mc rm minio/testcafe
mc rm minio/test/test.txt && mc rm minio/test/digitalinsights/xref_cust_guid_actd-v1.txt && mc rm minio/test/digitalinsights/test.txt && mc rm minio/test
mc rm minio/testcondition/test.txt && mc rm minio/testcondition/firstlevel/xref_cust_guid_actd-v1.txt && mc rm minio/testcondition/firstlevel/test.txt && mc rm minio/testcondition/firstlevel/secondlevel/test.txt && mc rm minio/testcondition/firstlevel/secondlevel/thirdlevel/test.txt && mc rm minio/testcondition
}
cleanup() {

View File

@@ -33,14 +33,28 @@ export const setUpNamedBucket = (t, name) => {
accessKey: "minioadmin",
secretKey: "minioadmin",
});
return new Promise((resolve, reject) => {
minioClient.makeBucket(name, "us-east-1").then(resolve).catch(resolve);
minioClient.makeBucket(name, "us-east-1", (err) => {
if (err) {
console.log(err);
}
resolve("done: " + err);
});
});
};
export const uploadObjectToBucket = (t, modifier, objectName, objectPath) => {
const bucketName = `${constants.TEST_BUCKET_NAME}-${modifier}`;
return uploadNamedObjectToBucket(t, bucketName, objectName, objectPath);
};
export const uploadNamedObjectToBucket = (
t,
modifier,
objectName,
objectPath
) => {
const bucketName = modifier;
const minioClient = new Minio.Client({
endPoint: "localhost",
port: 9000,
@@ -49,10 +63,12 @@ export const uploadObjectToBucket = (t, modifier, objectName, objectPath) => {
secretKey: "minioadmin",
});
return new Promise((resolve, reject) => {
minioClient
.fPutObject(bucketName, objectName, objectPath, {})
.then(resolve)
.catch(resolve);
minioClient.fPutObject(bucketName, objectName, objectPath, {}, (err) => {
if (err) {
console.log(err);
}
resolve("done");
});
});
};

View File

@@ -250,3 +250,25 @@ export const deleteObjectWithPrefixOnly = Role(
},
{ preserveUrl: true }
);
export const conditions1 = Role(
loginUrl,
async (t) => {
await t
.typeText("#accessKey", "conditions-" + unixTimestamp)
.typeText("#secretKey", "conditions1234")
.click(submitButton);
},
{ preserveUrl: true }
);
export const conditions2 = Role(
loginUrl,
async (t) => {
await t
.typeText("#accessKey", "conditions-2-" + unixTimestamp)
.typeText("#secretKey", "conditions1234")
.click(submitButton);
},
{ preserveUrl: true }
);

View File

@@ -3775,6 +3775,11 @@ bser@2.1.1:
dependencies:
node-int64 "^0.4.0"
buffer-crc32@^0.2.13:
version "0.2.13"
resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242"
integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==
buffer-equal-constant-time@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819"
@@ -8061,14 +8066,15 @@ minimist@^1.1.0, minimist@^1.1.1, minimist@^1.2.0, minimist@^1.2.6:
resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44"
integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==
minio@^7.0.26:
version "7.0.26"
resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.26.tgz#83bcda813ed486a8bc7f028efa135d49b02d9bc8"
integrity sha512-knutnEZZMIUB/Xln6psVDrqObFKXDcF9m4IfFIX+zgDHYg3AlcF88DY1wdgg7bUkf+uU8iHkzP2q5CXAhia73w==
minio@^7.0.28:
version "7.0.28"
resolved "https://registry.yarnpkg.com/minio/-/minio-7.0.28.tgz#1f527ded42df457833e0b203ea51316d6d3d58a4"
integrity sha512-4Oua0R73oCxxmxhh2NiXDJo4Md159I/mdG8ybu6351leMQoB2Sy8S4HmgG6CxuPlEJ0h9M8/WyaI2CARDeeDTQ==
dependencies:
async "^3.1.0"
block-stream2 "^2.0.0"
browser-or-node "^1.3.0"
buffer-crc32 "^0.2.13"
crypto-browserify "^3.12.0"
es6-error "^4.1.1"
fast-xml-parser "^3.17.5"