Skip to content

Commit 41e0fce

Browse files
authored
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>
1 parent d876beb commit 41e0fce

File tree

17 files changed

+352
-26
lines changed

17 files changed

+352
-26
lines changed

.github/workflows/jobs.yaml

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -808,6 +808,75 @@ jobs:
808808
with:
809809
args: '"chrome:headless" portal-ui/tests/permissions-6/ --skip-js-errors'
810810

811+
all-permissions-7:
812+
name: Permissions Tests Part 7
813+
needs:
814+
- lint-job
815+
- no-warnings-and-make-assets
816+
- reuse-golang-dependencies
817+
- vulnerable-dependencies-checks
818+
- semgrep-static-code-analysis
819+
runs-on: ${{ matrix.os }}
820+
strategy:
821+
matrix:
822+
go-version: [ 1.17.x ]
823+
os: [ ubuntu-latest ]
824+
steps:
825+
- name: Set up Go ${{ matrix.go-version }} on ${{ matrix.os }}
826+
uses: actions/setup-go@v2
827+
with:
828+
go-version: ${{ matrix.go-version }}
829+
id: go
830+
- uses: actions/setup-node@v2
831+
with:
832+
node-version: '16'
833+
- name: Check out code into the Go module directory
834+
uses: actions/checkout@v2
835+
- name: Get yarn cache directory path
836+
id: yarn-cache-dir-path
837+
run: echo "::set-output name=dir::$(yarn cache dir)"
838+
- uses: actions/cache@v2
839+
id: yarn-cache
840+
name: Yarn Cache
841+
with:
842+
path: |
843+
${{ steps.yarn-cache-dir-path.outputs.dir }}
844+
./portal-ui/node_modules/
845+
./portal-ui/build/
846+
key: ${{ runner.os }}-yarn-${{ hashFiles('./portal-ui/yarn.lock') }}
847+
restore-keys: |
848+
${{ runner.os }}-yarn-
849+
- uses: actions/cache@v2
850+
id: assets-cache
851+
name: Assets Cache
852+
with:
853+
path: |
854+
./portal-ui/build/
855+
key: ${{ runner.os }}-assets-${{ github.run_id }}
856+
restore-keys: |
857+
${{ runner.os }}-assets-
858+
- uses: actions/cache@v2
859+
name: Go Mod Cache
860+
with:
861+
path: |
862+
~/.cache/go-build
863+
~/go/pkg/mod
864+
key: ${{ runner.os }}-go-${{ github.run_id }}
865+
- name: Build Console on ${{ matrix.os }}
866+
env:
867+
GO111MODULE: on
868+
GOOS: linux
869+
run: |
870+
make console
871+
- name: Start Console, front-end app and initialize users/policies
872+
run: |
873+
(./console server) & (make initialize-permissions)
874+
- name: Run TestCafe Tests
875+
timeout-minutes: 5
876+
uses: DevExpress/testcafe-action@latest
877+
with:
878+
args: '"chrome:headless" portal-ui/tests/permissions-7/ --skip-js-errors'
879+
811880
all-operator-tests:
812881
name: Operator UI Tests
813882
needs:

portal-ui/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"kbar": "^0.1.0-beta.27",
4141
"local-storage-fallback": "^4.1.1",
4242
"lodash": "^4.17.21",
43-
"minio": "^7.0.26",
43+
"minio": "^7.0.28",
4444
"moment": "^2.29.2",
4545
"react": "^17.0.2",
4646
"react-chartjs-2": "^2.9.0",

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ListObjects.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -496,7 +496,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
496496

497497
if (decodedIPaths.endsWith("/") || decodedIPaths === "") {
498498
dispatch(setObjectDetailsView(false));
499-
dispatch(setSelectedObjectView(""));
499+
dispatch(setSelectedObjectView(null));
500500
dispatch(
501501
setSimplePathHandler(decodedIPaths === "" ? "/" : decodedIPaths)
502502
);
@@ -1113,7 +1113,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
11131113
elements = elements.filter((element) => element !== value);
11141114
}
11151115
setSelectedObjects(elements);
1116-
dispatch(setSelectedObjectView(""));
1116+
dispatch(setSelectedObjectView(null));
11171117

11181118
return elements;
11191119
};
@@ -1140,7 +1140,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
11401140
}
11411141

