Repository: minio/minio-go
Pull request #1182 (2019-11-06 12:56:08)

Add owner information in GetObjectACL and add Context
This PR add the object owner information in the `ObjectInfo` returned by `GetObjectACL`.
And add the context to `GetObjectACL` by creating a method `GetObjectACLWithContext`

poornas: @pierre-emmanuelJ, it would be better to do a `git mv get-object-acl.go get-object-acl-context.go` to preserve the history better rather than add and remove files. GetObjectACL() function needs to be replaced with GetObjectACLWithContext in the functional tests and examples/s3/getobjectacl.go
pierre-emmanuelJ: Thank you, I'll do it ASAP
pierre-emmanuelJ: > Thank you, I'll do it ASAP
Hello, I'll fix the comment of @poornas  today.
Thank you

pierre-emmanuelJ: Is it okay for you? 😃 

Patch:
diff --git a/api-get-object-acl-context.go b/api-get-object-acl-context.go
new file mode 100644
index 0000000..24d8767
--- /dev/null
+++ b/api-get-object-acl-context.go
@@ -0,0 +1,138 @@
+/*
+ * MinIO Go Library for Amazon S3 Compatible Cloud Storage
+ * Copyright 2018 MinIO, Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package minio
+
+import (
+	"context"
+	"net/http"
+	"net/url"
+)
+
+type accessControlPolicy struct {
+	Owner struct {
+		ID          string `xml:"ID"`
+		DisplayName string `xml:"DisplayName"`
+	} `xml:"Owner"`
+	AccessControlList struct {
+		Grant []struct {
+			Grantee struct {
+				ID          string `xml:"ID"`
+				DisplayName string `xml:"DisplayName"`
+				URI         string `xml:"URI"`
+			} `xml:"Grantee"`
+			Permission string `xml:"Permission"`
+		} `xml:"Grant"`
+	} `xml:"AccessControlList"`
+}
+
+//GetObjectACLWithContext get object ACLs
+func (c Client) GetObjectACLWithContext(ctx context.Context, bucketName, objectName string) (*ObjectInfo, error) {
+	resp, err := c.executeMethod(ctx, "GET", requestMetadata{
+		bucketName: bucketName,
+		objectName: objectName,
+		queryValues: url.Values{
+			"acl": []string{""},
+		},
+	})
+	if err != nil {
+		return nil, err
+	}
+	defer closeResponse(resp)
+
+	if resp.StatusCode != http.StatusOK {
+		return nil, httpRespToErrorResponse(resp, bucketName, objectName)
+	}
+
+	res := &accessControlPolicy{}
+
+	if err := xmlDecoder(resp.Body, res); err != nil {
+		return nil, err
+	}
+
+	objInfo, err := c.statObject(ctx, bucketName, objectName, StatObjectOptions{})
+	if err != nil {
+		return nil, err
+	}
+
+	objInfo.Owner.DisplayName = res.Owner.DisplayName
+	objInfo.Owner.ID = res.Owner.ID
+
+	cannedACL := getCannedACL(res)
+	if cannedACL != "" {
+		objInfo.Metadata.Add("X-Amz-Acl", cannedACL)
+		return &objInfo, nil
+	}
+
+	grantACL := getAmzGrantACL(res)
+	for k, v := range grantACL {
+		objInfo.Metadata[k] = v
+	}
+
+	return &objInfo, nil
+}
+
+func getCannedACL(aCPolicy *accessControlPolicy) string {
+	grants := aCPolicy.AccessControlList.Grant
+
+	switch {
+	case len(grants) == 1:
+		if grants[0].Grantee.URI == "" && grants[0].Permission == "FULL_CONTROL" {
+			return "private"
+		}
+	case len(grants) == 2:
+		for _, g := range grants {
+			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" && g.Permission == "READ" {
+				return "authenticated-read"
+			}
+			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "READ" {
+				return "public-read"
+			}
+			if g.Permission == "READ" && g.Grantee.ID == aCPolicy.Owner.ID {
+				return "bucket-owner-read"
+			}
+		}
+	case len(grants) == 3:
+		for _, g := range grants {
+			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "WRITE" {
+				return "public-read-write"
+			}
+		}
+	}
+	return ""
+}
+
+func getAmzGrantACL(aCPolicy *accessControlPolicy) map[string][]string {
+	grants := aCPolicy.AccessControlList.Grant
+	res := map[string][]string{}
+
+	for _, g := range grants {
+		switch {
+		case g.Permission == "READ":
+			res["X-Amz-Grant-Read"] = append(res["X-Amz-Grant-Read"], "id="+g.Grantee.ID)
+		case g.Permission == "WRITE":
+			res["X-Amz-Grant-Write"] = append(res["X-Amz-Grant-Write"], "id="+g.Grantee.ID)
+		case g.Permission == "READ_ACP":
+			res["X-Amz-Grant-Read-Acp"] = append(res["X-Amz-Grant-Read-Acp"], "id="+g.Grantee.ID)
+		case g.Permission == "WRITE_ACP":
+			res["X-Amz-Grant-Write-Acp"] = append(res["X-Amz-Grant-Write-Acp"], "id="+g.Grantee.ID)
+		case g.Permission == "FULL_CONTROL":
+			res["X-Amz-Grant-Full-Control"] = append(res["X-Amz-Grant-Full-Control"], "id="+g.Grantee.ID)
+		}
+	}
+	return res
+}
diff --git a/api-get-object-acl.go b/api-get-object-acl.go
index ea80949..9a664f1 100644
--- a/api-get-object-acl.go
+++ b/api-get-object-acl.go
@@ -19,118 +19,9 @@ package minio
 
 import (
 	"context"
-	"net/http"
-	"net/url"
 )
 
-type accessControlPolicy struct {
-	Owner struct {
-		ID          string `xml:"ID"`
-		DisplayName string `xml:"DisplayName"`
-	} `xml:"Owner"`
-	AccessControlList struct {
-		Grant []struct {
-			Grantee struct {
-				ID          string `xml:"ID"`
-				DisplayName string `xml:"DisplayName"`
-				URI         string `xml:"URI"`
-			} `xml:"Grantee"`
-			Permission string `xml:"Permission"`
-		} `xml:"Grant"`
-	} `xml:"AccessControlList"`
-}
-
 //GetObjectACL get object ACLs
 func (c Client) GetObjectACL(bucketName, objectName string) (*ObjectInfo, error) {
-
-	resp, err := c.executeMethod(context.Background(), "GET", requestMetadata{
-		bucketName: bucketName,
-		objectName: objectName,
-		queryValues: url.Values{
-			"acl": []string{""},
-		},
-	})
-	if err != nil {
-		return nil, err
-	}
-	defer closeResponse(resp)
-
-	if resp.StatusCode != http.StatusOK {
-		return nil, httpRespToErrorResponse(resp, bucketName, objectName)
-	}
-
-	res := &accessControlPolicy{}
-
-	if err := xmlDecoder(resp.Body, res); err != nil {
-		return nil, err
-	}
-
-	objInfo, err := c.statObject(context.Background(), bucketName, objectName, StatObjectOptions{})
-	if err != nil {
-		return nil, err
-	}
-
-	cannedACL := getCannedACL(res)
-	if cannedACL != "" {
-		objInfo.Metadata.Add("X-Amz-Acl", cannedACL)
-		return &objInfo, nil
-	}
-
-	grantACL := getAmzGrantACL(res)
-	for k, v := range grantACL {
-		objInfo.Metadata[k] = v
-	}
-
-	return &objInfo, nil
-}
-
-func getCannedACL(aCPolicy *accessControlPolicy) string {
-	grants := aCPolicy.AccessControlList.Grant
-
-	switch {
-	case len(grants) == 1:
-		if grants[0].Grantee.URI == "" && grants[0].Permission == "FULL_CONTROL" {
-			return "private"
-		}
-	case len(grants) == 2:
-		for _, g := range grants {
-			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AuthenticatedUsers" && g.Permission == "READ" {
-				return "authenticated-read"
-			}
-			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "READ" {
-				return "public-read"
-			}
-			if g.Permission == "READ" && g.Grantee.ID == aCPolicy.Owner.ID {
-				return "bucket-owner-read"
-			}
-		}
-	case len(grants) == 3:
-		for _, g := range grants {
-			if g.Grantee.URI == "http://acs.amazonaws.com/groups/global/AllUsers" && g.Permission == "WRITE" {
-				return "public-read-write"
-			}
-		}
-	}
-	return ""
-}
-
-func getAmzGrantACL(aCPolicy *accessControlPolicy) map[string][]string {
-	grants := aCPolicy.AccessControlList.Grant
-	res := map[string][]string{}
-
-	for _, g := range grants {
-		switch {
-		case g.Permission == "READ":
-			res["X-Amz-Grant-Read"] = append(res["X-Amz-Grant-Read"], "id="+g.Grantee.ID)
-		case g.Permission == "WRITE":
-			res["X-Amz-Grant-Write"] = append(res["X-Amz-Grant-Write"], "id="+g.Grantee.ID)
-		case g.Permission == "READ_ACP":
-			res["X-Amz-Grant-Read-Acp"] = append(res["X-Amz-Grant-Read-Acp"], "id="+g.Grantee.ID)
-		case g.Permission == "WRITE_ACP":
-			res["X-Amz-Grant-Write-Acp"] = append(res["X-Amz-Grant-Write-Acp"], "id="+g.Grantee.ID)
-		case g.Permission == "FULL_CONTROL":
-			res["X-Amz-Grant-Full-Control"] = append(res["X-Amz-Grant-Full-Control"], "id="+g.Grantee.ID)
-		}
-	}
-	return res
+	return c.GetObjectACLWithContext(context.Background(), bucketName, objectName)
 }
diff --git a/examples/s3/getobjectacl.go b/examples/s3/getobjectacl.go
index f557009..b4e7207 100644
--- a/examples/s3/getobjectacl.go
+++ b/examples/s3/getobjectacl.go
@@ -45,6 +45,12 @@ func main() {
 		log.Fatalln(err)
 	}
 
+	//print object owner informations
+	fmt.Printf(`Object owner:
+Display name: %q
+ID: %q
+`, objectInfo.Owner.DisplayName, objectInfo.Owner.ID)
+
 	//print all value header (acl, metadata, standard header value...)
 	for k, v := range objectInfo.Metadata {
 		fmt.Println("key:", k)
diff --git a/pkg/credentials/iam_aws.go b/pkg/credentials/iam_aws.go
index 5732f2e..34c593f 100644
--- a/pkg/credentials/iam_aws.go
+++ b/pkg/credentials/iam_aws.go
@@ -22,6 +22,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"net"
 	"net/http"
 	"net/url"
 	"os"
@@ -57,14 +58,35 @@ const (
 )
 
 // https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task-iam-roles.html
-func getEndpoint(endpoint string) (string, bool) {
+func getEndpoint(endpoint string) (string, bool, error) {
+	ecsFullURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")
+	ecsURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI")
+
 	if endpoint != "" {
-		return endpoint, os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI") != ""
+		return endpoint, ecsURI != "" || ecsFullURI != "", nil
+	}
+	if ecsFullURI != "" {
+		u, err := url.Parse(ecsFullURI)
+		if err != nil {
+			return "", false, err
+		}
+		host := u.Hostname()
+		if host == "" {
+			return "", false, fmt.Errorf("can't parse host from uri: %s", ecsFullURI)
+		}
+
+		if loopback, err := isLoopback(host); loopback {
+			return ecsFullURI, true, nil
+		} else if err != nil {
+			return "", false, err
+		} else {
+			return "", false, fmt.Errorf("host is not on a loopback address: %s", host)
+		}
 	}
-	if ecsURI := os.Getenv("AWS_CONTAINER_CREDENTIALS_RELATIVE_URI"); ecsURI != "" {
-		return fmt.Sprintf("%s%s", defaultECSRoleEndpoint, ecsURI), true
+	if ecsURI != "" {
+		return fmt.Sprintf("%s%s", defaultECSRoleEndpoint, ecsURI), true, nil
 	}
-	return defaultIAMRoleEndpoint, false
+	return defaultIAMRoleEndpoint, false, nil
 }
 
 // NewIAM returns a pointer to a new Credentials object wrapping the IAM.
@@ -82,9 +104,14 @@ func NewIAM(endpoint string) *Credentials {
 // Error will be returned if the request fails, or unable to extract
 // the desired
 func (m *IAM) Retrieve() (Value, error) {
-	endpoint, isEcsTask := getEndpoint(m.endpoint)
 	var roleCreds ec2RoleCredRespBody
 	var err error
+
+	endpoint, isEcsTask, err := getEndpoint(m.endpoint)
+	if err != nil {
+		return Value{}, err
+	}
+
 	if isEcsTask {
 		roleCreds, err = getEcsTaskCredentials(m.Client, endpoint)
 	} else {
@@ -248,3 +275,18 @@ func getCredentials(client *http.Client, endpoint string) (ec2RoleCredRespBody,
 
 	return respCreds, nil
 }
+
+// isLoopback identifies if a host is on a loopback address
+func isLoopback(host string) (bool, error) {
+	ips, err := net.LookupHost(host)
+	if err != nil {
+		return false, err
+	}
+	for _, ip := range ips {
+		if !net.ParseIP(ip).IsLoopback() {
+			return false, nil
+		}
+	}
+
+	return true, nil
+}


Test patch:
diff --git a/functional_tests.go b/functional_tests.go
index 69ae3b2..38d69f7 100644
--- a/functional_tests.go
+++ b/functional_tests.go
@@ -9528,13 +9528,14 @@ func testFGetObjectWithContext() {
 
 }
 
-// Test get object ACLs with GetObjectACL
-func testGetObjectACL() {
+// Test get object ACLs with GetObjectACLWithContext
+func testGetObjectACLWithContext() {
 	// initialize logging params
 	startTime := time.Now()
 	testName := getFuncName()
-	function := "GetObjectACL(bucketName, objectName)"
+	function := "GetObjectACLWithContext(ctx, bucketName, objectName)"
 	args := map[string]interface{}{
+		"ctx":        "",
 		"bucketName": "",
 		"objectName": "",
 	}
@@ -9594,26 +9595,30 @@ func testGetObjectACL() {
 		return
 	}
 
+	ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	args["ctx"] = ctx
+	defer cancel()
+
 	// Read the data back
-	objectInfo, getObjectACLErr := c.GetObjectACL(bucketName, objectName)
+	objectInfo, getObjectACLErr := c.GetObjectACLWithContext(ctx, bucketName, objectName)
 	if getObjectACLErr == nil {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail", getObjectACLErr)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail", getObjectACLErr)
 		return
 	}
 
 	s, ok := objectInfo.Metadata["X-Amz-Acl"]
 	if !ok {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Acl\"", nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail unable to find \"X-Amz-Acl\"", nil)
 		return
 	}
 
 	if len(s) != 1 {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" canned acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Acl\" canned acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
 		return
 	}
 
 	if s[0] != "public-read-write" {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Acl\" expected \"public-read-write\" but got"+fmt.Sprintf("%q", s[0]), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Acl\" expected \"public-read-write\" but got"+fmt.Sprintf("%q", s[0]), nil)
 		return
 	}
 
@@ -9636,47 +9641,51 @@ func testGetObjectACL() {
 		return
 	}
 
+	ctx, cancel = context.WithTimeout(context.Background(), 10*time.Second)
+	args["ctx"] = ctx
+	defer cancel()
+
 	// Read the data back
-	objectInfo, getObjectACLErr = c.GetObjectACL(bucketName, objectName)
+	objectInfo, getObjectACLErr = c.GetObjectACLWithContext(ctx, bucketName, objectName)
 	if getObjectACLErr == nil {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail", getObjectACLErr)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail", getObjectACLErr)
 		return
 	}
 
 	if len(objectInfo.Metadata) != 3 {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail expected \"3\" ACLs but got "+fmt.Sprintf(`"%d"`, len(objectInfo.Metadata)), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail expected \"3\" ACLs but got "+fmt.Sprintf(`"%d"`, len(objectInfo.Metadata)), nil)
 		return
 	}
 
 	s, ok = objectInfo.Metadata["X-Amz-Grant-Read"]
 	if !ok {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Read\"", nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail unable to find \"X-Amz-Grant-Read\"", nil)
 		return
 	}
 
 	if len(s) != 1 {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Grant-Read\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
 		return
 	}
 
 	if s[0] != "fooread@minio.go" {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Read\" acl expected \"fooread@minio.go\" got "+fmt.Sprintf("%q", s), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Grant-Read\" acl expected \"fooread@minio.go\" got "+fmt.Sprintf("%q", s), nil)
 		return
 	}
 
 	s, ok = objectInfo.Metadata["X-Amz-Grant-Write"]
 	if !ok {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail unable to find \"X-Amz-Grant-Write\"", nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail unable to find \"X-Amz-Grant-Write\"", nil)
 		return
 	}
 
 	if len(s) != 1 {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Grant-Write\" acl expected \"1\" got "+fmt.Sprintf(`"%d"`, len(s)), nil)
 		return
 	}
 
 	if s[0] != "foowrite@minio.go" {
-		logError(testName, function, args, startTime, "", "GetObjectACL fail \"X-Amz-Grant-Write\" acl expected \"foowrite@minio.go\" got "+fmt.Sprintf("%q", s), nil)
+		logError(testName, function, args, startTime, "", "GetObjectACLWithContext fail \"X-Amz-Grant-Write\" acl expected \"foowrite@minio.go\" got "+fmt.Sprintf("%q", s), nil)
 		return
 	}
 
@@ -10146,7 +10155,7 @@ func main() {
 		testFPutObjectWithContext()
 		testFGetObjectWithContext()
 
-		testGetObjectACL()
+		testGetObjectACLWithContext()
 
 		testPutObjectWithContext()
 		testStorageClassMetadataPutObject()
diff --git a/pkg/credentials/iam_aws_test.go b/pkg/credentials/iam_aws_test.go
index 90f9806..d502a8a 100644
--- a/pkg/credentials/iam_aws_test.go
+++ b/pkg/credentials/iam_aws_test.go
@@ -243,3 +243,33 @@ func TestEcsTask(t *testing.T) {
 		t.Error("Expected creds to be expired.")
 	}
 }
+
+func TestEcsTaskFullURI(t *testing.T) {
+	server := initEcsTaskTestServer("2014-12-16T01:51:37Z")
+	defer server.Close()
+	p := &IAM{
+		Client: http.DefaultClient,
+	}
+	os.Setenv("AWS_CONTAINER_CREDENTIALS_FULL_URI",
+		fmt.Sprintf("%s%s", server.URL, "/v2/credentials?id=task_credential_id"))
+	creds, err := p.Retrieve()
+	os.Unsetenv("AWS_CONTAINER_CREDENTIALS_FULL_URI")
+	if err != nil {
+		t.Errorf("Unexpected failure %s", err)
+	}
+	if "accessKey" != creds.AccessKeyID {
+		t.Errorf("Expected \"accessKey\", got %s", creds.AccessKeyID)
+	}
+
+	if "secret" != creds.SecretAccessKey {
+		t.Errorf("Expected \"secret\", got %s", creds.SecretAccessKey)
+	}
+
+	if "token" != creds.SessionToken {
+		t.Errorf("Expected \"token\", got %s", creds.SessionToken)
+	}
+
+	if !p.IsExpired() {
+		t.Error("Expected creds to be expired.")
+	}
+}