Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
// 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 { BucketObject } from "api/consoleApi";
import { ApiError, BucketObject } from "api/consoleApi";
import { IFileInfo } from "../ObjectDetails/types";

export interface BucketObjectItem {
Expand All @@ -38,13 +38,18 @@ export interface WebsocketRequest {

export interface WebsocketResponse {
request_id: number;
error?: string;
error?: WebsocketErrorResponse;
request_end?: boolean;
data?: ObjectResponse[];
prefix?: string;
bucketName?: string;
}

export interface WebsocketErrorResponse {
Code: number;
APIError: ApiError;
}

export interface ObjectResponse {
name: string;
last_modified: string;
Expand Down
37 changes: 29 additions & 8 deletions portal-ui/src/websockets/objectBrowserWSMiddleware.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export const objectBrowserWSMiddleware = (
};

objectsWS.onmessage = (message) => {
const basicErrorMessage = {
errorMessage: "An error occurred",
detailedMessage:
"An unknown error occurred. Please refer to Console logs to get more information.",
};

const response: WebsocketResponse = JSON.parse(
message.data.toString(),
);
Expand All @@ -94,13 +100,10 @@ export const objectBrowserWSMiddleware = (
return;
}

if (
response.error ===
"The Access Key Id you provided does not exist in our records."
) {
// Session expired.
if (response.error?.Code === 401) {
// Session expired. We reload this page
window.location.reload();
} else if (response.error === "Access Denied.") {
} else if (response.error?.Code === 403) {
const internalPathsPrefix = response.prefix;
let pathPrefix = "";

Expand All @@ -119,10 +122,15 @@ export const objectBrowserWSMiddleware = (
);

if (!permitItems || permitItems.length === 0) {
const errorMsg = response.error.APIError;

dispatch(
setErrorSnackMessage({
errorMessage: response.error,
detailedError: response.error,
errorMessage:
errorMsg.message || basicErrorMessage.errorMessage,
detailedError:
errorMsg.detailedMessage ||
basicErrorMessage.detailedMessage,
}),
);
} else {
Expand All @@ -131,6 +139,19 @@ export const objectBrowserWSMiddleware = (
}

return;
} else if (response.error) {
const errorMsg = response.error.APIError;

dispatch(setRequestInProgress(false));
dispatch(
setErrorSnackMessage({
errorMessage:
errorMsg.message || basicErrorMessage.errorMessage,
detailedError:
errorMsg.detailedMessage ||
basicErrorMessage.detailedMessage,
}),
);
}

// This indicates final messages is received.
Expand Down
116 changes: 116 additions & 0 deletions portal-ui/tests/permissions-7/errorsVisibleOB.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// This file is part of MinIO Console Server
// Copyright (c) 2023 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 { namedTestBucketBrowseButtonFor } from "../utils/functions";

fixture("Test error visibility in Object Browser Navigation").page(
"http://localhost:9090/",
);

const bucketName = "my-company";
const bucketName2 = "my-company2";
const bucketBrowseButton = namedTestBucketBrowseButtonFor(bucketName);
const bucketBrowseButton2 = namedTestBucketBrowseButtonFor(bucketName2);
export const file = Selector(".ReactVirtualized__Table__rowColumn").withText(
"test.txt",
);
export const deniedError = Selector(".message-text").withText("Access Denied.");

test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucketName);
await functions.uploadNamedObjectToBucket(
t,
bucketName,
"test.txt",
"portal-ui/tests/uploads/test.txt",
);
await functions.uploadNamedObjectToBucket(
t,
bucketName,
"home/UserY/test.txt",
"portal-ui/tests/uploads/test.txt",
);
await functions.uploadNamedObjectToBucket(
t,
bucketName,
"home/UserX/test.txt",
"portal-ui/tests/uploads/test.txt",
);
})(
"Error Notification is shown in Object Browser when no privileges are set",
async (t) => {
await t
.useRole(roles.conditions3)
.navigateTo(`http://localhost:9090/browser`)
.click(bucketBrowseButton)
.click(Selector(".ReactVirtualized__Table__rowColumn").withText("home"))
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("UserX"),
)
.expect(deniedError.exists)
.ok();
},
)
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucketName);
});