11421142
const selectAllItems = () => {
1143-
dispatch(setSelectedObjectView(""));
1143+
dispatch(setSelectedObjectView(null));
11441144

11451145
if (selectedObjects.length === payload.length) {
11461146
setSelectedObjects([]);
@@ -1171,7 +1171,7 @@ const ListObjects = ({ match, history }: IListObjectsProps) => {
11711171
}
11721172

11731173
const onClosePanel = (forceRefresh: boolean) => {
1174-
dispatch(setSelectedObjectView(""));
1174+
dispatch(setSelectedObjectView(null));
11751175
dispatch(setVersionsModeEnabled({ status: false }));
11761176
if (detailsOpen && selectedInternalPaths !== null) {
11771177
// We change URL to be the contained folder

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ListObjects/ObjectDetailPanel.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import { displayFileIconName } from "./utils";
7171
import TagsModal from "../ObjectDetails/TagsModal";
7272
import InspectObject from "./InspectObject";
7373
import Loader from "../../../../Common/Loader/Loader";
74-
import { setErrorSnackMessage } from "../../../../../../systemSlice";
7574
import {
7675
makeid,
7776
storeCallForObjectWithID,
@@ -239,7 +238,7 @@ const ObjectDetailPanel = ({
239238
dispatch(setLoadingObjectInfo(false));
240239
})
241240
.catch((error: ErrorResponseHandler) => {
242-
dispatch(setErrorSnackMessage(error));
241+
console.error("Error loading object details", error);
243242
dispatch(setLoadingObjectInfo(false));
244243
});
245244
}

portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/utils.ts

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,15 @@ export const permissionItems = (
194194

195195
// We split ARN & get the last item to check the URL
196196
const splitARN = permissionElement.resource.split(":");
197-
const url = splitARN.pop() || "";
197+
const urlARN = splitARN.pop() || "";
198198

199199
// 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
200200

201-
const splitURL = url.split("/");
201+
const splitURLARN = urlARN.split("/");
202202

203203
// splitURL has more items than bucket name, we can continue validating
204-
if (splitURL.length > 1) {
205-
splitURL.every((currentElementInPath, index) => {
204+
if (splitURLARN.length > 1) {
205+
splitURLARN.every((currentElementInPath, index) => {
206206
// It is a wildcard element. We can stor the verification as value should be included (?)
207207
if (currentElementInPath === "*") {
208208
return false;
@@ -240,17 +240,25 @@ export const permissionItems = (
240240
if (prefixItem !== "") {
241241
const splitItems = prefixItem.split("/");
242242

243+
let pathToRouteElements: string[] = [];
244+
243245
splitItems.every((splitElement, index) => {
244-
if (!splitElement.includes("*")) {
245-
if (splitElement !== splitURL[index]) {
246+
if (!splitElement.includes("*") && splitElement !== "") {
247+
if (splitElement !== splitCurrentPath[index]) {
246248
returnElements.push({
247-
name: `${splitElement}/`,
249+
name: `${pathToRouteElements.join("/")}${
250+
pathToRouteElements.length > 0 ? "/" : ""
251+
}${splitElement}/`,
248252
size: 0,
249253
last_modified: new Date(),
250254
version_id: "",
251255
});
252256
return false;
253257
}
258+
if (splitElement !== "") {
259+
pathToRouteElements.push(splitElement);
260+
}
261+
254262
return true;
255263
}
256264
return false;

portal-ui/src/screens/Console/ObjectBrowser/objectBrowserSlice.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,7 @@ export const objectBrowserSlice = createSlice({
195195
? state.selectedInternalPaths
196196
: null;
197197
},
198-
setSelectedObjectView: (state, action: PayloadAction<string>) => {
198+
setSelectedObjectView: (state, action: PayloadAction<string | null>) => {
199199
state.selectedInternalPaths = action.payload;
200200
},
201201
setSimplePathHandler: (state, action: PayloadAction<string>) => {
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
1652244779
1+
1653008276
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
// This file is part of MinIO Console Server
2+
// Copyright (c) 2022 MinIO, Inc.
3+
//
4+
// This program is free software: you can redistribute it and/or modify
5+
// it under the terms of the GNU Affero General Public License as published by
6+
// the Free Software Foundation, either version 3 of the License, or
7+
// (at your option) any later version.
8+
//
9+
// This program is distributed in the hope that it will be useful,
10+
// but WITHOUT ANY WARRANTY; without even the implied warranty of
11+
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+
// GNU Affero General Public License for more details.
13+
//
14+
// You should have received a copy of the GNU Affero General Public License
15+
// along with this program. If not, see <http://www.gnu.org/licenses/>.
16+
17+
import * as roles from "../utils/roles";
18+
import { Selector } from "testcafe";
19+
import * as functions from "../utils/functions";
20+
import {
21+
cleanUpNamedBucketAndUploads,
22+
namedTestBucketBrowseButtonFor,
23+
} from "../utils/functions";
24+
25+
fixture("Test resources policy").page("http://localhost:9090/");
26+
27+
const bucket1 = "testcondition";
28+
const test1BucketBrowseButton = namedTestBucketBrowseButtonFor(bucket1);
29+
export const file = Selector(".ReactVirtualized__Table__rowColumn").withText(
30+
"test.txt"
31+
);
32+
test
33+
.before(async (t) => {
34+
await functions.setUpNamedBucket(t, bucket1);
35+
await functions.uploadNamedObjectToBucket(
36+
t,
37+
bucket1,
38+
"test.txt",
39+
"portal-ui/tests/uploads/test.txt"
40+
);
41+
await functions.uploadNamedObjectToBucket(
42+
t,
43+
bucket1,
44+
"firstlevel/test.txt",
45+
"portal-ui/tests/uploads/test.txt"
46+
);
47+
await functions.uploadNamedObjectToBucket(
48+
t,
49+
bucket1,
50+
"firstlevel/secondlevel/test.txt",
51+
"portal-ui/tests/uploads/test.txt"
52+
);
53+
await functions.uploadNamedObjectToBucket(
54+
t,
55+
bucket1,
56+
"firstlevel/secondlevel/thirdlevel/test.txt",
57+
"portal-ui/tests/uploads/test.txt"
58+
);
59+
})(
60+
"User can only see permitted files in last path as expected",
61+
async (t) => {
62+
await t
63+
.useRole(roles.conditions2)
64+
.navigateTo(`http://localhost:9090/buckets`)
65+
.click(test1BucketBrowseButton)
66+
.click(
67+
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
68+
)
69+
.expect(file.exists)
70+
.notOk()
71+
.click(
72+
Selector(".ReactVirtualized__Table__rowColumn").withText(
73+
"secondlevel"
74+
)
75+
)
76+
.expect(file.exists)
77+
.notOk();
78+
}
79+
)
80+
.after(async (t) => {
81+
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
82+
});
83+
84+
test
85+
.before(async (t) => {
86+
await functions.setUpNamedBucket(t, bucket1);
87+
await functions.uploadNamedObjectToBucket(
88+
t,
89+
bucket1,
90+
"test.txt",
91+
"portal-ui/tests/uploads/test.txt"
92+
);
93+
await functions.uploadNamedObjectToBucket(
94+
t,
95+
bucket1,
96+
"firstlevel/test.txt",
97+
"portal-ui/tests/uploads/test.txt"
98+
);
99+
await functions.uploadNamedObjectToBucket(
100+
t,
101+
bucket1,
102+
"firstlevel/secondlevel/test.txt",
103+
"portal-ui/tests/uploads/test.txt"
104+
);
105+
await functions.uploadNamedObjectToBucket(
106+
t,
107+
bucket1,
108+
"firstlevel/secondlevel/thirdlevel/test.txt",
109+
"portal-ui/tests/uploads/test.txt"
110+
);
111+
})("User can browse from first level as policy has wildcard", async (t) => {
112+
await t
113+
.useRole(roles.conditions1)
114+
.navigateTo(`http://localhost:9090/buckets`)
115+
.click(test1BucketBrowseButton)
116+
.click(
117+
Selector(".ReactVirtualized__Table__rowColumn").withText("firstlevel")
118+
)
119+
.expect(file.exists)
120+
.ok()
121+
.click(
122+
Selector(".ReactVirtualized__Table__rowColumn").withText("secondlevel")
123+
)
124+
.expect(file.exists)
125+
.ok()
126+
.click(
127+
Selector(".ReactVirtualized__Table__rowColumn").withText("thirdlevel")
128+
)
129+
.expect(file.exists)
130+
.ok();
131+
})
132+
.after(async (t) => {
133+
await functions.cleanUpNamedBucketAndUploads(t, bucket1);
134+
});
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [
4+
{
5+
"Sid": "read-only",
6+
"Effect": "Allow",
7+
"Action": ["s3:GetBucketLocation"],
8+
"Resource": ["arn:aws:s3:::testcondition"]
9+
},
10+
{
11+
"Sid": "read",
12+
"Effect": "Allow",
13+
"Action": ["s3:GetObject"],
14+
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
15+
},
16+
{
17+
"Sid": "statement2",
18+
"Effect": "Allow",
19+
"Action": ["s3:ListBucket"],
20+
"Resource": ["arn:aws:s3:::testcondition"],
21+
"Condition": {
22+
"StringLike": {
23+
"s3:prefix": ["firstlevel/*"]
24+
}
25+
}
26+
}
27+
]
28+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
{
2+
"Version": "2012-10-17",
3+
"Statement": [
4+
{
5+
"Sid": "read-only",
6+
"Effect": "Allow",
7+
"Action": ["s3:GetBucketLocation"],
8+
"Resource": ["arn:aws:s3:::testcondition"]
9+
},
10+
{
11+
"Sid": "read",
12+
"Effect": "Allow",
13+
"Action": ["s3:GetObject"],
14+
"Resource": ["arn:aws:s3:::testcondition/firstlevel/*"]
15+
},
16+
{
17+
"Sid": "statement2",
18+
"Effect": "Allow",
19+
"Action": ["s3:ListBucket"],
20+
"Resource": ["arn:aws:s3:::testcondition"],
21+
"Condition": {
22+
"StringLike": {
23+
"s3:prefix": ["firstlevel/secondlevel/thirdlevel/*"]
24+
}
25+
}
26+
}
27+
]
28+
}

0 commit comments

Comments
 (0)