-
Notifications
You must be signed in to change notification settings - Fork 849
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Containeracl #422
Containeracl #422
Changes from 29 commits
0bfe6be
daf1095
5161cd3
15e14b1
4a35137
ab94ed9
080d2ef
db88e32
473d57d
5b3af80
63cb499
309d477
f9a0fd1
4184e8d
cce5bd0
907bd6c
6083a83
ac366db
362ef1d
5bf4861
831ca62
522dfdc
f22ad3d
bb22d80
21f854e
7287de8
84c0f6c
0df936c
24913ed
6a9edd1
3218bf1
5b8d080
ded4c38
c47bff4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,6 +6,7 @@ import ( | |
"errors" | ||
"fmt" | ||
"io" | ||
"io/ioutil" | ||
"net/http" | ||
"net/url" | ||
"strconv" | ||
|
@@ -301,6 +302,65 @@ const ( | |
ContainerAccessTypeContainer ContainerAccessType = "container" | ||
) | ||
|
||
// ContainerAccessOptions are used when setting ACLs of containers (after creation) | ||
type ContainerAccessOptions struct { | ||
ContainerAccess ContainerAccessType | ||
Timeout int | ||
LeaseID string | ||
} | ||
|
||
// AccessPolicyDetails are used for SETTING policies | ||
type AccessPolicyDetails struct { | ||
ID string | ||
StartTime time.Time | ||
ExpiryTime time.Time | ||
CanRead bool | ||
CanWrite bool | ||
CanDelete bool | ||
} | ||
|
||
// ContainerPermissions is used when setting permissions and Access Policies for containers. | ||
type ContainerPermissions struct { | ||
AccessOptions ContainerAccessOptions | ||
AccessPolicy AccessPolicyDetails | ||
} | ||
|
||
// AccessPolicyDetailsXML has specifics about an access policy | ||
// annotated with XML details. | ||
type AccessPolicyDetailsXML struct { | ||
StartTime time.Time `xml:"Start"` | ||
ExpiryTime time.Time `xml:"Expiry"` | ||
Permission string `xml:"Permission"` | ||
} | ||
|
||
// SignedIdentifier is a wrapper for a specific policy | ||
type SignedIdentifier struct { | ||
ID string `xml:"Id"` | ||
AccessPolicy AccessPolicyDetailsXML `xml:"AccessPolicy"` | ||
} | ||
|
||
// SignedIdentifiers part of the response from GetPermissions call. | ||
type SignedIdentifiers struct { | ||
SignedIdentifiers []SignedIdentifier `xml:"SignedIdentifier"` | ||
} | ||
|
||
// AccessPolicy is the response type from the GetPermissions call. | ||
type AccessPolicy struct { | ||
SignedIdentifiersList SignedIdentifiers `xml:"SignedIdentifiers"` | ||
} | ||
|
||
// ContainerAccessResponse is returned for the GetContainerPermissions function. | ||
// This contains both the permission and access policy for the container. | ||
type ContainerAccessResponse struct { | ||
ContainerAccess ContainerAccessType | ||
AccessPolicy SignedIdentifiers | ||
} | ||
|
||
// ContainerAccessHeader references header used when setting/getting container ACL | ||
const ( | ||
ContainerAccessHeader string = "x-ms-blob-public-access" | ||
) | ||
|
||
// Maximum sizes (per REST API) for various concepts | ||
const ( | ||
MaxBlobBlockSize = 4 * 1024 * 1024 | ||
|
@@ -416,7 +476,7 @@ func (b BlobStorageClient) createContainer(name string, access ContainerAccessTy | |
|
||
headers := b.client.getStandardHeaders() | ||
if access != "" { | ||
headers["x-ms-blob-public-access"] = string(access) | ||
headers[ContainerAccessHeader] = string(access) | ||
} | ||
return b.client.exec(verb, uri, headers, nil) | ||
} | ||
|
@@ -438,6 +498,96 @@ func (b BlobStorageClient) ContainerExists(name string) (bool, error) { | |
return false, err | ||
} | ||
|
||
// SetContainerPermissions sets up container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179391.aspx | ||
func (b BlobStorageClient) SetContainerPermissions(container string, containerPermissions ContainerPermissions) error { | ||
params := url.Values{ | ||
"restype": {"container"}, | ||
"comp": {"acl"}, | ||
} | ||
|
||
if containerPermissions.AccessOptions.Timeout > 0 { | ||
params.Add("timeout", strconv.Itoa(containerPermissions.AccessOptions.Timeout)) | ||
} | ||
|
||
uri := b.client.getEndpoint(blobServiceName, pathForContainer(container), params) | ||
headers := b.client.getStandardHeaders() | ||
if containerPermissions.AccessOptions.ContainerAccess != "" { | ||
headers[ContainerAccessHeader] = string(containerPermissions.AccessOptions.ContainerAccess) | ||
} | ||
|
||
if containerPermissions.AccessOptions.LeaseID != "" { | ||
headers[leaseID] = containerPermissions.AccessOptions.LeaseID | ||
} | ||
|
||
// generate the XML for the SharedAccessSignature if required. | ||
accessPolicyXML, err := generateAccessPolicy(containerPermissions.AccessPolicy) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
var resp *storageResponse | ||
if accessPolicyXML != "" { | ||
headers["Content-Length"] = strconv.Itoa(len(accessPolicyXML)) | ||
resp, err = b.client.exec("PUT", uri, headers, strings.NewReader(accessPolicyXML)) | ||
} else { | ||
resp, err = b.client.exec("PUT", uri, headers, nil) | ||
} | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if resp != nil { | ||
defer resp.body.Close() | ||
|
||
if resp.statusCode != http.StatusOK { | ||
return errors.New("Unable to set permissions") | ||
} | ||
} | ||
return nil | ||
} | ||
|
||
// GetContainerPermissions gets the container permissions as per https://msdn.microsoft.com/en-us/library/azure/dd179469.aspx | ||
// If timeout is 0 then it will not be passed to Azure | ||
// leaseID will only be passed to Azure if populated | ||
// Returns permissionResponse which is combined permissions and AccessPolicy | ||
func (b BlobStorageClient) GetContainerPermissions(container string, timeout int, leaseID string) (permissionResponse *ContainerAccessResponse, err error) { | ||
params := url.Values{"restype": {"container"}, | ||
"comp": {"acl"}} | ||
|
||
if timeout > 0 { | ||
params.Add("timeout", strconv.Itoa(timeout)) | ||
} | ||
|
||
uri := b.client.getEndpoint(blobServiceName, pathForContainer(container), params) | ||
headers := b.client.getStandardHeaders() | ||
|
||
if leaseID != "" { | ||
headers[leaseID] = leaseID | ||
} | ||
|
||
resp, err := b.client.exec("GET", uri, headers, nil) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// containerAccess. Blob, Container, empty | ||
containerAccess := resp.headers.Get(http.CanonicalHeaderKey(ContainerAccessHeader)) | ||
|
||
defer resp.body.Close() | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same as my above comment. |
||
var out AccessPolicy | ||
err = xmlUnmarshal(resp.body, &out.SignedIdentifiersList) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
permissionResponse = &ContainerAccessResponse{} | ||
permissionResponse.AccessPolicy = out.SignedIdentifiersList | ||
permissionResponse.ContainerAccess = ContainerAccessType(containerAccess) | ||
|
||
return permissionResponse, nil | ||
} | ||
|
||
// DeleteContainer deletes the container with given name on the storage | ||
// account. If the container does not exist returns error. | ||
// | ||
|
@@ -614,8 +764,6 @@ func (b BlobStorageClient) AcquireLease(container string, name string, leaseTime | |
return returnedLeaseID, nil | ||
} | ||
|
||
// what should we return in case of HTTP 201 but no lease ID? | ||
// or it just cant happen? (brave words) | ||
return "", errors.New("LeaseID not returned") | ||
} | ||
|
||
|
@@ -1276,3 +1424,61 @@ func blobSASStringToSign(signedVersion, canonicalizedResource, signedExpiry, sig | |
} | ||
return "", errors.New("storage: not implemented SAS for versions earlier than 2013-08-15") | ||
} | ||
|
||
func generatePermissions(accessPolicy AccessPolicyDetails) (permissions string) { | ||
// generate the permissions string (rwd). | ||
// still want the end user API to have bool flags. | ||
permissions = "" | ||
|
||
if accessPolicy.CanRead { | ||
permissions += "r" | ||
} | ||
|
||
if accessPolicy.CanWrite { | ||
permissions += "w" | ||
} | ||
|
||
if accessPolicy.CanDelete { | ||
permissions += "d" | ||
} | ||
|
||
return permissions | ||
} | ||
|
||
// convertAccessPolicyToXMLStructs converts between AccessPolicyDetails which is a struct better for API usage to the | ||
// AccessPolicy struct which will get converted to XML. | ||
func convertAccessPolicyToXMLStructs(accessPolicy AccessPolicyDetails) SignedIdentifiers { | ||
return SignedIdentifiers{ | ||
SignedIdentifiers: []SignedIdentifier{ | ||
{ | ||
ID: accessPolicy.ID, | ||
AccessPolicy: AccessPolicyDetailsXML{ | ||
StartTime: accessPolicy.StartTime.Round(time.Second), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Last comment. In think it would be better if |
||
ExpiryTime: accessPolicy.ExpiryTime.Round(time.Second), | ||
Permission: generatePermissions(accessPolicy), | ||
}, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
// generateAccessPolicy generates the XML access policy used as the payload for SetContainerPermissions. | ||
func generateAccessPolicy(accessPolicy AccessPolicyDetails) (accessPolicyXML string, err error) { | ||
|
||
if accessPolicy.ID != "" { | ||
signedIdentifiers := convertAccessPolicyToXMLStructs(accessPolicy) | ||
body, _, err := xmlMarshal(signedIdentifiers) | ||
if err != nil { | ||
return "", err | ||
} | ||
|
||
xmlByteArray, err := ioutil.ReadAll(body) | ||
if err != nil { | ||
return "", err | ||
} | ||
accessPolicyXML = string(xmlByteArray) | ||
return accessPolicyXML, nil | ||
} | ||
|
||
return "", nil | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -678,6 +678,115 @@ func (s *StorageBlobSuite) TestSetBlobProperties(c *chk.C) { | |
c.Check(mPut.ContentLanguage, chk.Equals, props.ContentLanguage) | ||
} | ||
|
||
func (s *StorageBlobSuite) createContainerPermissions(accessType ContainerAccessType, | ||
timeout int, leaseID string, ID string, canRead bool, | ||
canWrite bool, canDelete bool) ContainerPermissions { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add a new line for readability |
||
perms := ContainerPermissions{} | ||
perms.AccessOptions.ContainerAccess = accessType | ||
perms.AccessOptions.Timeout = timeout | ||
perms.AccessOptions.LeaseID = leaseID | ||
|
||
if ID != "" { | ||
perms.AccessPolicy.ID = ID | ||
perms.AccessPolicy.StartTime = time.Now().UTC().Round(time.Second) | ||
perms.AccessPolicy.ExpiryTime = time.Now().UTC().Add(time.Hour * 10).Round(time.Second) | ||
perms.AccessPolicy.CanRead = canRead | ||
perms.AccessPolicy.CanWrite = canWrite | ||
perms.AccessPolicy.CanDelete = canDelete | ||
} | ||
|
||
return perms | ||
} | ||
|
||
func (s *StorageBlobSuite) TestSetContainerPermissionsWithTimeoutSuccessfully(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
||
c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil) | ||
defer cli.deleteContainer(cnt) | ||
|
||
perms := s.createContainerPermissions(ContainerAccessTypeBlob, 30, "", "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTa=", true, true, true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a question, where does the ID come from? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just made it up (well stole from a previous test). Just needed to be a unique string... and that's pretty unique :) |
||
|
||
err := cli.SetContainerPermissions(cnt, perms) | ||
c.Assert(err, chk.IsNil) | ||
} | ||
|
||
func (s *StorageBlobSuite) TestSetContainerPermissionsSuccessfully(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
||
c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil) | ||
defer cli.deleteContainer(cnt) | ||
|
||
perms := s.createContainerPermissions(ContainerAccessTypeBlob, 0, "", "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTa=", true, true, true) | ||
|
||
err := cli.SetContainerPermissions(cnt, perms) | ||
c.Assert(err, chk.IsNil) | ||
} | ||
|
||
func (s *StorageBlobSuite) TestSetThenGetContainerPermissionsSuccessfully(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
||
c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil) | ||
defer cli.deleteContainer(cnt) | ||
|
||
perms := s.createContainerPermissions(ContainerAccessTypeBlob, 0, "", "MTIzNDU2Nzg5MDEyMzQ1Njc4OTAxMjM0NTY3ODkwMTa=", true, true, true) | ||
|
||
err := cli.SetContainerPermissions(cnt, perms) | ||
c.Assert(err, chk.IsNil) | ||
|
||
returnedPerms, err := cli.GetContainerPermissions(cnt, 0, "") | ||
c.Assert(err, chk.IsNil) | ||
|
||
// check container permissions itself. | ||
c.Assert(returnedPerms.ContainerAccess, chk.Equals, perms.AccessOptions.ContainerAccess) | ||
|
||
// now check policy set. | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers, chk.HasLen, 1) | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers[0].ID, chk.Equals, perms.AccessPolicy.ID) | ||
|
||
// test timestamps down the minutes | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers[0].AccessPolicy.StartTime.Format(time.RFC1123), chk.Equals, perms.AccessPolicy.StartTime.Format(time.RFC1123)) | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers[0].AccessPolicy.ExpiryTime.Format(time.RFC1123), chk.Equals, perms.AccessPolicy.ExpiryTime.Format(time.RFC1123)) | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers[0].AccessPolicy.Permission, chk.Equals, "rwd") | ||
} | ||
|
||
func (s *StorageBlobSuite) TestSetContainerPermissionsOnlySuccessfully(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
||
c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil) | ||
defer cli.deleteContainer(cnt) | ||
|
||
perms := s.createContainerPermissions(ContainerAccessTypeBlob, 0, "", "", true, true, true) | ||
|
||
err := cli.SetContainerPermissions(cnt, perms) | ||
c.Assert(err, chk.IsNil) | ||
} | ||
|
||
func (s *StorageBlobSuite) TestSetThenGetContainerPermissionsOnlySuccessfully(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
||
c.Assert(cli.CreateContainer(cnt, ContainerAccessTypePrivate), chk.IsNil) | ||
defer cli.deleteContainer(cnt) | ||
|
||
perms := s.createContainerPermissions(ContainerAccessTypeBlob, 0, "", "", true, true, true) | ||
|
||
err := cli.SetContainerPermissions(cnt, perms) | ||
c.Assert(err, chk.IsNil) | ||
|
||
returnedPerms, err := cli.GetContainerPermissions(cnt, 0, "") | ||
c.Assert(err, chk.IsNil) | ||
|
||
// check container permissions itself. | ||
c.Assert(returnedPerms.ContainerAccess, chk.Equals, perms.AccessOptions.ContainerAccess) | ||
|
||
// now check there are NO policies set | ||
c.Assert(returnedPerms.AccessPolicy.SignedIdentifiers, chk.HasLen, 0) | ||
} | ||
|
||
func (s *StorageBlobSuite) TestAcquireLeaseWithNoProposedLeaseID(c *chk.C) { | ||
cli := getBlobClient(c) | ||
cnt := randContainer() | ||
|
@@ -690,6 +799,7 @@ func (s *StorageBlobSuite) TestAcquireLeaseWithNoProposedLeaseID(c *chk.C) { | |
|
||
_, err := cli.AcquireLease(cnt, blob, 30, "") | ||
c.Assert(err, chk.NotNil) | ||
|
||
} | ||
|
||
func (s *StorageBlobSuite) TestAcquireLeaseWithProposedLeaseID(c *chk.C) { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we need to capture and return the error object returned from Close() if it's not nil. See PR #431 for an example (util.go).