test
.before(async (t) => {
await functions.setUpNamedBucket(t, bucketName2);
await functions.setVersionedBucket(t, bucketName2);
await functions.uploadNamedObjectToBucket(
t,
bucketName2,
"test.txt",
"portal-ui/tests/uploads/test.txt",
);
await functions.uploadNamedObjectToBucket(
t,
bucketName2,
"home/UserY/test.txt",
"portal-ui/tests/uploads/test.txt",
);
await functions.uploadNamedObjectToBucket(
t,
bucketName2,
"home/UserX/test.txt",
"portal-ui/tests/uploads/test.txt",
);
})(
"Error Notification is shown in Object Browser with Rewind request set",
async (t) => {
await t
.useRole(roles.conditions4)
.navigateTo(`http://localhost:9090/browser`)
.click(bucketBrowseButton2)
.click(Selector("label").withText("Show deleted objects"))
.wait(1500)
.click(Selector(".ReactVirtualized__Table__rowColumn").withText("home"))
.click(
Selector(".ReactVirtualized__Table__rowColumn").withText("UserX"),
)
.expect(deniedError.exists)
.ok();
},
)
.after(async (t) => {
await functions.cleanUpNamedBucketAndUploads(t, bucketName2);
});
44 changes: 44 additions & 0 deletions portal-ui/tests/policies/conditionsPolicy4.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "AllowUserToSeeBucketListInTheConsole",
"Action": [
"s3:ListAllMyBuckets",
"s3:GetBucketLocation",
"s3:GetBucketVersioning"
],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::*"]
},
{
"Sid": "AllowRootAndHomeListingOfCompanyBucket",
"Action": ["s3:ListBucket", "s3:List*"],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::my-company2"],
"Condition": {
"StringEquals": {
"s3:prefix": ["", "home/", "home/User"],
"s3:delimiter": ["/"]
}
}
},
{
"Sid": "AllowListingOfUserFolder",
"Action": ["s3:ListBucket", "s3:List*"],
"Effect": "Allow",
"Resource": ["arn:aws:s3:::my-company2"],
"Condition": {
"StringLike": {
"s3:prefix": ["home/User/*"]
}
}
},
{
"Sid": "AllowAllS3ActionsInUserFolder",
"Effect": "Allow",
"Action": ["s3:*"],
"Resource": ["arn:aws:s3:::my-company2/home/User/*"]
}
]
}
2 changes: 2 additions & 0 deletions portal-ui/tests/scripts/cleanup-env.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ remove_users() {
mc admin user remove minio conditions-$TIMESTAMP
mc admin user remove minio conditions-2-$TIMESTAMP
mc admin user remove minio conditions-3-$TIMESTAMP
mc admin user remove minio conditions-4-$TIMESTAMP
}

remove_policies() {
Expand All @@ -56,6 +57,7 @@ remove_policies() {
mc admin policy remove minio conditions-policy-$TIMESTAMP
mc admin policy remove minio conditions-policy-2-$TIMESTAMP
mc admin policy remove minio conditions-policy-3-$TIMESTAMP
mc admin policy remove minio conditions-policy-4-$TIMESTAMP
}

__init__() {
Expand Down
3 changes: 3 additions & 0 deletions portal-ui/tests/scripts/common.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ create_policies() {
mc admin policy create minio conditions-policy-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy.json
mc admin policy create minio conditions-policy-2-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy2.json
mc admin policy create minio conditions-policy-3-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy3.json
mc admin policy create minio conditions-policy-4-$TIMESTAMP portal-ui/tests/policies/conditionsPolicy4.json
}

create_users() {
Expand Down Expand Up @@ -79,6 +80,7 @@ create_users() {
mc admin user add minio conditions-$TIMESTAMP conditions1234
mc admin user add minio conditions-2-$TIMESTAMP conditions1234
mc admin user add minio conditions-3-$TIMESTAMP conditions1234
mc admin user add minio conditions-4-$TIMESTAMP conditions1234
}

create_buckets() {
Expand Down Expand Up @@ -114,4 +116,5 @@ assign_policies() {
mc admin policy attach minio conditions-policy-$TIMESTAMP --user conditions-$TIMESTAMP
mc admin policy attach minio conditions-policy-2-$TIMESTAMP --user conditions-2-$TIMESTAMP
mc admin policy attach minio conditions-policy-3-$TIMESTAMP --user conditions-3-$TIMESTAMP
mc admin policy attach minio conditions-policy-4-$TIMESTAMP --user conditions-4-$TIMESTAMP
}
2 changes: 2 additions & 0 deletions portal-ui/tests/scripts/permissions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ remove_users() {
mc admin user remove minio conditions-"$TIMESTAMP"
mc admin user remove minio conditions-2-"$TIMESTAMP"
mc admin user remove minio conditions-3-"$TIMESTAMP"
mc admin user remove minio conditions-4-"$TIMESTAMP"
}

remove_policies() {
Expand All @@ -65,6 +66,7 @@ remove_policies() {
mc admin policy remove conditions-policy-"$TIMESTAMP"
mc admin policy remove conditions-policy-2-"$TIMESTAMP"
mc admin policy remove conditions-policy-3-"$TIMESTAMP"
mc admin policy remove conditions-policy-4-"$TIMESTAMP"
}

remove_buckets() {
Expand Down
11 changes: 11 additions & 0 deletions portal-ui/tests/utils/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -283,3 +283,14 @@ export const conditions3 = Role(
},
{ preserveUrl: true },
);

export const conditions4 = Role(
loginUrl,
async (t) => {
await t
.typeText("#accessKey", "conditions-4-" + unixTimestamp)
.typeText("#secretKey", "conditions1234")
.click(submitButton);
},
{ preserveUrl: true },
);
2 changes: 1 addition & 1 deletion restapi/admin_objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ type ObjectsRequest struct {

type WSResponse struct {
RequestID int64 `json:"request_id,omitempty"`
Error string `json:"error,omitempty"`
Error *CodedAPIError `json:"error,omitempty"`
RequestEnd bool `json:"request_end,omitempty"`
Prefix string `json:"prefix,omitempty"`
BucketName string `json:"bucketName,omitempty"`
Expand Down
4 changes: 4 additions & 0 deletions restapi/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,10 @@ func ErrorWithContext(ctx context.Context, err ...interface{}) *CodedAPIError {
errorCode = 401
errorMessage = ErrInvalidLogin.Error()
}
if strings.Contains(strings.ToLower(err1.Error()), ErrAccessDenied.Error()) {
errorCode = 403
errorMessage = err1.Error()
}
// If the last error is ErrInvalidLogin, this is a login failure
if errors.Is(lastError, ErrInvalidLogin) {
errorCode = 401
Expand Down
Loading