diff --git a/integration/user_api_bucket_test.go b/integration/user_api_bucket_test.go
index 37976eab41..54444849a9 100644
--- a/integration/user_api_bucket_test.go
+++ b/integration/user_api_bucket_test.go
@@ -461,12 +461,24 @@ func ListObjects(bucketName, prefix, withVersions string) (*http.Response, error
return response, err
}
-func SharesAnObjectOnAUrl(bucketName, prefix, versionID, expires string) (*http.Response, error) {
- // Helper function to share an object on a url
+func SharesAnObjectOnAUrl(bucketName, prefix, versionID, expires, accessKey, secretKey string) (*http.Response, error) {
+ // Helper function to share an object on an url
+
+ requestDataAdd := map[string]interface{}{
+ "prefix": prefix,
+ "version_id": versionID,
+ "expires": expires,
+ "access_key": accessKey,
+ "secret_key": secretKey,
+ }
+
+ requestDataJSON, _ := json.Marshal(requestDataAdd)
+ requestDataBody := bytes.NewReader(requestDataJSON)
+
request, err := http.NewRequest(
- "GET",
- "http://localhost:9090/api/v1/buckets/"+bucketName+"/objects/share?prefix="+prefix+"&version_id="+versionID+"&expires="+expires,
- nil,
+ "POST",
+ "http://localhost:9090/api/v1/buckets/"+bucketName+"/objects/share",
+ requestDataBody,
)
if err != nil {
log.Println(err)
@@ -743,6 +755,39 @@ func PutObjectsLegalholdStatus(bucketName, prefix, status, versionID string) (*h
return response, err
}
+func PostServiceAccountCredentials(accessKey, secretKey, policy string) (*http.Response, error) {
+ /*
+ Helper function to create a service account
+ POST: {{baseUrl}}/service-account-credentials
+ {
+ "accessKey":"testsa",
+ "secretKey":"secretsa",
+ "policy":""
+ }
+ */
+ requestDataAdd := map[string]interface{}{
+ "accessKey": accessKey,
+ "secretKey": secretKey,
+ "policy": policy,
+ }
+ requestDataJSON, _ := json.Marshal(requestDataAdd)
+ requestDataBody := bytes.NewReader(requestDataJSON)
+
+ request, err := http.NewRequest("POST",
+ "http://localhost:9090/api/v1/service-account-credentials",
+ requestDataBody)
+ if err != nil {
+ log.Println(err)
+ }
+ request.Header.Add("Cookie", fmt.Sprintf("token=%s", token))
+ request.Header.Add("Content-Type", "application/json")
+ client := &http.Client{
+ Timeout: 2 * time.Second,
+ }
+ response, err := client.Do(request)
+ return response, err
+}
+
func TestPutObjectsLegalholdStatus(t *testing.T) {
// Variables
assert := assert.New(t)
@@ -1514,6 +1559,8 @@ func TestShareObjectOnURL(t *testing.T) {
tags := make(map[string]string)
tags["tag"] = "testputobjecttagbucketonetagone"
versionID := "null"
+ accessKey := "testaccesskey"
+ secretKey := "secretAccessKey"
// 1. Create the bucket
if !setupBucket(bucketName, false, false, nil, nil, assert, 200) {
@@ -1534,6 +1581,21 @@ func TestShareObjectOnURL(t *testing.T) {
inspectHTTPResponse(uploadResponse),
)
}
+ // 2. Create Access Key
+ accKeyRsp, createError := PostServiceAccountCredentials(accessKey, secretKey, "")
+
+ if createError != nil {
+ log.Println(createError)
+ return
+ }
+
+ if accKeyRsp != nil {
+ assert.Equal(
+ 201,
+ accKeyRsp.StatusCode,
+ inspectHTTPResponse(accKeyRsp),
+ )
+ }
type args struct {
prefix string
@@ -1561,7 +1623,7 @@ func TestShareObjectOnURL(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// 3. Share the object on a URL
- shareResponse, shareError := SharesAnObjectOnAUrl(bucketName, tt.args.prefix, versionID, "604800s")
+ shareResponse, shareError := SharesAnObjectOnAUrl(bucketName, tt.args.prefix, versionID, "604800s", accessKey, secretKey)
assert.Nil(shareError)
if shareError != nil {
log.Println(shareError)
diff --git a/models/share_request.go b/models/share_request.go
new file mode 100644
index 0000000000..b12a7f041e
--- /dev/null
+++ b/models/share_request.go
@@ -0,0 +1,142 @@
+// Code generated by go-swagger; DO NOT EDIT.
+
+// 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 .
+//
+
+package models
+
+// This file was generated by the swagger tool.
+// Editing this file might prove futile when you re-run the swagger generate command
+
+import (
+ "context"
+
+ "github.com/go-openapi/errors"
+ "github.com/go-openapi/strfmt"
+ "github.com/go-openapi/swag"
+ "github.com/go-openapi/validate"
+)
+
+// ShareRequest share request
+//
+// swagger:model shareRequest
+type ShareRequest struct {
+
+ // access key
+ // Required: true
+ AccessKey *string `json:"access_key"`
+
+ // expires
+ Expires string `json:"expires,omitempty"`
+
+ // prefix
+ // Required: true
+ Prefix *string `json:"prefix"`
+
+ // secret key
+ // Required: true
+ SecretKey *string `json:"secret_key"`
+
+ // version id
+ // Required: true
+ VersionID *string `json:"version_id"`
+}
+
+// Validate validates this share request
+func (m *ShareRequest) Validate(formats strfmt.Registry) error {
+ var res []error
+
+ if err := m.validateAccessKey(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validatePrefix(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateSecretKey(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if err := m.validateVersionID(formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) > 0 {
+ return errors.CompositeValidationError(res...)
+ }
+ return nil
+}
+
+func (m *ShareRequest) validateAccessKey(formats strfmt.Registry) error {
+
+ if err := validate.Required("access_key", "body", m.AccessKey); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *ShareRequest) validatePrefix(formats strfmt.Registry) error {
+
+ if err := validate.Required("prefix", "body", m.Prefix); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *ShareRequest) validateSecretKey(formats strfmt.Registry) error {
+
+ if err := validate.Required("secret_key", "body", m.SecretKey); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+func (m *ShareRequest) validateVersionID(formats strfmt.Registry) error {
+
+ if err := validate.Required("version_id", "body", m.VersionID); err != nil {
+ return err
+ }
+
+ return nil
+}
+
+// ContextValidate validates this share request based on context it is used
+func (m *ShareRequest) ContextValidate(ctx context.Context, formats strfmt.Registry) error {
+ return nil
+}
+
+// MarshalBinary interface implementation
+func (m *ShareRequest) MarshalBinary() ([]byte, error) {
+ if m == nil {
+ return nil, nil
+ }
+ return swag.WriteJSON(m)
+}
+
+// UnmarshalBinary interface implementation
+func (m *ShareRequest) UnmarshalBinary(b []byte) error {
+ var res ShareRequest
+ if err := swag.ReadJSON(b, &res); err != nil {
+ return err
+ }
+ *m = res
+ return nil
+}
diff --git a/portal-ui/package.json b/portal-ui/package.json
index 08669ae6c0..8b4e3ec8a1 100644
--- a/portal-ui/package.json
+++ b/portal-ui/package.json
@@ -66,7 +66,7 @@
},
"proxy": "http://localhost:9090/",
"devDependencies": {
- "@playwright/test": "^1.32.3",
+ "@playwright/test": "^1.34.0",
"@types/lodash": "^4.14.194",
"@types/luxon": "^3.3.0",
"@types/minio": "^7.0.18",
@@ -85,6 +85,7 @@
"@types/websocket": "^1.0.0",
"babel-plugin-istanbul": "^6.1.1",
"customize-cra": "^1.0.0",
+ "minio": "^7.0.33",
"nyc": "^15.1.0",
"playwright": "^1.31.3",
"prettier": "2.8.8",
@@ -92,8 +93,7 @@
"react-app-rewired": "^2.2.1",
"react-scripts": "5.0.1",
"testcafe": "^2.5.0",
- "typescript": "^4.4.3",
- "minio": "^7.0.33"
+ "typescript": "^4.4.3"
},
"resolutions": {
"nth-check": "^2.0.1",
diff --git a/portal-ui/src/api/consoleApi.ts b/portal-ui/src/api/consoleApi.ts
index 390bd9db75..cc4896ef0a 100644
--- a/portal-ui/src/api/consoleApi.ts
+++ b/portal-ui/src/api/consoleApi.ts
@@ -1494,6 +1494,14 @@ export interface LdapPolicyEntity {
groups?: string[];
}
+export interface ShareRequest {
+ prefix: string;
+ version_id: string;
+ expires?: string;
+ access_key: string;
+ secret_key: string;
+}
+
export type QueryParamsType = Record;
export type ResponseFormat = keyof Omit;
@@ -2171,23 +2179,20 @@ export class Api<
* @tags Object
* @name ShareObject
* @summary Shares an Object on a url
- * @request GET:/buckets/{bucket_name}/objects/share
+ * @request POST:/buckets/{bucket_name}/objects/share
* @secure
*/
shareObject: (
bucketName: string,
- query: {
- prefix: string;
- version_id: string;
- expires?: string;
- },
+ body: ShareRequest,
params: RequestParams = {}
) =>
this.request({
path: `/buckets/${bucketName}/objects/share`,
- method: "GET",
- query: query,
+ method: "POST",
+ body: body,
secure: true,
+ type: ContentType.Json,
format: "json",
...params,
}),
diff --git a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx
index 99c97c55a8..1361e14db5 100644
--- a/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx
+++ b/portal-ui/src/screens/Console/Buckets/ListBuckets/Objects/ObjectDetails/ShareFile.tsx
@@ -17,11 +17,20 @@
import React, { Fragment, useEffect, useState } from "react";
import { useSelector } from "react-redux";
import { Theme } from "@mui/material/styles";
-import { Button, CopyIcon, ReadBox, ShareIcon } from "mds";
+import {
+ Button,
+ CopyIcon,
+ FormLayout,
+ Grid,
+ InputBox,
+ RadioGroup,
+ ReadBox,
+ Select,
+ ShareIcon,
+} from "mds";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import CopyToClipboard from "react-copy-to-clipboard";
-import Grid from "@mui/material/Grid";
import LinearProgress from "@mui/material/LinearProgress";
import {
formFieldStyles,
@@ -36,10 +45,13 @@ import DaysSelector from "../../../../Common/FormComponents/DaysSelector/DaysSel
import { encodeURLString } from "../../../../../../common/utils";
import {
selDistSet,
+ setErrorSnackMessage,
setModalErrorSnackMessage,
setModalSnackMessage,
} from "../../../../../../systemSlice";
import { useAppDispatch } from "../../../../../../store";
+import { DateTime } from "luxon";
+import { stringSort } from "../../../../../../utils/sortFunctions";
const styles = (theme: Theme) =>
createStyles({
@@ -85,11 +97,17 @@ const ShareFile = ({
const dispatch = useAppDispatch();
const distributedSetup = useSelector(selDistSet);
const [shareURL, setShareURL] = useState("");
- const [isLoadingVersion, setIsLoadingVersion] = useState(true);
- const [isLoadingFile, setIsLoadingFile] = useState(false);
+ const [isLoadingURL, setIsLoadingURL] = useState(false);
+ const [isLoadingAccessKeys, setLoadingAccessKeys] = useState(true);
const [selectedDate, setSelectedDate] = useState("");
const [dateValid, setDateValid] = useState(true);
const [versionID, setVersionID] = useState("null");
+ const [displayURL, setDisplayURL] = useState(false);
+ const [accessKeys, setAccessKeys] = useState([]);
+ const [selectedAccessKey, setSelectedAccessKey] = useState("");
+ const [secretKey, setSecretKey] = useState("");
+ const [authType, setAuthType] = useState("acc-list");
+ const [otherAK, setOtherAK] = useState("");
const initialDate = new Date();
@@ -134,20 +152,19 @@ const ShareFile = ({
dispatch(setModalErrorSnackMessage(error));
});
- setIsLoadingVersion(false);
+ setIsLoadingURL(false);
return;
}
setVersionID("null");
- setIsLoadingVersion(false);
+ setIsLoadingURL(false);
return;
}
setVersionID(dataObject.version_id || "null");
- setIsLoadingVersion(false);
+ setIsLoadingURL(false);
}, [bucketName, dataObject, distributedSetup, dispatch]);
useEffect(() => {
- if (dateValid && !isLoadingVersion) {
- setIsLoadingFile(true);
+ if (dateValid && isLoadingURL) {
setShareURL("");
const slDate = new Date(`${selectedDate}`);
@@ -157,28 +174,33 @@ const ShareFile = ({
(slDate.getTime() - currDate.getTime()) / 1000
);
+ const accKey = authType === "acc-list" ? selectedAccessKey : otherAK;
+
if (diffDate > 0) {
api
- .invoke(
- "GET",
- `/api/v1/buckets/${bucketName}/objects/share?prefix=${encodeURLString(
- dataObject.name
- )}&version_id=${versionID}${
- selectedDate !== "" ? `&expires=${diffDate}s` : ""
- }`
- )
+ .invoke("POST", `/api/v1/buckets/${bucketName}/objects/share`, {
+ prefix: encodeURLString(dataObject.name),
+ version_id: versionID,
+ expires: selectedDate !== "" ? `${diffDate}s` : "",
+ access_key: accKey,
+ secret_key: secretKey,
+ })
.then((res: string) => {
setShareURL(res);
- setIsLoadingFile(false);
+ setIsLoadingURL(false);
+ setDisplayURL(true);
})
.catch((error: ErrorResponseHandler) => {
dispatch(setModalErrorSnackMessage(error));
setShareURL("");
- setIsLoadingFile(false);
+ setIsLoadingURL(false);
+ setDisplayURL(false);
});
}
}
}, [
+ secretKey,
+ selectedAccessKey,
dataObject,
selectedDate,
bucketName,
@@ -186,80 +208,205 @@ const ShareFile = ({
setShareURL,
dispatch,
distributedSetup,
- isLoadingVersion,
versionID,
+ isLoadingURL,
+ authType,
+ otherAK,
]);
+ useEffect(() => {
+ if (isLoadingAccessKeys) {
+ const userLoggedIn = localStorage.getItem("userLoggedIn");
+
+ api
+ .invoke(
+ "GET",
+ `/api/v1/user/${encodeURLString(userLoggedIn)}/service-accounts`
+ )
+ .then((res: string[]) => {
+ if (res.length === 0) {
+ setAuthType("acc-other");
+ }
+
+ const serviceAccounts = res
+ .sort(stringSort)
+ .map((element) => ({ value: element, label: element }));
+
+ setLoadingAccessKeys(false);
+ setAccessKeys(serviceAccounts);
+ setSelectedAccessKey(serviceAccounts[0].value);
+ })
+ .catch((err: ErrorResponseHandler) => {
+ dispatch(setErrorSnackMessage(err));
+ setLoadingAccessKeys(false);
+ });
+ }
+ }, [isLoadingAccessKeys, dispatch]);
+
+ const generateLink = () => {
+ setIsLoadingURL(true);
+ };
+
+ const generateAnotherLink = () => {
+ setIsLoadingURL(false);
+ setDisplayURL(false);
+ setSelectedDate("");
+ setShareURL("");
+ };
+
return (
-
- }
- modalOpen={open}
- onClose={() => {
- closeModalAndRefresh();
- }}
- >
- {isLoadingVersion && (
-
-
+ }
+ modalOpen={open}
+ onClose={() => {
+ closeModalAndRefresh();
+ }}
+ >
+ {displayURL ? (
+
+
+ This is a temporary URL with integrated access credentials for
+ sharing {dataObject.name} until{" "}
+
+ {DateTime.fromISO(selectedDate).toFormat(
+ "ccc, LLL dd yyyy HH:mm (ZZZZ)"
+ )}
+
+
+
+ This temporary URL will expiry after this time.
- )}
- {!isLoadingVersion && (
-
-
- This is a temporary URL with integrated access credentials for
- sharing objects valid for up to 7 days.
-
-
- The temporary URL expires after the configured time limit.
-
+
+
+
+
+
+
+
+
+ ) : (
+
+
+ To generate a temporary URL, please provide a set of credentials,
+ this link can ve valid up to 7 days.
-
-
+
+
+ {accessKeys.length > 0 && (
+ {
+ setAuthType(e.target.value);
+ }}
+ currentValue={authType}
+ />
+ )}
+ {authType === "acc-other" ? (
+ {
+ setOtherAK(e.target.value);
+ }}
+ label={"Access Key"}
+ />
+ ) : (
+
+
+
+
+
+ {isLoadingURL && (
+
+
-
+
-
- )}
-
-
+ Generate Link
+
+
+
+ )}
+
);
};
diff --git a/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx b/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
index 93a8c03b07..e25f2cee55 100644
--- a/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
+++ b/portal-ui/src/screens/Console/Common/FormComponents/DaysSelector/DaysSelector.tsx
@@ -20,7 +20,7 @@ import { Theme } from "@mui/material/styles";
import createStyles from "@mui/styles/createStyles";
import withStyles from "@mui/styles/withStyles";
import { fieldBasic, tooltipHelper } from "../common/styleLibrary";
-import { LinkIcon, InputLabel, InputBox, Grid } from "mds";
+import { Grid, InputBox, InputLabel, LinkIcon } from "mds";
interface IDaysSelector {
classes: any;
@@ -177,6 +177,10 @@ const DaysSelector = ({
valid = false;
}
+ if (selectedDays <= 0 && selectedHours <= 0 && selectedMinutes <= 0) {
+ valid = false;
+ }
+
setValidDate(valid);
}, [
dateSelected,
@@ -203,9 +207,7 @@ const DaysSelector = ({
-
- {label}
-
+ {label}
diff --git a/portal-ui/src/screens/Console/Common/MainError/MainError.tsx b/portal-ui/src/screens/Console/Common/MainError/MainError.tsx
index 33ec2db56c..94f8f6909a 100644
--- a/portal-ui/src/screens/Console/Common/MainError/MainError.tsx
+++ b/portal-ui/src/screens/Console/Common/MainError/MainError.tsx
@@ -21,7 +21,10 @@ import { AppState, useAppDispatch } from "../../../../store";
import { Box } from "@mui/material";
import { AlertCloseIcon } from "mds";
import { Portal } from "@mui/base";
-import { setErrorSnackMessage } from "../../../../systemSlice";
+import {
+ setErrorSnackMessage,
+ setModalSnackMessage,
+} from "../../../../systemSlice";
interface IMainErrorProps {
isModal?: boolean;
@@ -51,6 +54,7 @@ const MainError = ({ isModal = false }: IMainErrorProps) => {
useEffect(() => {
if (!displayErrorMsg) {
dispatch(setErrorSnackMessage({ detailedError: "", errorMessage: "" }));
+ dispatch(setModalSnackMessage(""));
clearInterval(timerI);
}
}, [dispatch, displayErrorMsg]);
diff --git a/portal-ui/yarn.lock b/portal-ui/yarn.lock
index acc0d2910e..253d3a479e 100644
--- a/portal-ui/yarn.lock
+++ b/portal-ui/yarn.lock
@@ -1879,13 +1879,13 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
-"@playwright/test@^1.32.3":
- version "1.32.3"
- resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.32.3.tgz#75be8346d4ef289896835e1d2a86fdbe3d9be92a"
- integrity sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==
+"@playwright/test@^1.34.0":
+ version "1.34.0"
+ resolved "https://registry.yarnpkg.com/@playwright/test/-/test-1.34.0.tgz#1f3359523c3fd7460c83fe83c8152751a9e21f49"
+ integrity sha512-GIALJVODOIrMflLV54H3Cow635OfrTwOu24ZTDyKC66uchtFX2NcCRq83cLdakMjZKYK78lODNLQSYBj2OgaTw==
dependencies:
"@types/node" "*"
- playwright-core "1.32.3"
+ playwright-core "1.34.0"
optionalDependencies:
fsevents "2.3.2"
@@ -9159,6 +9159,11 @@ playwright-core@1.32.3:
resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.32.3.tgz#e6dc7db0b49e9b6c0b8073c4a2d789a96f519c48"
integrity sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==
+playwright-core@1.34.0:
+ version "1.34.0"
+ resolved "https://registry.yarnpkg.com/playwright-core/-/playwright-core-1.34.0.tgz#6a8f05c657400677591ed82b6749ef7e120a152d"
+ integrity sha512-fMUY1+iR6kYbJF/EsOOqzBA99ZHXbw9sYPNjwA4X/oV0hVF/1aGlWYBGPVUEqxBkGANDKMziYoOdKGU5DIP5Gg==
+
playwright@^1.31.3:
version "1.32.3"
resolved "https://registry.yarnpkg.com/playwright/-/playwright-1.32.3.tgz#88583167880e42ca04fa8c4cc303680f4ff457d0"
diff --git a/restapi/client.go b/restapi/client.go
index ebbf96362d..d1f33b4f95 100644
--- a/restapi/client.go
+++ b/restapi/client.go
@@ -21,6 +21,7 @@ import (
"errors"
"fmt"
"io"
+ "net/url"
"path"
"strings"
"time"
@@ -221,6 +222,10 @@ func (c minioClient) copyObject(ctx context.Context, dst minio.CopyDestOptions,
return c.client.CopyObject(ctx, dst, src)
}
+func (c minioClient) presignedGetObject(ctx context.Context, bucketName, objectName string, expiry time.Duration, reqParams url.Values) (*url.URL, error) {
+ return c.client.PresignedGetObject(ctx, bucketName, objectName, expiry, reqParams)
+}
+
// MCClient interface with all functions to be implemented
// by mock when testing, it should include all mc/S3Client respective api calls
// that are used within this project.
diff --git a/restapi/embedded_spec.go b/restapi/embedded_spec.go
index 5f77661af3..b251dd66ff 100644
--- a/restapi/embedded_spec.go
+++ b/restapi/embedded_spec.go
@@ -1858,7 +1858,7 @@ func init() {
}
},
"/buckets/{bucket_name}/objects/share": {
- "get": {
+ "post": {
"tags": [
"Object"
],
@@ -1872,21 +1872,12 @@ func init() {
"required": true
},
{
- "type": "string",
- "name": "prefix",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "name": "version_id",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "name": "expires",
- "in": "query"
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/shareRequest"
+ }
}
],
"responses": {
@@ -8319,6 +8310,32 @@ func init() {
}
}
},
+ "shareRequest": {
+ "type": "object",
+ "required": [
+ "prefix",
+ "version_id",
+ "access_key",
+ "secret_key"
+ ],
+ "properties": {
+ "access_key": {
+ "type": "string"
+ },
+ "expires": {
+ "type": "string"
+ },
+ "prefix": {
+ "type": "string"
+ },
+ "secret_key": {
+ "type": "string"
+ },
+ "version_id": {
+ "type": "string"
+ }
+ }
+ },
"siteReplicationAddRequest": {
"type": "array",
"items": {
@@ -10860,7 +10877,7 @@ func init() {
}
},
"/buckets/{bucket_name}/objects/share": {
- "get": {
+ "post": {
"tags": [
"Object"
],
@@ -10874,21 +10891,12 @@ func init() {
"required": true
},
{
- "type": "string",
- "name": "prefix",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "name": "version_id",
- "in": "query",
- "required": true
- },
- {
- "type": "string",
- "name": "expires",
- "in": "query"
+ "name": "body",
+ "in": "body",
+ "required": true,
+ "schema": {
+ "$ref": "#/definitions/shareRequest"
+ }
}
],
"responses": {
@@ -17450,6 +17458,32 @@ func init() {
}
}
},
+ "shareRequest": {
+ "type": "object",
+ "required": [
+ "prefix",
+ "version_id",
+ "access_key",
+ "secret_key"
+ ],
+ "properties": {
+ "access_key": {
+ "type": "string"
+ },
+ "expires": {
+ "type": "string"
+ },
+ "prefix": {
+ "type": "string"
+ },
+ "secret_key": {
+ "type": "string"
+ },
+ "version_id": {
+ "type": "string"
+ }
+ }
+ },
"siteReplicationAddRequest": {
"type": "array",
"items": {
diff --git a/restapi/operations/console_api.go b/restapi/operations/console_api.go
index e3cf2d745c..022ff9da15 100644
--- a/restapi/operations/console_api.go
+++ b/restapi/operations/console_api.go
@@ -2149,10 +2149,10 @@ func (o *ConsoleAPI) initHandlerCache() {
o.handlers["PUT"] = make(map[string]http.Handler)
}
o.handlers["PUT"]["/service-accounts/{access_key}/policy"] = service_account.NewSetServiceAccountPolicy(o.context, o.ServiceAccountSetServiceAccountPolicyHandler)
- if o.handlers["GET"] == nil {
- o.handlers["GET"] = make(map[string]http.Handler)
+ if o.handlers["POST"] == nil {
+ o.handlers["POST"] = make(map[string]http.Handler)
}
- o.handlers["GET"]["/buckets/{bucket_name}/objects/share"] = object.NewShareObject(o.context, o.ObjectShareObjectHandler)
+ o.handlers["POST"]["/buckets/{bucket_name}/objects/share"] = object.NewShareObject(o.context, o.ObjectShareObjectHandler)
if o.handlers["PUT"] == nil {
o.handlers["PUT"] = make(map[string]http.Handler)
}
diff --git a/restapi/operations/object/share_object.go b/restapi/operations/object/share_object.go
index 834e992105..96f9ae23a4 100644
--- a/restapi/operations/object/share_object.go
+++ b/restapi/operations/object/share_object.go
@@ -49,7 +49,7 @@ func NewShareObject(ctx *middleware.Context, handler ShareObjectHandler) *ShareO
}
/*
- ShareObject swagger:route GET /buckets/{bucket_name}/objects/share Object shareObject
+ ShareObject swagger:route POST /buckets/{bucket_name}/objects/share Object shareObject
Shares an Object on a url
*/
diff --git a/restapi/operations/object/share_object_parameters.go b/restapi/operations/object/share_object_parameters.go
index e199afcfff..f4a3a6e2ab 100644
--- a/restapi/operations/object/share_object_parameters.go
+++ b/restapi/operations/object/share_object_parameters.go
@@ -23,6 +23,7 @@ package object
// Editing this file might prove futile when you re-run the swagger generate command
import (
+ "io"
"net/http"
"github.com/go-openapi/errors"
@@ -30,6 +31,8 @@ import (
"github.com/go-openapi/runtime/middleware"
"github.com/go-openapi/strfmt"
"github.com/go-openapi/validate"
+
+ "github.com/minio/console/models"
)
// NewShareObjectParams creates a new ShareObjectParams object
@@ -51,23 +54,14 @@ type ShareObjectParams struct {
/*
Required: true
- In: path
- */
- BucketName string
- /*
- In: query
- */
- Expires *string
- /*
- Required: true
- In: query
+ In: body
*/
- Prefix string
+ Body *models.ShareRequest
/*
Required: true
- In: query
+ In: path
*/
- VersionID string
+ BucketName string
}
// BindRequest both binds and validates a request, it assumes that complex things implement a Validatable(strfmt.Registry) error interface
@@ -79,27 +73,38 @@ func (o *ShareObjectParams) BindRequest(r *http.Request, route *middleware.Match
o.HTTPRequest = r
- qs := runtime.Values(r.URL.Query())
+ if runtime.HasBody(r) {
+ defer r.Body.Close()
+ var body models.ShareRequest
+ if err := route.Consumer.Consume(r.Body, &body); err != nil {
+ if err == io.EOF {
+ res = append(res, errors.Required("body", "body", ""))
+ } else {
+ res = append(res, errors.NewParseError("body", "body", "", err))
+ }
+ } else {
+ // validate body object
+ if err := body.Validate(route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ ctx := validate.WithOperationRequest(r.Context())
+ if err := body.ContextValidate(ctx, route.Formats); err != nil {
+ res = append(res, err)
+ }
+
+ if len(res) == 0 {
+ o.Body = &body
+ }
+ }
+ } else {
+ res = append(res, errors.Required("body", "body", ""))
+ }
rBucketName, rhkBucketName, _ := route.Params.GetOK("bucket_name")
if err := o.bindBucketName(rBucketName, rhkBucketName, route.Formats); err != nil {
res = append(res, err)
}
-
- qExpires, qhkExpires, _ := qs.GetOK("expires")
- if err := o.bindExpires(qExpires, qhkExpires, route.Formats); err != nil {
- res = append(res, err)
- }
-
- qPrefix, qhkPrefix, _ := qs.GetOK("prefix")
- if err := o.bindPrefix(qPrefix, qhkPrefix, route.Formats); err != nil {
- res = append(res, err)
- }
-
- qVersionID, qhkVersionID, _ := qs.GetOK("version_id")
- if err := o.bindVersionID(qVersionID, qhkVersionID, route.Formats); err != nil {
- res = append(res, err)
- }
if len(res) > 0 {
return errors.CompositeValidationError(res...)
}
@@ -119,63 +124,3 @@ func (o *ShareObjectParams) bindBucketName(rawData []string, hasKey bool, format
return nil
}
-
-// bindExpires binds and validates parameter Expires from query.
-func (o *ShareObjectParams) bindExpires(rawData []string, hasKey bool, formats strfmt.Registry) error {
- var raw string
- if len(rawData) > 0 {
- raw = rawData[len(rawData)-1]
- }
-
- // Required: false
- // AllowEmptyValue: false
-
- if raw == "" { // empty values pass all other validations
- return nil
- }
- o.Expires = &raw
-
- return nil
-}
-
-// bindPrefix binds and validates parameter Prefix from query.
-func (o *ShareObjectParams) bindPrefix(rawData []string, hasKey bool, formats strfmt.Registry) error {
- if !hasKey {
- return errors.Required("prefix", "query", rawData)
- }
- var raw string
- if len(rawData) > 0 {
- raw = rawData[len(rawData)-1]
- }
-
- // Required: true
- // AllowEmptyValue: false
-
- if err := validate.RequiredString("prefix", "query", raw); err != nil {
- return err
- }
- o.Prefix = raw
-
- return nil
-}
-
-// bindVersionID binds and validates parameter VersionID from query.
-func (o *ShareObjectParams) bindVersionID(rawData []string, hasKey bool, formats strfmt.Registry) error {
- if !hasKey {
- return errors.Required("version_id", "query", rawData)
- }
- var raw string
- if len(rawData) > 0 {
- raw = rawData[len(rawData)-1]
- }
-
- // Required: true
- // AllowEmptyValue: false
-
- if err := validate.RequiredString("version_id", "query", raw); err != nil {
- return err
- }
- o.VersionID = raw
-
- return nil
-}
diff --git a/restapi/operations/object/share_object_urlbuilder.go b/restapi/operations/object/share_object_urlbuilder.go
index 875a27811b..64af1ab5b4 100644
--- a/restapi/operations/object/share_object_urlbuilder.go
+++ b/restapi/operations/object/share_object_urlbuilder.go
@@ -33,10 +33,6 @@ import (
type ShareObjectURL struct {
BucketName string
- Expires *string
- Prefix string
- VersionID string
-
_basePath string
// avoid unkeyed usage
_ struct{}
@@ -76,28 +72,6 @@ func (o *ShareObjectURL) Build() (*url.URL, error) {
}
_result.Path = golangswaggerpaths.Join(_basePath, _path)
- qs := make(url.Values)
-
- var expiresQ string
- if o.Expires != nil {
- expiresQ = *o.Expires
- }
- if expiresQ != "" {
- qs.Set("expires", expiresQ)
- }
-
- prefixQ := o.Prefix
- if prefixQ != "" {
- qs.Set("prefix", prefixQ)
- }
-
- versionIDQ := o.VersionID
- if versionIDQ != "" {
- qs.Set("version_id", versionIDQ)
- }
-
- _result.RawQuery = qs.Encode()
-
return &_result, nil
}
diff --git a/restapi/user_objects.go b/restapi/user_objects.go
index bc32008be2..7cdc6d14de 100644
--- a/restapi/user_objects.go
+++ b/restapi/user_objects.go
@@ -31,6 +31,8 @@ import (
"strings"
"time"
+ "github.com/minio/minio-go/v7/pkg/credentials"
+
"github.com/go-openapi/runtime"
"github.com/go-openapi/runtime/middleware"
"github.com/klauspost/compress/zip"
@@ -117,7 +119,7 @@ func registerObjectsHandlers(api *operations.ConsoleAPI) {
})
// get share object url
api.ObjectShareObjectHandler = objectApi.ShareObjectHandlerFunc(func(params objectApi.ShareObjectParams, session *models.Principal) middleware.Responder {
- resp, err := getShareObjectResponse(session, params)
+ resp, err := getShareObjectResponse(params)
if err != nil {
return objectApi.NewShareObjectDefault(int(err.Code)).WithPayload(err)
}
@@ -892,34 +894,60 @@ func uploadFiles(ctx context.Context, client MinioClient, params objectApi.PostB
return nil
}
-// getShareObjectResponse returns a share object url
-func getShareObjectResponse(session *models.Principal, params objectApi.ShareObjectParams) (*string, *models.Error) {
+// getShareObjectResponse returns a share object url, Session is omitted as we will sign the URl with a new static token
+func getShareObjectResponse(params objectApi.ShareObjectParams) (*string, *models.Error) {
ctx := params.HTTPRequest.Context()
+ bodyPrefix := *params.Body.Prefix
+
+ accessKey := *params.Body.AccessKey
+ secretKey := *params.Body.SecretKey
+
+ creds := credentials.NewStaticV4(accessKey, secretKey, "")
+
+ mClient, err := minio.New(getMinIOEndpoint(), &minio.Options{
+ Creds: creds,
+ Secure: getMinIOEndpointIsSecure(),
+ Transport: GetConsoleHTTPClient(getMinIOServer()).Transport,
+ })
+ if err != nil {
+ return nil, ErrorWithContext(ctx, err)
+ }
+
+ minioClient := minioClient{client: mClient}
+
var prefix string
- if params.Prefix != "" {
- encodedPrefix := SanitizeEncodedPrefix(params.Prefix)
+ if bodyPrefix != "" {
+ encodedPrefix := SanitizeEncodedPrefix(bodyPrefix)
decodedPrefix, err := base64.StdEncoding.DecodeString(encodedPrefix)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
prefix = string(decodedPrefix)
}
- s3Client, err := newS3BucketClient(session, params.BucketName, prefix)
- if err != nil {
- return nil, ErrorWithContext(ctx, err)
+
+ expireDuration := time.Duration(604800) * time.Second
+
+ if params.Body.Expires != "" {
+ expireDuration, err = time.ParseDuration(params.Body.Expires)
+
+ if err != nil {
+ return nil, ErrorWithContext(ctx, err)
+ }
}
- // create a mc S3Client interface implementation
- // defining the client to be used
- mcClient := mcClient{client: s3Client}
- var expireDuration string
- if params.Expires != nil {
- expireDuration = *params.Expires
+
+ reqParams := make(url.Values)
+ if *params.Body.VersionID != "" {
+ reqParams.Set("versionId", *params.Body.VersionID)
}
- url, err := getShareObjectURL(ctx, mcClient, params.VersionID, expireDuration)
+
+ urlParams, err := minioClient.presignedGetObject(ctx, params.BucketName, prefix, expireDuration, reqParams)
if err != nil {
return nil, ErrorWithContext(ctx, err)
}
- return url, nil
+
+ stringURL := urlParams.String()
+
+ return &stringURL, nil
}
func getShareObjectURL(ctx context.Context, client MCClient, versionID string, duration string) (url *string, err error) {
diff --git a/swagger.yml b/swagger.yml
index 87f1e72663..91da6bf856 100644
--- a/swagger.yml
+++ b/swagger.yml
@@ -484,7 +484,7 @@ paths:
- Object
/buckets/{bucket_name}/objects/share:
- get:
+ post:
summary: Shares an Object on a url
operationId: ShareObject
parameters:
@@ -492,18 +492,11 @@ paths:
in: path
required: true
type: string
- - name: prefix
- in: query
- required: true
- type: string
- - name: version_id
- in: query
+ - name: body
+ in: body
required: true
- type: string
- - name: expires
- in: query
- required: false
- type: string
+ schema:
+ $ref: "#/definitions/shareRequest"
responses:
200:
description: A successful response.
@@ -6152,4 +6145,23 @@ definitions:
groups:
type: array
items:
- type: string
\ No newline at end of file
+ type: string
+
+ shareRequest:
+ type: object
+ required:
+ - prefix
+ - version_id
+ - access_key
+ - secret_key
+ properties:
+ prefix:
+ type: string
+ version_id:
+ type: string
+ expires:
+ type: string
+ access_key:
+ type: string
+ secret_key:
+ type: string
\ No newline at end of file