From 1e53316241966aba9f1d5a59feff02adfcedeb21 Mon Sep 17 00:00:00 2001 From: Krishna Srinivas Date: Fri, 23 Sep 2016 01:24:49 -0700 Subject: [PATCH] Add tests for presigned-get (#2767) * web-handlers: support for presigned-get json-rpc call for MinioBrowser's "share" feature. * Add tests for presigned-get --- cmd/web-handlers.go | 65 ++++++++++++++++++++++++++++++ cmd/web-handlers_test.go | 86 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 151 insertions(+) diff --git a/cmd/web-handlers.go b/cmd/web-handlers.go index 36cbea2f9..65297e473 100644 --- a/cmd/web-handlers.go +++ b/cmd/web-handlers.go @@ -27,6 +27,7 @@ import ( "path" "runtime" "strconv" + "strings" "time" jwtgo "github.com/dgrijalva/jwt-go" @@ -669,3 +670,67 @@ func (web *webAPIHandlers) SetBucketPolicy(r *http.Request, args *SetBucketPolic reply.UIVersion = miniobrowser.UIVersion return nil } + +// PresignedGetArgs - presigned-get API args. +type PresignedGetArgs struct { + // Host header required for signed headers. + HostName string `json:"host"` + + // Bucket name of the object to be presigned. + BucketName string `json:"bucket"` + + // Object name to be presigned. + ObjectName string `json:"object"` +} + +// PresignedGetRep - presigned-get URL reply. +type PresignedGetRep struct { + // Presigned URL of the object. + URL string `json:"url"` +} + +// PresignedGET - returns presigned-Get url. +func (web *webAPIHandlers) PresignedGet(r *http.Request, args *PresignedGetArgs, reply *PresignedGetRep) error { + if !isJWTReqAuthenticated(r) { + return &json2.Error{Message: "Unauthorized request"} + } + if args.BucketName == "" || args.ObjectName == "" { + return &json2.Error{Message: "Required arguments: Host, Bucket, Object"} + } + reply.URL = presignedGet(args.HostName, args.BucketName, args.ObjectName) + return nil +} + +// Returns presigned url for GET method. +func presignedGet(host, bucket, object string) string { + cred := serverConfig.GetCredential() + region := serverConfig.GetRegion() + + accessKey := cred.AccessKeyID + secretKey := cred.SecretAccessKey + + date := time.Now().UTC() + dateStr := date.Format("20060102T150405Z") + credential := fmt.Sprintf("%s/%s", accessKey, getScope(date, region)) + + query := strings.Join([]string{ + "X-Amz-Algorithm=" + signV4Algorithm, + "X-Amz-Credential=" + strings.Replace(credential, "/", "%2F", -1), + "X-Amz-Date=" + dateStr, + "X-Amz-Expires=" + "604800", // Default set to be expire in 7days. + "X-Amz-SignedHeaders=host", + }, "&") + + path := "/" + path.Join(bucket, object) + + // Headers are empty, since "host" is the only header required to be signed for Presigned URLs. + var extractedSignedHeaders http.Header + + canonicalRequest := getCanonicalRequest(extractedSignedHeaders, unsignedPayload, query, path, "GET", host) + stringToSign := getStringToSign(canonicalRequest, date, region) + signingKey := getSigningKey(secretKey, date, region) + signature := getSignature(signingKey, stringToSign) + + // Construct the final presigned URL. + return host + path + "?" + query + "&" + "X-Amz-Signature=" + signature +} diff --git a/cmd/web-handlers_test.go b/cmd/web-handlers_test.go index 83189226f..f2f632fd9 100644 --- a/cmd/web-handlers_test.go +++ b/cmd/web-handlers_test.go @@ -717,6 +717,92 @@ func testDownloadWebHandler(obj ObjectLayer, instanceType string, t TestErrHandl } } +// Wrapper for calling PresignedGet handler +func TestWebHandlerPresignedGetHandler(t *testing.T) { + ExecObjectLayerTest(t, testWebPresignedGetHandler) +} + +func testWebPresignedGetHandler(obj ObjectLayer, instanceType string, t TestErrHandler) { + // Register the API end points with XL/FS object layer. + apiRouter := initTestWebRPCEndPoint(obj) + // initialize the server and obtain the credentials and root. + // credentials are necessary to sign the HTTP request. + rootPath, err := newTestConfig("us-east-1") + if err != nil { + t.Fatalf("Init Test config failed") + } + // remove the root folder after the test ends. + defer removeAll(rootPath) + + credentials := serverConfig.GetCredential() + + authorization, err := getWebRPCToken(apiRouter, credentials.AccessKeyID, credentials.SecretAccessKey) + if err != nil { + t.Fatal("Cannot authenticate") + } + + rec := httptest.NewRecorder() + + bucketName := getRandomBucketName() + objectName := "object" + objectSize := 1024 + + // Create bucket. + err = obj.MakeBucket(bucketName) + if err != nil { + // failed to create newbucket, abort. + t.Fatalf("%s : %s", instanceType, err) + } + + data := bytes.Repeat([]byte("a"), objectSize) + _, err = obj.PutObject(bucketName, objectName, int64(len(data)), bytes.NewReader(data), map[string]string{"md5Sum": "c9a34cfc85d982698c6ac89f76071abd"}) + if err != nil { + t.Fatalf("Was not able to upload an object, %v", err) + } + + presignGetReq := PresignedGetArgs{ + HostName: "", + BucketName: bucketName, + ObjectName: objectName, + } + presignGetRep := &PresignedGetRep{} + req, err := newTestWebRPCRequest("Web.PresignedGet", authorization, presignGetReq) + if err != nil { + t.Fatalf("Failed to create HTTP request: %v", err) + } + apiRouter.ServeHTTP(rec, req) + if rec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", rec.Code) + } + err = getTestWebRPCResponse(rec, &presignGetRep) + if err != nil { + t.Fatalf("Failed, %v", err) + } + + // Register the API end points with XL/FS object layer. + apiRouter = initTestAPIEndPoints(obj, []string{"GetObject"}) + + // Initialize a new api recorder. + arec := httptest.NewRecorder() + + req, err = newTestRequest("GET", presignGetRep.URL, 0, nil) + req.Header.Del("x-amz-content-sha256") + if err != nil { + t.Fatal("Failed to initialized a new request", err) + } + apiRouter.ServeHTTP(arec, req) + if arec.Code != http.StatusOK { + t.Fatalf("Expected the response status to be 200, but instead found `%d`", arec.Code) + } + savedData, err := ioutil.ReadAll(arec.Body) + if err != nil { + t.Fatal("Reading body failed", err) + } + if !bytes.Equal(data, savedData) { + t.Fatal("Read data is not equal was what was expected") + } +} + // Wrapper for calling GetBucketPolicy Handler func TestWebHandlerGetBucketPolicyHandler(t *testing.T) { ExecObjectLayerTest(t, testWebGetBucketPolicyHandler)