From 0c3009911e8f944c2bee2df8f69b048e64ad2ed8 Mon Sep 17 00:00:00 2001 From: "changlin.shi" Date: Sun, 11 Dec 2022 18:25:59 +0800 Subject: [PATCH] optimize readability Signed-off-by: changlin.shi --- weed/s3api/auth_credentials_subscribe.go | 4 +- weed/s3api/bucket_metadata.go | 65 +- weed/s3api/bucket_metadata_test.go | 10 +- weed/s3api/filer_multipart.go | 24 +- weed/s3api/s3_constants/acp_ownership.go | 4 +- weed/s3api/s3_constants/extend_key.go | 7 +- weed/s3api/s3_constants/header.go | 5 +- weed/s3api/s3acl/acl_helper.go | 373 +++-- weed/s3api/s3acl/acl_helper_test.go | 1391 +++++++++++++---- weed/s3api/s3api_acp.go | 332 ++-- weed/s3api/s3api_bucket_handlers.go | 51 +- weed/s3api/s3api_object_handlers.go | 14 +- weed/s3api/s3api_object_multipart_handlers.go | 63 +- weed/s3api/s3api_server.go | 10 +- weed/s3api/s3err/s3api_errors.go | 14 +- weed/server/filer_server_handlers_read.go | 17 +- 16 files changed, 1703 insertions(+), 681 deletions(-) diff --git a/weed/s3api/auth_credentials_subscribe.go b/weed/s3api/auth_credentials_subscribe.go index 377cf2728..c9dd1b086 100644 --- a/weed/s3api/auth_credentials_subscribe.go +++ b/weed/s3api/auth_credentials_subscribe.go @@ -70,10 +70,10 @@ func (s3a *S3ApiServer) onBucketMetadataChange(dir string, oldEntry *filer_pb.En if dir == s3a.option.BucketsPath { if newEntry != nil { s3a.bucketRegistry.LoadBucketMetadata(newEntry) - glog.V(0).Infof("updated bucketMetadata %s/%s", dir, newEntry) + glog.V(1).Infof("updated bucketMetadata %s/%s", dir, newEntry) } else { s3a.bucketRegistry.RemoveBucketMetadata(oldEntry) - glog.V(0).Infof("remove bucketMetadata %s/%s", dir, newEntry) + glog.V(1).Infof("remove bucketMetadata %s/%s", dir, newEntry) } } return nil diff --git a/weed/s3api/bucket_metadata.go b/weed/s3api/bucket_metadata.go index f4088e6b3..412076da3 100644 --- a/weed/s3api/bucket_metadata.go +++ b/weed/s3api/bucket_metadata.go @@ -107,7 +107,6 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_ } } - //access control policy //owner acpOwnerBytes, ok := entry.Extended[s3_constants.ExtAmzOwnerKey] if ok && len(acpOwnerBytes) > 0 { @@ -122,17 +121,31 @@ func buildBucketMetadata(accountManager *s3account.AccountManager, entry *filer_ } } } + //grants acpGrantsBytes, ok := entry.Extended[s3_constants.ExtAmzAclKey] - if ok && len(acpGrantsBytes) > 0 { - var grants []*s3.Grant - err := json.Unmarshal(acpGrantsBytes, &grants) - if err == nil { - bucketMetadata.Acl = grants - } else { - glog.Warningf("Unmarshal ACP grants: %s(%v), bucket: %s", string(acpGrantsBytes), err, bucketMetadata.Name) + if ok { + if len(acpGrantsBytes) > 0 { + var grants []*s3.Grant + err := json.Unmarshal(acpGrantsBytes, &grants) + if err == nil { + bucketMetadata.Acl = grants + } else { + glog.Warningf("Unmarshal ACP grants: %s(%v), bucket: %s", string(acpGrantsBytes), err, bucketMetadata.Name) + } + } + } else { + bucketMetadata.Acl = []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: bucketMetadata.Owner.ID, + }, + Permission: &s3_constants.PermissionFullControl, + }, } } + } return bucketMetadata } @@ -143,17 +156,12 @@ func (r *BucketRegistry) RemoveBucketMetadata(entry *filer_pb.Entry) { } func (r *BucketRegistry) GetBucketMetadata(bucketName string) (*BucketMetaData, s3err.ErrorCode) { - r.metadataCacheLock.RLock() - bucketMetadata, ok := r.metadataCache[bucketName] - r.metadataCacheLock.RUnlock() - if ok { + bucketMetadata := r.getMetadataCache(bucketName) + if bucketMetadata != nil { return bucketMetadata, s3err.ErrNone } - r.notFoundLock.RLock() - _, ok = r.notFound[bucketName] - r.notFoundLock.RUnlock() - if ok { + if r.isNotFound(bucketName) { return nil, s3err.ErrNoSuchBucket } @@ -172,10 +180,8 @@ func (r *BucketRegistry) LoadBucketMetadataFromFiler(bucketName string) (*Bucket defer r.notFoundLock.Unlock() //check if already exists - r.metadataCacheLock.RLock() - bucketMetaData, ok := r.metadataCache[bucketName] - r.metadataCacheLock.RUnlock() - if ok { + bucketMetaData := r.getMetadataCache(bucketName) + if bucketMetaData != nil { return bucketMetaData, s3err.ErrNone } @@ -184,6 +190,7 @@ func (r *BucketRegistry) LoadBucketMetadataFromFiler(bucketName string) (*Bucket if err != nil { if err == filer_pb.ErrNotFound { // The bucket doesn't actually exist and should no longer loaded from the filer + glog.Warning("bucket not found in filer: ", bucketName) r.notFound[bucketName] = struct{}{} return nil, s3err.ErrNoSuchBucket } @@ -192,6 +199,15 @@ func (r *BucketRegistry) LoadBucketMetadataFromFiler(bucketName string) (*Bucket return bucketMetadata, s3err.ErrNone } +func (r *BucketRegistry) getMetadataCache(bucket string) *BucketMetaData { + r.metadataCacheLock.RLock() + defer r.metadataCacheLock.RUnlock() + if cache, ok := r.metadataCache[bucket]; ok { + return cache + } + return nil +} + func (r *BucketRegistry) setMetadataCache(metadata *BucketMetaData) { r.metadataCacheLock.Lock() defer r.metadataCacheLock.Unlock() @@ -204,10 +220,11 @@ func (r *BucketRegistry) removeMetadataCache(bucket string) { delete(r.metadataCache, bucket) } -func (r *BucketRegistry) markNotFound(bucket string) { - r.notFoundLock.Lock() - defer r.notFoundLock.Unlock() - r.notFound[bucket] = struct{}{} +func (r *BucketRegistry) isNotFound(bucket string) bool { + r.notFoundLock.RLock() + defer r.notFoundLock.RUnlock() + _, ok := r.notFound[bucket] + return ok } func (r *BucketRegistry) unMarkNotFound(bucket string) { diff --git a/weed/s3api/bucket_metadata_test.go b/weed/s3api/bucket_metadata_test.go index f852a272a..cd6aef78f 100644 --- a/weed/s3api/bucket_metadata_test.go +++ b/weed/s3api/bucket_metadata_test.go @@ -86,7 +86,7 @@ var tcs = []*BucketMetadataTestCase{ { badEntry, &BucketMetaData{ Name: badEntry.Name, - ObjectOwnership: s3_constants.DefaultOwnershipForExists, + ObjectOwnership: s3_constants.DefaultObjectOwnership, Owner: &s3.Owner{ DisplayName: &s3account.AccountAdmin.Name, ID: &s3account.AccountAdmin.Id, @@ -108,7 +108,7 @@ var tcs = []*BucketMetadataTestCase{ { ownershipEmptyStr, &BucketMetaData{ Name: ownershipEmptyStr.Name, - ObjectOwnership: s3_constants.DefaultOwnershipForExists, + ObjectOwnership: s3_constants.DefaultObjectOwnership, Owner: &s3.Owner{ DisplayName: &s3account.AccountAdmin.Name, ID: &s3account.AccountAdmin.Id, @@ -130,7 +130,7 @@ var tcs = []*BucketMetadataTestCase{ { acpEmptyStr, &BucketMetaData{ Name: acpEmptyStr.Name, - ObjectOwnership: s3_constants.DefaultOwnershipForExists, + ObjectOwnership: s3_constants.DefaultObjectOwnership, Owner: &s3.Owner{ DisplayName: &s3account.AccountAdmin.Name, ID: &s3account.AccountAdmin.Id, @@ -141,7 +141,7 @@ var tcs = []*BucketMetadataTestCase{ { acpEmptyObject, &BucketMetaData{ Name: acpEmptyObject.Name, - ObjectOwnership: s3_constants.DefaultOwnershipForExists, + ObjectOwnership: s3_constants.DefaultObjectOwnership, Owner: &s3.Owner{ DisplayName: &s3account.AccountAdmin.Name, ID: &s3account.AccountAdmin.Id, @@ -152,7 +152,7 @@ var tcs = []*BucketMetadataTestCase{ { acpOwnerNil, &BucketMetaData{ Name: acpOwnerNil.Name, - ObjectOwnership: s3_constants.DefaultOwnershipForExists, + ObjectOwnership: s3_constants.DefaultObjectOwnership, Owner: &s3.Owner{ DisplayName: &s3account.AccountAdmin.Name, ID: &s3account.AccountAdmin.Id, diff --git a/weed/s3api/filer_multipart.go b/weed/s3api/filer_multipart.go index 414ba4bb2..fefddf4b2 100644 --- a/weed/s3api/filer_multipart.go +++ b/weed/s3api/filer_multipart.go @@ -5,6 +5,7 @@ import ( "encoding/xml" "fmt" "github.com/google/uuid" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3err" "golang.org/x/exp/slices" "math" @@ -27,7 +28,7 @@ type InitiateMultipartUploadResult struct { s3.CreateMultipartUploadOutput } -func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInput) (output *InitiateMultipartUploadResult, code s3err.ErrorCode) { +func (s3a *S3ApiServer) createMultipartUpload(initiatorId string, input *s3.CreateMultipartUploadInput) (output *InitiateMultipartUploadResult, code s3err.ErrorCode) { glog.V(2).Infof("createMultipartUpload input %v", input) @@ -46,6 +47,7 @@ func (s3a *S3ApiServer) createMultipartUpload(input *s3.CreateMultipartUploadInp if input.ContentType != nil { entry.Attributes.Mime = *input.ContentType } + entry.Extended[s3_constants.ExtAmzMultipartInitiator] = []byte(initiatorId) }); err != nil { glog.Errorf("NewMultipartUpload error: %v", err) return nil, s3err.ErrInternalError @@ -236,7 +238,7 @@ type ListMultipartUploadsResult struct { Upload []*s3.MultipartUpload `locationName:"Upload" type:"list" flattened:"true"` } -func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput) (output *ListMultipartUploadsResult, code s3err.ErrorCode) { +func (s3a *S3ApiServer) listMultipartUploads(bucketMetaData *BucketMetaData, input *s3.ListMultipartUploadsInput) (output *ListMultipartUploadsResult, code s3err.ErrorCode) { // https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html glog.V(2).Infof("listMultipartUploads input %v", input) @@ -267,9 +269,27 @@ func (s3a *S3ApiServer) listMultipartUploads(input *s3.ListMultipartUploadsInput if *input.Prefix != "" && !strings.HasPrefix(key, *input.Prefix) { continue } + initiatorId := string(entry.Extended[s3_constants.ExtAmzMultipartInitiator]) + if initiatorId == "" { + initiatorId = *bucketMetaData.Owner.ID + } + initiatorDisplayName := s3a.accountManager.IdNameMapping[initiatorId] + ownerId := string(entry.Extended[s3_constants.ExtAmzOwnerKey]) + if ownerId == "" { + ownerId = *bucketMetaData.Owner.ID + } + ownerDisplayName := s3a.accountManager.IdNameMapping[ownerId] output.Upload = append(output.Upload, &s3.MultipartUpload{ Key: objectKey(aws.String(key)), UploadId: aws.String(entry.Name), + Owner: &s3.Owner{ + ID: &initiatorId, + DisplayName: &ownerDisplayName, + }, + Initiator: &s3.Initiator{ + ID: &initiatorId, + DisplayName: &initiatorDisplayName, + }, }) uploadsCount += 1 } diff --git a/weed/s3api/s3_constants/acp_ownership.go b/weed/s3api/s3_constants/acp_ownership.go index e11e95935..7a9f508a9 100644 --- a/weed/s3api/s3_constants/acp_ownership.go +++ b/weed/s3api/s3_constants/acp_ownership.go @@ -4,9 +4,7 @@ var ( OwnershipBucketOwnerPreferred = "BucketOwnerPreferred" OwnershipObjectWriter = "ObjectWriter" OwnershipBucketOwnerEnforced = "BucketOwnerEnforced" - - DefaultOwnershipForCreate = OwnershipObjectWriter - DefaultOwnershipForExists = OwnershipBucketOwnerEnforced + DefaultObjectOwnership = OwnershipBucketOwnerEnforced ) func ValidateOwnership(ownership string) bool { diff --git a/weed/s3api/s3_constants/extend_key.go b/weed/s3api/s3_constants/extend_key.go index f78983a99..d69af2698 100644 --- a/weed/s3api/s3_constants/extend_key.go +++ b/weed/s3api/s3_constants/extend_key.go @@ -1,7 +1,8 @@ package s3_constants const ( - ExtAmzOwnerKey = "Seaweed-X-Amz-Owner" - ExtAmzAclKey = "Seaweed-X-Amz-Acl" - ExtOwnershipKey = "Seaweed-X-Amz-Ownership" + ExtAmzOwnerKey = "Seaweed-X-Amz-Owner" + ExtAmzMultipartInitiator = "Seaweed-X-Amz-Multipart-Initiator" + ExtAmzAclKey = "Seaweed-X-Amz-Acl" + ExtOwnershipKey = "Seaweed-X-Amz-Ownership" ) diff --git a/weed/s3api/s3_constants/header.go b/weed/s3api/s3_constants/header.go index e4581bff6..9d22b8e4f 100644 --- a/weed/s3api/s3_constants/header.go +++ b/weed/s3api/s3_constants/header.go @@ -37,8 +37,9 @@ const ( AmzObjectTaggingDirective = "X-Amz-Tagging-Directive" AmzTagCount = "x-amz-tagging-count" - X_SeaweedFS_Header_Directory_Key = "x-seaweedfs-is-directory-key" - XSeaweedFSHeaderAmzBucketOwnerId = "x-seaweedfs-amz-bucket-owner-id" + X_SeaweedFS_Header_Directory_Key = "x-seaweedfs-is-directory-key" + XSeaweedFSHeaderAmzBucketOwnerId = "x-seaweedfs-amz-bucket-owner-id" + XSeaweedFSHeaderAmzBucketAccessDenied = "x-seaweedfs-amz-bucket-access-denied" // S3 ACL headers AmzCannedAcl = "X-Amz-Acl" diff --git a/weed/s3api/s3acl/acl_helper.go b/weed/s3api/s3acl/acl_helper.go index ef56da313..942b55797 100644 --- a/weed/s3api/s3acl/acl_helper.go +++ b/weed/s3api/s3acl/acl_helper.go @@ -3,6 +3,7 @@ package s3acl import ( "encoding/json" "encoding/xml" + "fmt" "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil" "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/filer" @@ -16,6 +17,8 @@ import ( "strings" ) +var customAclHeaders = []string{s3_constants.AmzAclFullControl, s3_constants.AmzAclRead, s3_constants.AmzAclReadAcp, s3_constants.AmzAclWrite, s3_constants.AmzAclWriteAcp} + // GetAccountId get AccountId from request headers, AccountAnonymousId will be return if not presen func GetAccountId(r *http.Request) string { id := r.Header.Get(s3_constants.AmzAccountId) @@ -26,11 +29,36 @@ func GetAccountId(r *http.Request) string { } } -// ExtractAcl extracts the acl from the request body, or from the header if request body is empty -func ExtractAcl(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, ownerId, accountId string) (grants []*s3.Grant, errCode s3err.ErrorCode) { - if r.Body != nil && r.Body != http.NoBody { - defer util.CloseRequest(r) +// ValidateAccount validate weather request account id is allowed to access +func ValidateAccount(requestAccountId string, allowedAccounts ...string) bool { + for _, allowedAccount := range allowedAccounts { + if requestAccountId == allowedAccount { + return true + } + } + return false +} + +// ExtractBucketAcl extracts the acl from the request body, or from the header if request body is empty +func ExtractBucketAcl(r *http.Request, accountManager *s3account.AccountManager, objectOwnership, bucketOwnerId, requestAccountId string, createBucket bool) (grants []*s3.Grant, errCode s3err.ErrorCode) { + cannedAclPresent := false + if r.Header.Get(s3_constants.AmzCannedAcl) != "" { + cannedAclPresent = true + } + customAclPresent := false + for _, customAclHeader := range customAclHeaders { + if r.Header.Get(customAclHeader) != "" { + customAclPresent = true + break + } + } + // AccessControlList body is not support when create object/bucket + if !createBucket && r.Body != nil && r.Body != http.NoBody { + defer util.CloseRequest(r) + if cannedAclPresent || customAclPresent { + return nil, s3err.ErrUnexpectedContent + } var acp s3.AccessControlPolicy err := xmlutil.UnmarshalXML(&acp, xml.NewDecoder(r.Body), "") if err != nil || acp.Owner == nil || acp.Owner.ID == nil { @@ -38,116 +66,127 @@ func ExtractAcl(r *http.Request, accountManager *s3account.AccountManager, owner } //owner should present && owner is immutable - if *acp.Owner.ID != ownerId { - glog.V(3).Infof("set acl denied! owner account is not consistent, request account id: %s, expect account id: %s", accountId, ownerId) + if *acp.Owner.ID == "" || *acp.Owner.ID != bucketOwnerId { + glog.V(3).Infof("set acl denied! owner account is not consistent, request account id: %s, expect account id: %s", *acp.Owner.ID, bucketOwnerId) return nil, s3err.ErrAccessDenied } - - return ValidateAndTransferGrants(accountManager, acp.Grants) + grants = acp.Grants } else { - _, grants, errCode = ParseAndValidateAclHeadersOrElseDefault(r, accountManager, ownership, bucketOwnerId, accountId, true) - return grants, errCode + if cannedAclPresent && customAclPresent { + return nil, s3err.ErrInvalidRequest + } + if cannedAclPresent { + grants, errCode = ExtractBucketCannedAcl(r, requestAccountId) + } else if customAclPresent { + grants, errCode = ExtractCustomAcl(r) + } + if errCode != s3err.ErrNone { + return nil, errCode + } } -} - -// ParseAndValidateAclHeadersOrElseDefault will callParseAndValidateAclHeaders to get Grants, if empty, it will return Grant that grant `accountId` with `FullControl` permission -func ParseAndValidateAclHeadersOrElseDefault(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { - ownerId, grants, errCode = ParseAndValidateAclHeaders(r, accountManager, ownership, bucketOwnerId, accountId, putAcl) + errCode = ValidateObjectOwnershipAndGrants(objectOwnership, bucketOwnerId, grants) if errCode != s3err.ErrNone { - return + return nil, errCode } - if len(grants) == 0 { - //if no acl(both customAcl and cannedAcl) specified, grant accountId(object writer) with full control permission - grants = append(grants, &s3.Grant{ - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &accountId, - }, - Permission: &s3_constants.PermissionFullControl, - }) + grants, errCode = ValidateAndTransferGrants(accountManager, grants) + if errCode != s3err.ErrNone { + return nil, errCode } - return + return grants, s3err.ErrNone } -// ParseAndValidateAclHeaders parse and validate acl from header -func ParseAndValidateAclHeaders(r *http.Request, accountManager *s3account.AccountManager, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { - ownerId, grants, errCode = ParseAclHeaders(r, ownership, bucketOwnerId, accountId, putAcl) - if errCode != s3err.ErrNone { - return +// ExtractObjectAcl extracts the acl from the request body, or from the header if request body is empty +func ExtractObjectAcl(r *http.Request, accountManager *s3account.AccountManager, objectOwnership, bucketOwnerId, requestAccountId string, createObject bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { + cannedAclPresent := false + if r.Header.Get(s3_constants.AmzCannedAcl) != "" { + cannedAclPresent = true } - if len(grants) > 0 { - grants, errCode = ValidateAndTransferGrants(accountManager, grants) + customAclPresent := false + for _, customAclHeader := range customAclHeaders { + if r.Header.Get(customAclHeader) != "" { + customAclPresent = true + break + } } - return -} -// ParseAclHeaders parse acl headers -// When `putAcl` is true, only `CannedAcl` is parsed, such as `PutBucketAcl` or `PutObjectAcl` -// is requested, `CustomAcl` is parsed from the request body not from headers, and only if the -// request body is empty, `CannedAcl` is parsed from the header, and will not parse `CustomAcl` from the header -// -// Since `CustomAcl` has higher priority, it will be parsed first; if `CustomAcl` does not exist, `CannedAcl` will be parsed -func ParseAclHeaders(r *http.Request, ownership, bucketOwnerId, accountId string, putAcl bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { - if !putAcl { - errCode = ParseCustomAclHeaders(r, &grants) + // AccessControlList body is not support when create object/bucket + if !createObject && r.Body != nil && r.Body != http.NoBody { + defer util.CloseRequest(r) + if cannedAclPresent || customAclPresent { + return "", nil, s3err.ErrUnexpectedContent + } + var acp s3.AccessControlPolicy + err := xmlutil.UnmarshalXML(&acp, xml.NewDecoder(r.Body), "") + if err != nil || acp.Owner == nil || acp.Owner.ID == nil { + return "", nil, s3err.ErrInvalidRequest + } + + //owner should present && owner is immutable + if *acp.Owner.ID == "" { + glog.V(1).Infof("Access denied! The owner id is required when specifying grants using AccessControlList") + return "", nil, s3err.ErrAccessDenied + } + ownerId = *acp.Owner.ID + grants = acp.Grants + } else { + if cannedAclPresent && customAclPresent { + return "", nil, s3err.ErrInvalidRequest + } + if cannedAclPresent { + ownerId, grants, errCode = ExtractObjectCannedAcl(r, objectOwnership, bucketOwnerId, requestAccountId, createObject) + } else { + grants, errCode = ExtractCustomAcl(r) + } if errCode != s3err.ErrNone { return "", nil, errCode } } - if len(grants) > 0 { - return accountId, grants, s3err.ErrNone - } - - cannedAcl := r.Header.Get(s3_constants.AmzCannedAcl) - if len(cannedAcl) == 0 { - return accountId, grants, s3err.ErrNone - } - - //if canned acl specified, parse cannedAcl (lower priority to custom acl) - ownerId, grants, errCode = ParseCannedAclHeader(ownership, bucketOwnerId, accountId, cannedAcl, putAcl) + errCode = ValidateObjectOwnershipAndGrants(objectOwnership, bucketOwnerId, grants) if errCode != s3err.ErrNone { return "", nil, errCode } + grants, errCode = ValidateAndTransferGrants(accountManager, grants) return ownerId, grants, errCode } -func ParseCustomAclHeaders(r *http.Request, grants *[]*s3.Grant) s3err.ErrorCode { - customAclHeaders := []string{s3_constants.AmzAclFullControl, s3_constants.AmzAclRead, s3_constants.AmzAclReadAcp, s3_constants.AmzAclWrite, s3_constants.AmzAclWriteAcp} +func ExtractCustomAcl(r *http.Request) ([]*s3.Grant, s3err.ErrorCode) { var errCode s3err.ErrorCode + var grants []*s3.Grant for _, customAclHeader := range customAclHeaders { headerValue := r.Header.Get(customAclHeader) switch customAclHeader { case s3_constants.AmzAclRead: - errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionRead, grants) + errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionRead, &grants) case s3_constants.AmzAclWrite: - errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWrite, grants) + errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWrite, &grants) case s3_constants.AmzAclReadAcp: - errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionReadAcp, grants) + errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionReadAcp, &grants) case s3_constants.AmzAclWriteAcp: - errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWriteAcp, grants) + errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionWriteAcp, &grants) case s3_constants.AmzAclFullControl: - errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionFullControl, grants) + errCode = ParseCustomAclHeader(headerValue, s3_constants.PermissionFullControl, &grants) + default: + errCode = s3err.ErrInvalidAclArgument } if errCode != s3err.ErrNone { - return errCode + return nil, errCode } } - return s3err.ErrNone + return grants, s3err.ErrNone } func ParseCustomAclHeader(headerValue, permission string, grants *[]*s3.Grant) s3err.ErrorCode { if len(headerValue) > 0 { - split := strings.Split(headerValue, ", ") + split := strings.Split(headerValue, ",") for _, grantStr := range split { kv := strings.Split(grantStr, "=") if len(kv) != 2 { return s3err.ErrInvalidRequest } - switch kv[0] { + switch strings.TrimSpace(kv[0]) { case "id": - var accountId string - _ = json.Unmarshal([]byte(kv[1]), &accountId) + accountId := decodeGranteeValue(kv[1]) *grants = append(*grants, &s3.Grant{ Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeCanonicalUser, @@ -156,8 +195,7 @@ func ParseCustomAclHeader(headerValue, permission string, grants *[]*s3.Grant) s Permission: &permission, }) case "emailAddress": - var emailAddress string - _ = json.Unmarshal([]byte(kv[1]), &emailAddress) + emailAddress := decodeGranteeValue(kv[1]) *grants = append(*grants, &s3.Grant{ Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeAmazonCustomerByEmail, @@ -167,7 +205,7 @@ func ParseCustomAclHeader(headerValue, permission string, grants *[]*s3.Grant) s }) case "uri": var groupName string - _ = json.Unmarshal([]byte(kv[1]), &groupName) + groupName = decodeGranteeValue(kv[1]) *grants = append(*grants, &s3.Grant{ Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, @@ -179,17 +217,66 @@ func ParseCustomAclHeader(headerValue, permission string, grants *[]*s3.Grant) s } } return s3err.ErrNone +} +func decodeGranteeValue(value string) (result string) { + if !strings.HasPrefix(value, "\"") { + return value + } + _ = json.Unmarshal([]byte(value), &result) + if result == "" { + result = value + } + return result } -func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl string, putAcl bool) (ownerId string, grants []*s3.Grant, err s3err.ErrorCode) { +// ExtractBucketCannedAcl parse bucket canned acl, includes: 'private'|'public-read'|'public-read-write'|'authenticated-read' +func ExtractBucketCannedAcl(request *http.Request, requestAccountId string) (grants []*s3.Grant, err s3err.ErrorCode) { + cannedAcl := request.Header.Get(s3_constants.AmzCannedAcl) + if cannedAcl == "" { + return grants, s3err.ErrNone + } err = s3err.ErrNone - ownerId = accountId + objectWriterFullControl := &s3.Grant{ + Grantee: &s3.Grantee{ + ID: &requestAccountId, + Type: &s3_constants.GrantTypeCanonicalUser, + }, + Permission: &s3_constants.PermissionFullControl, + } + switch cannedAcl { + case s3_constants.CannedAclPrivate: + grants = append(grants, objectWriterFullControl) + case s3_constants.CannedAclPublicRead: + grants = append(grants, objectWriterFullControl) + grants = append(grants, s3_constants.PublicRead...) + case s3_constants.CannedAclPublicReadWrite: + grants = append(grants, objectWriterFullControl) + grants = append(grants, s3_constants.PublicReadWrite...) + case s3_constants.CannedAclAuthenticatedRead: + grants = append(grants, objectWriterFullControl) + grants = append(grants, s3_constants.AuthenticatedRead...) + default: + err = s3err.ErrInvalidAclArgument + } + return +} - //objectWrite automatically has full control on current object +// ExtractObjectCannedAcl parse object canned acl, includes: 'private'|'public-read'|'public-read-write'|'authenticated-read'|'aws-exec-read'|'bucket-owner-read'|'bucket-owner-full-control' +func ExtractObjectCannedAcl(request *http.Request, objectOwnership, bucketOwnerId, requestAccountId string, createObject bool) (ownerId string, grants []*s3.Grant, errCode s3err.ErrorCode) { + if createObject { + ownerId = requestAccountId + } + + cannedAcl := request.Header.Get(s3_constants.AmzCannedAcl) + if cannedAcl == "" { + return ownerId, grants, s3err.ErrNone + } + + errCode = s3err.ErrNone objectWriterFullControl := &s3.Grant{ Grantee: &s3.Grantee{ - ID: &accountId, + ID: &requestAccountId, Type: &s3_constants.GrantTypeCanonicalUser, }, Permission: &s3_constants.PermissionFullControl, @@ -212,7 +299,7 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s grants = append(grants, s3_constants.LogDeliveryWrite...) case s3_constants.CannedAclBucketOwnerRead: grants = append(grants, objectWriterFullControl) - if bucketOwnerId != "" && bucketOwnerId != accountId { + if requestAccountId != bucketOwnerId { grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ @@ -225,7 +312,7 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s case s3_constants.CannedAclBucketOwnerFullControl: if bucketOwnerId != "" { // if set ownership to 'BucketOwnerPreferred' when upload object, the bucket owner will be the object owner - if !putAcl && bucketOwnership == s3_constants.OwnershipBucketOwnerPreferred { + if createObject && objectOwnership == s3_constants.OwnershipBucketOwnerPreferred { ownerId = bucketOwnerId grants = append(grants, &s3.Grant{ @@ -237,7 +324,7 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s }) } else { grants = append(grants, objectWriterFullControl) - if accountId != bucketOwnerId { + if requestAccountId != bucketOwnerId { grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ @@ -249,15 +336,13 @@ func ParseCannedAclHeader(bucketOwnership, bucketOwnerId, accountId, cannedAcl s } } } - case s3_constants.CannedAclAwsExecRead: - err = s3err.ErrNotImplemented default: - err = s3err.ErrInvalidRequest + errCode = s3err.ErrInvalidAclArgument } return } -// ValidateAndTransferGrants validate grant & transfer Email-Grant to Id-Grant +// ValidateAndTransferGrants validate grant entity exists and transfer Email-Grant to Id-Grant func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants []*s3.Grant) ([]*s3.Grant, s3err.ErrorCode) { var result []*s3.Grant for _, grant := range grants { @@ -314,15 +399,43 @@ func ValidateAndTransferGrants(accountManager *s3account.AccountManager, grants return result, s3err.ErrNone } -// DetermineReqGrants generates the grant set (Grants) according to accountId and reqPermission. -func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) { +// ValidateObjectOwnershipAndGrants validate if grants equals OwnerFullControl when 'ObjectOwnership' is 'BucketOwnerEnforced' +func ValidateObjectOwnershipAndGrants(objectOwnership, bucketOwnerId string, grants []*s3.Grant) s3err.ErrorCode { + if len(grants) == 0 { + return s3err.ErrNone + } + if objectOwnership == "" { + objectOwnership = s3_constants.DefaultObjectOwnership + } + if objectOwnership != s3_constants.OwnershipBucketOwnerEnforced { + return s3err.ErrNone + } + if len(grants) > 1 { + return s3err.AccessControlListNotSupported + } + + bucketOwnerFullControlGrant := &s3.Grant{ + Permission: &s3_constants.PermissionFullControl, + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + } + if GrantEquals(bucketOwnerFullControlGrant, grants[0]) { + return s3err.ErrNone + } + return s3err.AccessControlListNotSupported +} + +// DetermineRequiredGrants generates the grant set (Grants) according to accountId and reqPermission. +func DetermineRequiredGrants(accountId, permission string) (grants []*s3.Grant) { // group grantee (AllUsers) grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAllUsers, }, - Permission: &aclAction, + Permission: &permission, }) grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ @@ -338,7 +451,7 @@ func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) { Type: &s3_constants.GrantTypeCanonicalUser, ID: &accountId, }, - Permission: &aclAction, + Permission: &permission, }) grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ @@ -355,7 +468,7 @@ func DetermineReqGrants(accountId, aclAction string) (grants []*s3.Grant) { Type: &s3_constants.GrantTypeGroup, URI: &s3_constants.GranteeGroupAuthenticatedUsers, }, - Permission: &aclAction, + Permission: &permission, }) grants = append(grants, &s3.Grant{ Grantee: &s3.Grantee{ @@ -392,7 +505,7 @@ func SetAcpGrantsHeader(r *http.Request, acpGrants []*s3.Grant) { } // GetAcpGrants return grants parsed from entry -func GetAcpGrants(entryExtended map[string][]byte) []*s3.Grant { +func GetAcpGrants(ownerId *string, entryExtended map[string][]byte) []*s3.Grant { acpBytes, ok := entryExtended[s3_constants.ExtAmzAclKey] if ok && len(acpBytes) > 0 { var grants []*s3.Grant @@ -400,31 +513,43 @@ func GetAcpGrants(entryExtended map[string][]byte) []*s3.Grant { if err == nil { return grants } + glog.Warning("grants Unmarshal error", err) + } + if ownerId == nil { + return nil + } + return []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: ownerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, } - return nil } // AssembleEntryWithAcp fill entry with owner and grants -func AssembleEntryWithAcp(objectEntry *filer_pb.Entry, objectOwner string, grants []*s3.Grant) s3err.ErrorCode { - if objectEntry.Extended == nil { - objectEntry.Extended = make(map[string][]byte) +func AssembleEntryWithAcp(filerEntry *filer_pb.Entry, ownerId string, grants []*s3.Grant) s3err.ErrorCode { + if filerEntry.Extended == nil { + filerEntry.Extended = make(map[string][]byte) } - if len(objectOwner) > 0 { - objectEntry.Extended[s3_constants.ExtAmzOwnerKey] = []byte(objectOwner) + if len(ownerId) > 0 { + filerEntry.Extended[s3_constants.ExtAmzOwnerKey] = []byte(ownerId) } else { - delete(objectEntry.Extended, s3_constants.ExtAmzOwnerKey) + delete(filerEntry.Extended, s3_constants.ExtAmzOwnerKey) } - if len(grants) > 0 { + if grants != nil { grantsBytes, err := json.Marshal(grants) if err != nil { glog.Warning("assemble acp to entry:", err) return s3err.ErrInvalidRequest } - objectEntry.Extended[s3_constants.ExtAmzAclKey] = grantsBytes + filerEntry.Extended[s3_constants.ExtAmzAclKey] = grantsBytes } else { - delete(objectEntry.Extended, s3_constants.ExtAmzAclKey) + delete(filerEntry.Extended, s3_constants.ExtAmzAclKey) } return s3err.ErrNone @@ -509,6 +634,46 @@ func GrantEquals(a, b *s3.Grant) bool { return true } +func MarshalGrantsToJson(grants []*s3.Grant) ([]byte, error) { + if len(grants) == 0 { + return nil, nil + } + var GrantsToMap []map[string]any + for _, grant := range grants { + grantee := grant.Grantee + switch *grantee.Type { + case s3_constants.GrantTypeGroup: + GrantsToMap = append(GrantsToMap, map[string]any{ + "Permission": grant.Permission, + "Grantee": map[string]any{ + "Type": grantee.Type, + "URI": grantee.URI, + }, + }) + case s3_constants.GrantTypeCanonicalUser: + GrantsToMap = append(GrantsToMap, map[string]any{ + "Permission": grant.Permission, + "Grantee": map[string]any{ + "Type": grantee.Type, + "ID": grantee.ID, + }, + }) + case s3_constants.GrantTypeAmazonCustomerByEmail: + GrantsToMap = append(GrantsToMap, map[string]any{ + "Permission": grant.Permission, + "Grantee": map[string]any{ + "Type": grantee.Type, + "EmailAddress": grantee.EmailAddress, + }, + }) + default: + return nil, fmt.Errorf("grantee type[%s] is not valid", *grantee.Type) + } + } + + return json.Marshal(GrantsToMap) +} + func GrantWithFullControl(accountId string) *s3.Grant { return &s3.Grant{ Permission: &s3_constants.PermissionFullControl, @@ -524,22 +689,22 @@ func CheckObjectAccessForReadObject(r *http.Request, w http.ResponseWriter, entr return http.StatusOK, true } - accountId := GetAccountId(r) - if len(accountId) == 0 { + requestAccountId := GetAccountId(r) + if len(requestAccountId) == 0 { glog.Warning("#checkObjectAccessForReadObject header[accountId] not exists!") return http.StatusForbidden, false } //owner access objectOwner := GetAcpOwner(entry.Extended, bucketOwnerId) - if accountId == objectOwner { + if ValidateAccount(requestAccountId, objectOwner) { return http.StatusOK, true } //find in Grants - acpGrants := GetAcpGrants(entry.Extended) + acpGrants := GetAcpGrants(nil, entry.Extended) if acpGrants != nil { - reqGrants := DetermineReqGrants(accountId, s3_constants.PermissionRead) + reqGrants := DetermineRequiredGrants(requestAccountId, s3_constants.PermissionRead) for _, requiredGrant := range reqGrants { for _, grant := range acpGrants { if GrantEquals(requiredGrant, grant) { @@ -549,6 +714,6 @@ func CheckObjectAccessForReadObject(r *http.Request, w http.ResponseWriter, entr } } - glog.V(3).Infof("acl denied! request account id: %s", accountId) + glog.V(3).Infof("acl denied! request account id: %s", requestAccountId) return http.StatusForbidden, false } diff --git a/weed/s3api/s3acl/acl_helper_test.go b/weed/s3api/s3acl/acl_helper_test.go index 713e56a26..ebf253851 100644 --- a/weed/s3api/s3acl/acl_helper_test.go +++ b/weed/s3api/s3acl/acl_helper_test.go @@ -56,326 +56,6 @@ func TestGetAccountId(t *testing.T) { } } -func TestExtractAcl(t *testing.T) { - type Case struct { - id int - resultErrCode, expectErrCode s3err.ErrorCode - resultGrants, expectGrants []*s3.Grant - } - testCases := make([]*Case, 0) - accountAdminId := "admin" - - { - //case1 (good case) - //parse acp from request body - req := &http.Request{ - Header: make(map[string][]string), - } - req.Body = io.NopCloser(bytes.NewReader([]byte(` - - - admin - admin - - - - - admin - - FULL_CONTROL - - - - http://acs.amazonaws.com/groups/global/AllUsers - - FULL_CONTROL - - - - `))) - objectWriter := "accountA" - grants, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) - testCases = append(testCases, &Case{ - 1, - errCode, s3err.ErrNone, - grants, []*s3.Grant{ - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &accountAdminId, - }, - Permission: &s3_constants.PermissionFullControl, - }, - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeGroup, - URI: &s3_constants.GranteeGroupAllUsers, - }, - Permission: &s3_constants.PermissionFullControl, - }, - }, - }) - } - - { - //case2 (good case) - //parse acp from header (cannedAcl) - req := &http.Request{ - Header: make(map[string][]string), - } - req.Body = nil - req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclPrivate) - objectWriter := "accountA" - grants, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) - testCases = append(testCases, &Case{ - 2, - errCode, s3err.ErrNone, - grants, []*s3.Grant{ - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &objectWriter, - }, - Permission: &s3_constants.PermissionFullControl, - }, - }, - }) - } - - { - //case3 (bad case) - //parse acp from request body (content is invalid) - req := &http.Request{ - Header: make(map[string][]string), - } - req.Body = io.NopCloser(bytes.NewReader([]byte("zdfsaf"))) - req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclPrivate) - objectWriter := "accountA" - _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, accountAdminId, objectWriter) - testCases = append(testCases, &Case{ - id: 3, - resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, - }) - } - - //case4 (bad case) - //parse acp from header (cannedAcl is invalid) - req := &http.Request{ - Header: make(map[string][]string), - } - req.Body = nil - req.Header.Set(s3_constants.AmzCannedAcl, "dfaksjfk") - objectWriter := "accountA" - _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, "", objectWriter) - testCases = append(testCases, &Case{ - id: 4, - resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, - }) - - { - //case5 (bad case) - //parse acp from request body: owner is inconsistent - req.Body = io.NopCloser(bytes.NewReader([]byte(` - - - admin - admin - - - - - admin - - FULL_CONTROL - - - - http://acs.amazonaws.com/groups/global/AllUsers - - FULL_CONTROL - - - - `))) - objectWriter = "accountA" - _, errCode := ExtractAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, objectWriter, objectWriter) - testCases = append(testCases, &Case{ - id: 5, - resultErrCode: errCode, expectErrCode: s3err.ErrAccessDenied, - }) - } - - for _, tc := range testCases { - if tc.resultErrCode != tc.expectErrCode { - t.Fatalf("case[%d]: errorCode not expect", tc.id) - } - if !grantsEquals(tc.resultGrants, tc.expectGrants) { - t.Fatalf("case[%d]: grants not expect", tc.id) - } - } -} - -func TestParseAndValidateAclHeaders(t *testing.T) { - type Case struct { - id int - resultOwner, expectOwner string - resultErrCode, expectErrCode s3err.ErrorCode - resultGrants, expectGrants []*s3.Grant - } - testCases := make([]*Case, 0) - bucketOwner := "admin" - - { - //case1 (good case) - //parse custom acl - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzAclFullControl, `uri="http://acs.amazonaws.com/groups/global/AllUsers", id="anonymous", emailAddress="admin@example.com"`) - ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - 1, - ownerId, objectWriter, - errCode, s3err.ErrNone, - grants, []*s3.Grant{ - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeGroup, - URI: &s3_constants.GranteeGroupAllUsers, - }, - Permission: &s3_constants.PermissionFullControl, - }, - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &s3account.AccountAnonymous.Id, - }, - Permission: &s3_constants.PermissionFullControl, - }, - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &s3account.AccountAdmin.Id, - }, - Permission: &s3_constants.PermissionFullControl, - }, - }, - }) - } - { - //case2 (good case) - //parse canned acl (ownership=ObjectWriter) - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) - ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - 2, - ownerId, objectWriter, - errCode, s3err.ErrNone, - grants, []*s3.Grant{ - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &objectWriter, - }, - Permission: &s3_constants.PermissionFullControl, - }, - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &bucketOwner, - }, - Permission: &s3_constants.PermissionFullControl, - }, - }, - }) - } - { - //case3 (good case) - //parse canned acl (ownership=OwnershipBucketOwnerPreferred) - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) - ownerId, grants, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - 3, - ownerId, bucketOwner, - errCode, s3err.ErrNone, - grants, []*s3.Grant{ - { - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &bucketOwner, - }, - Permission: &s3_constants.PermissionFullControl, - }, - }, - }) - } - { - //case4 (bad case) - //parse custom acl (grantee id not exists) - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzAclFullControl, `uri="http://acs.amazonaws.com/groups/global/AllUsers", id="notExistsAccount", emailAddress="admin@example.com"`) - _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - id: 4, - resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, - }) - } - - { - //case5 (bad case) - //parse custom acl (invalid format) - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzAclFullControl, `uri="http:sfasf"`) - _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - id: 5, - resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, - }) - } - - { - //case6 (bad case) - //parse canned acl (invalid value) - req := &http.Request{ - Header: make(map[string][]string), - } - objectWriter := "accountA" - req.Header.Set(s3_constants.AmzCannedAcl, `uri="http:sfasf"`) - _, _, errCode := ParseAndValidateAclHeaders(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwner, objectWriter, false) - testCases = append(testCases, &Case{ - id: 5, - resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, - }) - } - - for _, tc := range testCases { - if tc.expectErrCode != tc.resultErrCode { - t.Errorf("case[%d]: errCode unexpect", tc.id) - } - if tc.resultOwner != tc.expectOwner { - t.Errorf("case[%d]: ownerId unexpect", tc.id) - } - if !grantsEquals(tc.resultGrants, tc.expectGrants) { - t.Fatalf("case[%d]: grants not expect", tc.id) - } - } -} - func grantsEquals(a, b []*s3.Grant) bool { if len(a) != len(b) { return false @@ -394,7 +74,7 @@ func TestDetermineReqGrants(t *testing.T) { accountId := s3account.AccountAnonymous.Id reqPermission := s3_constants.PermissionRead - resultGrants := DetermineReqGrants(accountId, reqPermission) + resultGrants := DetermineRequiredGrants(accountId, reqPermission) expectGrants := []*s3.Grant{ { Grantee: &s3.Grantee{ @@ -434,7 +114,7 @@ func TestDetermineReqGrants(t *testing.T) { accountId := "accountX" reqPermission := s3_constants.PermissionRead - resultGrants := DetermineReqGrants(accountId, reqPermission) + resultGrants := DetermineRequiredGrants(accountId, reqPermission) expectGrants := []*s3.Grant{ { Grantee: &s3.Grantee{ @@ -509,7 +189,7 @@ func TestAssembleEntryWithAcp(t *testing.T) { t.Fatalf("owner not expect") } - resultGrants := GetAcpGrants(entry.Extended) + resultGrants := GetAcpGrants(nil, entry.Extended) if !grantsEquals(resultGrants, expectGrants) { t.Fatal("grants not expect") } @@ -522,7 +202,7 @@ func TestAssembleEntryWithAcp(t *testing.T) { t.Fatalf("owner not expect") } - resultGrants = GetAcpGrants(entry.Extended) + resultGrants = GetAcpGrants(nil, entry.Extended) if len(resultGrants) != 0 { t.Fatal("grants not expect") } @@ -721,3 +401,1066 @@ func TestGrantWithFullControl(t *testing.T) { t.Fatal("GrantWithFullControl not expect") } } + +func TestExtractObjectAcl(t *testing.T) { + type Case struct { + id string + resultErrCode, expectErrCode s3err.ErrorCode + resultGrants, expectGrants []*s3.Grant + resultOwnerId, expectOwnerId string + } + testCases := make([]*Case, 0) + accountAdminId := "admin" + + //Request body to specify AccessControlList + { + //ownership: ObjectWriter + //s3:PutObjectAcl('createObject' is set to false), config acl through request body + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-false, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &accountAdminId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeGroup, + URI: &s3_constants.GranteeGroupAllUsers, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, accountAdminId, + }) + } + { + //ownership: BucketOwnerEnforced (extra acl is not allowed) + //s3:PutObjectAcl('createObject' is set to false), config acl through request body + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, accountAdminId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-false, acl-requestBody", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to false), request body will be ignored when parse acl + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-true, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{}, + ownerId, "", + }) + } + { + //ownership: BucketOwnerEnforced (extra acl is not allowed) + //s3:PutObject('createObject' is set to true), request body will be ignored when parse acl + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, accountAdminId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-true, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{}, + ownerId, "", + }) + } + + //CannedAcl Header to specify ACL + //cannedAcl, putObjectACL + { + //ownership: ObjectWriter + //s3:PutObjectACL('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-false, acl-CannedAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObjectACL('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-BucketOwnerPreferred, createObject-false, acl-CannedAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObjectACL('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-false, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + //cannedACL, putObject + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-true, acl-CannedAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-BucketOwnerPreferred, createObject-true, acl-CannedAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-true, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + + //cannedAcl, putObjectACL + { + //ownership: ObjectWriter + //s3:PutObjectACL('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-false, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObjectACL('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-BucketOwnerPreferred, createObject-false, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObjectACL('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-false, acl-customAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + //customAcl, putObject + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-ObjectWriter, createObject-true, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + ownerId, grants, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractObjectAcl: ownership-BucketOwnerPreferred, createObject-true, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + ownerId, "", + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractObjectAcl: ownership-BucketOwnerEnforced, createObject-true, acl-customAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + + { + //parse acp from request header: both canned acl and custom acl not allowed + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=admin, id=\"accountA\"") + req.Header.Set(s3_constants.AmzCannedAcl, "private") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "Only one of cannedAcl, customAcl is allowed", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, + }) + } + + { + //Acl can only be specified in one of requestBody, cannedAcl, customAcl, simultaneous use is not allowed + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=admin, id=\"accountA\"") + req.Header.Set(s3_constants.AmzCannedAcl, "private") + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + _, _, errCode := ExtractObjectAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "Only one of requestBody, cannedAcl, customAcl is allowed", + resultErrCode: errCode, expectErrCode: s3err.ErrUnexpectedContent, + }) + } + for _, tc := range testCases { + if tc.resultErrCode != tc.expectErrCode { + t.Fatalf("case[%s]: errorCode[%v] not expect[%v]", tc.id, s3err.GetAPIError(tc.resultErrCode).Code, s3err.GetAPIError(tc.expectErrCode).Code) + } + if !grantsEquals(tc.resultGrants, tc.expectGrants) { + t.Fatalf("case[%s]: grants not expect", tc.id) + } + } +} + +func TestBucketObjectAcl(t *testing.T) { + type Case struct { + id string + resultErrCode, expectErrCode s3err.ErrorCode + resultGrants, expectGrants []*s3.Grant + } + testCases := make([]*Case, 0) + accountAdminId := "admin" + + //Request body to specify AccessControlList + { + //ownership: ObjectWriter + //s3:PutBucketAcl('createObject' is set to false), config acl through request body + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-ObjectWriter, createObject-false, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &accountAdminId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeGroup, + URI: &s3_constants.GranteeGroupAllUsers, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + }) + } + { + //ownership: BucketOwnerEnforced (extra acl is not allowed) + //s3:PutBucketAcl('createObject' is set to false), config acl through request body + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, accountAdminId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-false, acl-requestBody", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to false), request body will be ignored when parse acl + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-ObjectWriter, createObject-true, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{}, + }) + } + { + //ownership: BucketOwnerEnforced (extra acl is not allowed) + //s3:PutObject('createObject' is set to true), request body will be ignored when parse acl + req := &http.Request{ + Header: make(map[string][]string), + } + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, accountAdminId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-true, acl-requestBody", + errCode, s3err.ErrNone, + grants, []*s3.Grant{}, + }) + } + + //CannedAcl Header to specify ACL + //cannedAcl, PutBucketAcl + { + //ownership: ObjectWriter + //s3:PutBucketAcl('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-ObjectWriter, createObject-false, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutBucketAcl('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerPreferred, createObject-false, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutBucketAcl('createObject' is set to false), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-false, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + //cannedACL, createBucket + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-true, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerPreferred, createObject-true, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObject('createObject' is set to true), parse cannedACL header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzCannedAcl, s3_constants.CannedAclBucketOwnerFullControl) + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-true, acl-CannedAcl", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidAclArgument, + }) + } + + //customAcl, PutBucketAcl + { + //ownership: ObjectWriter + //s3:PutBucketAcl('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-ObjectWriter, createObject-false, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutBucketAcl('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-BucketOwnerPreferred, createObject-false, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutBucketAcl('createObject' is set to false), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-false, acl-customAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + //customAcl, putObject + { + //ownership: ObjectWriter + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-ObjectWriter, createObject-true, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + }) + } + { + //ownership: BucketOwnerPreferred + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerPreferred, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + "TestExtractBucketAcl: ownership-BucketOwnerPreferred, createObject-true, acl-customAcl", + errCode, s3err.ErrNone, + grants, []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + }, + }) + } + { + //ownership: BucketOwnerEnforced + //s3:PutObject('createObject' is set to true), parse customAcl header + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=accountA,id=\"admin\"") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipBucketOwnerEnforced, bucketOwnerId, requestAccountId, true) + testCases = append(testCases, &Case{ + id: "TestExtractBucketAcl: ownership-BucketOwnerEnforced, createObject-true, acl-customAcl", + resultErrCode: errCode, expectErrCode: s3err.AccessControlListNotSupported, + }) + } + + { + //parse acp from request header: both canned acl and custom acl not allowed + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=admin, id=\"accountA\"") + req.Header.Set(s3_constants.AmzCannedAcl, "private") + bucketOwnerId := "admin" + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, bucketOwnerId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "Only one of cannedAcl, customAcl is allowed", + resultErrCode: errCode, expectErrCode: s3err.ErrInvalidRequest, + }) + } + + { + //Acl can only be specified in one of requestBody, cannedAcl, customAcl, simultaneous use is not allowed + req := &http.Request{ + Header: make(map[string][]string), + } + req.Header.Set(s3_constants.AmzAclFullControl, "id=admin, id=\"accountA\"") + req.Header.Set(s3_constants.AmzCannedAcl, "private") + req.Body = io.NopCloser(bytes.NewReader([]byte(` + + + admin + admin + + + + + admin + + FULL_CONTROL + + + + http://acs.amazonaws.com/groups/global/AllUsers + + FULL_CONTROL + + + + `))) + requestAccountId := "accountA" + _, errCode := ExtractBucketAcl(req, accountManager, s3_constants.OwnershipObjectWriter, accountAdminId, requestAccountId, false) + testCases = append(testCases, &Case{ + id: "Only one of requestBody, cannedAcl, customAcl is allowed", + resultErrCode: errCode, expectErrCode: s3err.ErrUnexpectedContent, + }) + } + for _, tc := range testCases { + if tc.resultErrCode != tc.expectErrCode { + t.Fatalf("case[%s]: errorCode[%v] not expect[%v]", tc.id, s3err.GetAPIError(tc.resultErrCode).Code, s3err.GetAPIError(tc.expectErrCode).Code) + } + if !grantsEquals(tc.resultGrants, tc.expectGrants) { + t.Fatalf("case[%s]: grants not expect", tc.id) + } + } +} + +func TestMarshalGrantsToJson(t *testing.T) { + //ok + bucketOwnerId := "admin" + requestAccountId := "accountA" + grants := []*s3.Grant{ + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &requestAccountId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + { + Grantee: &s3.Grantee{ + Type: &s3_constants.GrantTypeCanonicalUser, + ID: &bucketOwnerId, + }, + Permission: &s3_constants.PermissionFullControl, + }, + } + result, err := MarshalGrantsToJson(grants) + if err != nil { + t.Error(err) + } + + var grants2 []*s3.Grant + err = json.Unmarshal(result, &grants2) + if err != nil { + t.Error(err) + } + + print(string(result)) + if !grantsEquals(grants, grants2) { + t.Fatal("grants not equal", grants, grants2) + } + + //ok + result, err = MarshalGrantsToJson(nil) + if result != nil && err != nil { + t.Fatal("error: result, err = MarshalGrantsToJson(nil)") + } +} diff --git a/weed/s3api/s3api_acp.go b/weed/s3api/s3api_acp.go index 5fdec0397..528bf7e1d 100644 --- a/weed/s3api/s3api_acp.go +++ b/weed/s3api/s3api_acp.go @@ -23,19 +23,19 @@ func getAccountId(r *http.Request) string { } func (s3a *S3ApiServer) checkAccessByOwnership(r *http.Request, bucket string) s3err.ErrorCode { - metadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) + bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { return errCode } - accountId := getAccountId(r) - if accountId == s3account.AccountAdmin.Id || accountId == *metadata.Owner.ID { + requestAccountId := getAccountId(r) + if s3acl.ValidateAccount(requestAccountId, *bucketMetadata.Owner.ID) { return s3err.ErrNone } return s3err.ErrAccessDenied } //Check access for PutBucketAclHandler -func (s3a *S3ApiServer) checkAccessForPutBucketAcl(accountId, bucket string) (*BucketMetaData, s3err.ErrorCode) { +func (s3a *S3ApiServer) checkAccessForPutBucketAcl(requestAccountId, bucket string) (*BucketMetaData, s3err.ErrorCode) { bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { return nil, errCode @@ -45,12 +45,12 @@ func (s3a *S3ApiServer) checkAccessForPutBucketAcl(accountId, bucket string) (*B return nil, s3err.AccessControlListNotSupported } - if accountId == s3account.AccountAdmin.Id || accountId == *bucketMetadata.Owner.ID { + if s3acl.ValidateAccount(requestAccountId, *bucketMetadata.Owner.ID) { return bucketMetadata, s3err.ErrNone } if len(bucketMetadata.Acl) > 0 { - reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWriteAcp) + reqGrants := s3acl.DetermineRequiredGrants(requestAccountId, s3_constants.PermissionWriteAcp) for _, bucketGrant := range bucketMetadata.Acl { for _, reqGrant := range reqGrants { if s3acl.GrantEquals(bucketGrant, reqGrant) { @@ -59,7 +59,7 @@ func (s3a *S3ApiServer) checkAccessForPutBucketAcl(accountId, bucket string) (*B } } } - glog.V(3).Infof("acl denied! request account id: %s", accountId) + glog.V(3).Infof("acl denied! request account id: %s", requestAccountId) return nil, s3err.ErrAccessDenied } @@ -73,6 +73,7 @@ func updateBucketEntry(s3a *S3ApiServer, entry *filer_pb.Entry) error { // - GetBucketAclHandler // - ListObjectsV1Handler // - ListObjectsV2Handler +// - ListMultipartUploadsHandler func (s3a *S3ApiServer) checkAccessForReadBucket(r *http.Request, bucket, aclAction string) (*BucketMetaData, s3err.ErrorCode) { bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { @@ -83,13 +84,13 @@ func (s3a *S3ApiServer) checkAccessForReadBucket(r *http.Request, bucket, aclAct return bucketMetadata, s3err.ErrNone } - accountId := s3acl.GetAccountId(r) - if accountId == s3account.AccountAdmin.Id || accountId == *bucketMetadata.Owner.ID { + requestAccountId := s3acl.GetAccountId(r) + if s3acl.ValidateAccount(requestAccountId, *bucketMetadata.Owner.ID) { return bucketMetadata, s3err.ErrNone } if len(bucketMetadata.Acl) > 0 { - reqGrants := s3acl.DetermineReqGrants(accountId, aclAction) + reqGrants := s3acl.DetermineRequiredGrants(requestAccountId, aclAction) for _, bucketGrant := range bucketMetadata.Acl { for _, reqGrant := range reqGrants { if s3acl.GrantEquals(bucketGrant, reqGrant) { @@ -99,7 +100,7 @@ func (s3a *S3ApiServer) checkAccessForReadBucket(r *http.Request, bucket, aclAct } } - glog.V(3).Infof("acl denied! request account id: %s", accountId) + glog.V(3).Infof("acl denied! request account id: %s", requestAccountId) return nil, s3err.ErrAccessDenied } @@ -121,14 +122,12 @@ func (s3a *S3ApiServer) checkAccessForReadObjectAcl(r *http.Request, bucket, obj return nil, s3err.ErrInternalError } } - if entry.IsDirectory { return nil, s3err.ErrExistingObjectIsDirectory } - acpOwnerId := s3acl.GetAcpOwner(entry.Extended, *bucketMetadata.Owner.ID) acpOwnerName := s3a.accountManager.IdNameMapping[acpOwnerId] - acpGrants := s3acl.GetAcpGrants(entry.Extended) + acpGrants := s3acl.GetAcpGrants(&acpOwnerId, entry.Extended) acp = &s3.AccessControlPolicy{ Owner: &s3.Owner{ ID: &acpOwnerId, @@ -138,36 +137,29 @@ func (s3a *S3ApiServer) checkAccessForReadObjectAcl(r *http.Request, bucket, obj } return acp, s3err.ErrNone } - if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { return getAcpFunc() - } else { - accountId := s3acl.GetAccountId(r) - - acp, errCode := getAcpFunc() - if errCode != s3err.ErrNone { - return nil, errCode - } - - if accountId == *acp.Owner.ID { - return acp, s3err.ErrNone - } - - //find in Grants - if acp.Grants != nil { - reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionReadAcp) - for _, requiredGrant := range reqGrants { - for _, grant := range acp.Grants { - if s3acl.GrantEquals(requiredGrant, grant) { - return acp, s3err.ErrNone - } + } + requestAccountId := s3acl.GetAccountId(r) + acp, errCode = getAcpFunc() + if errCode != s3err.ErrNone { + return nil, errCode + } + if s3acl.ValidateAccount(requestAccountId, *acp.Owner.ID) { + return acp, s3err.ErrNone + } + if acp.Grants != nil { + reqGrants := s3acl.DetermineRequiredGrants(requestAccountId, s3_constants.PermissionReadAcp) + for _, requiredGrant := range reqGrants { + for _, grant := range acp.Grants { + if s3acl.GrantEquals(requiredGrant, grant) { + return acp, s3err.ErrNone } } } - - glog.V(3).Infof("acl denied! request account id: %s", accountId) - return nil, s3err.ErrAccessDenied } + glog.V(3).Infof("CheckAccessForReadObjectAcl denied! request account id: %s", requestAccountId) + return nil, s3err.ErrAccessDenied } // Check Object-Read related access @@ -184,6 +176,10 @@ func (s3a *S3ApiServer) checkBucketAccessForReadObject(r *http.Request, bucket s if bucketMetadata.ObjectOwnership != s3_constants.OwnershipBucketOwnerEnforced { //offload object acl validation to filer layer + _, defaultErrorCode := s3a.checkAccessForReadBucket(r, bucket, s3_constants.PermissionRead) + if defaultErrorCode != s3err.ErrNone { + r.Header.Set(s3_constants.XSeaweedFSHeaderAmzBucketAccessDenied, "true") + } r.Header.Set(s3_constants.XSeaweedFSHeaderAmzBucketOwnerId, *bucketMetadata.Owner.ID) } @@ -193,67 +189,58 @@ func (s3a *S3ApiServer) checkBucketAccessForReadObject(r *http.Request, bucket s // Check ObjectAcl-Write related access // includes: // - PutObjectAclHandler -func (s3a *S3ApiServer) checkAccessForWriteObjectAcl(accountId, bucket, object string) (bucketMetadata *BucketMetaData, objectEntry *filer_pb.Entry, objectOwner string, errCode s3err.ErrorCode) { - bucketMetadata, errCode = s3a.bucketRegistry.GetBucketMetadata(bucket) +func (s3a *S3ApiServer) checkAccessForWriteObjectAcl(r *http.Request, bucket, object string) (*filer_pb.Entry, string, []*s3.Grant, s3err.ErrorCode) { + bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { - return nil, nil, "", errCode + return nil, "", nil, errCode } - if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { - return nil, nil, "", s3err.AccessControlListNotSupported + requestAccountId := s3acl.GetAccountId(r) + reqOwnerId, grants, errCode := s3acl.ExtractObjectAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, requestAccountId, false) + if errCode != s3err.ErrNone { + return nil, "", nil, errCode } - //bucket acl - bucketAclAllowed := false - reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWrite) - if accountId == *bucketMetadata.Owner.ID { - bucketAclAllowed = true - } else if bucketMetadata.Acl != nil { - bucketLoop: - for _, bucketGrant := range bucketMetadata.Acl { - for _, requiredGrant := range reqGrants { - if s3acl.GrantEquals(bucketGrant, requiredGrant) { - bucketAclAllowed = true - break bucketLoop - } - } - } - } - if !bucketAclAllowed { - return nil, nil, "", s3err.ErrAccessDenied + if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { + return nil, "", nil, s3err.AccessControlListNotSupported } //object acl objectEntry, err := getObjectEntry(s3a, bucket, object) if err != nil { if err == filer_pb.ErrNotFound { - return nil, nil, "", s3err.ErrNoSuchKey + return nil, "", nil, s3err.ErrNoSuchKey } - return nil, nil, "", s3err.ErrInternalError + return nil, "", nil, s3err.ErrInternalError } if objectEntry.IsDirectory { - return nil, nil, "", s3err.ErrExistingObjectIsDirectory + return nil, "", nil, s3err.ErrExistingObjectIsDirectory } - objectOwner = s3acl.GetAcpOwner(objectEntry.Extended, *bucketMetadata.Owner.ID) - if accountId == objectOwner { - return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + objectOwner := s3acl.GetAcpOwner(objectEntry.Extended, *bucketMetadata.Owner.ID) + //object owner is immutable + if reqOwnerId != "" && reqOwnerId != objectOwner { + return nil, "", nil, s3err.ErrAccessDenied + } + if s3acl.ValidateAccount(requestAccountId, objectOwner) { + return objectEntry, objectOwner, grants, s3err.ErrNone } - objectGrants := s3acl.GetAcpGrants(objectEntry.Extended) + objectGrants := s3acl.GetAcpGrants(nil, objectEntry.Extended) if objectGrants != nil { + requiredGrants := s3acl.DetermineRequiredGrants(requestAccountId, s3_constants.PermissionWriteAcp) for _, objectGrant := range objectGrants { - for _, requiredGrant := range reqGrants { + for _, requiredGrant := range requiredGrants { if s3acl.GrantEquals(objectGrant, requiredGrant) { - return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + return objectEntry, objectOwner, grants, s3err.ErrNone } } } } - glog.V(3).Infof("acl denied! request account id: %s", accountId) - return nil, nil, "", s3err.ErrAccessDenied + glog.V(3).Infof("checkAccessForWriteObjectAcl denied! request account id: %s", requestAccountId) + return nil, "", nil, s3err.ErrAccessDenied } func updateObjectEntry(s3a *S3ApiServer, bucket, object string, entry *filer_pb.Entry) error { @@ -269,150 +256,159 @@ func (s3a *S3ApiServer) CheckAccessForPutObject(r *http.Request, bucket, object return s3a.checkAccessForWriteObject(r, bucket, object, accountId) } -// CheckAccessForNewMultipartUpload Check Acl for InitiateMultipartUploadResult API +// CheckAccessForPutObjectPartHandler Check Acl for Upload object part // includes: -// - NewMultipartUploadHandler -func (s3a *S3ApiServer) CheckAccessForNewMultipartUpload(r *http.Request, bucket, object string) s3err.ErrorCode { +// - PutObjectPartHandler +func (s3a *S3ApiServer) CheckAccessForPutObjectPartHandler(r *http.Request, bucket string) s3err.ErrorCode { + bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) + if errCode != s3err.ErrNone { + return errCode + } + if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { + return s3err.ErrNone + } accountId := s3acl.GetAccountId(r) - if accountId == IdentityAnonymous.AccountId { + if !CheckBucketAccess(accountId, bucketMetadata, s3_constants.PermissionWrite) { return s3err.ErrAccessDenied } - return s3a.checkAccessForWriteObject(r, bucket, object, accountId) + return s3err.ErrNone } -// CheckAccessForCompleteMultipartUpload Check Acl for CompleteMultipartUpload API +// CheckAccessForNewMultipartUpload Check Acl for API // includes: -// - CompleteMultipartUploadHandler +// - NewMultipartUploadHandler +func (s3a *S3ApiServer) CheckAccessForNewMultipartUpload(r *http.Request, bucket, object string) (s3err.ErrorCode, string) { + accountId := s3acl.GetAccountId(r) + if accountId == IdentityAnonymous.AccountId { + return s3err.ErrAccessDenied, "" + } + errCode := s3a.checkAccessForWriteObject(r, bucket, object, accountId) + return errCode, accountId +} + +func (s3a *S3ApiServer) CheckAccessForAbortMultipartUpload(r *http.Request, bucket, object string) s3err.ErrorCode { + return s3a.CheckAccessWithBucketOwnerAndInitiator(r, bucket, object) +} + func (s3a *S3ApiServer) CheckAccessForCompleteMultipartUpload(r *http.Request, bucket, object string) s3err.ErrorCode { bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { return errCode } + if bucketMetadata.ObjectOwnership != s3_constants.OwnershipBucketOwnerEnforced { + accountId := getAccountId(r) + if !CheckBucketAccess(accountId, bucketMetadata, s3_constants.PermissionWrite) { + return s3err.ErrAccessDenied + } + } + return s3err.ErrNone +} + +func (s3a *S3ApiServer) CheckAccessForListMultipartUploadParts(r *http.Request, bucket, object string) s3err.ErrorCode { + return s3a.CheckAccessWithBucketOwnerAndInitiator(r, bucket, object) +} + +// CheckAccessWithBucketOwnerAndInitiator Check Access Permission with 'bucketOwner' and 'multipartUpload initiator' +func (s3a *S3ApiServer) CheckAccessWithBucketOwnerAndInitiator(r *http.Request, bucket, object string) s3err.ErrorCode { + bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) + if errCode != s3err.ErrNone { + return errCode + } + //bucket access allowed accountId := s3acl.GetAccountId(r) - if accountId == *bucketMetadata.Owner.ID { + if s3acl.ValidateAccount(*bucketMetadata.Owner.ID, accountId) { return s3err.ErrNone + } + + //multipart initiator allowed + entry, err := getMultipartUpload(s3a, bucket, object) + if err != nil { + if err != filer_pb.ErrNotFound { + return s3err.ErrInternalError + } } else { - if len(bucketMetadata.Acl) > 0 { - reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWrite) - for _, bucketGrant := range bucketMetadata.Acl { - for _, requiredGrant := range reqGrants { - if s3acl.GrantEquals(bucketGrant, requiredGrant) { - return s3err.ErrNone - } - } - } + uploadInitiator, ok := entry.Extended[s3_constants.ExtAmzMultipartInitiator] + if !ok || accountId == string(uploadInitiator) { + return s3err.ErrNone } } - glog.V(3).Infof("acl denied! request account id: %s", accountId) + glog.V(3).Infof("CheckAccessWithBucketOwnerAndInitiator denied! request account id: %s", accountId) return s3err.ErrAccessDenied } -func (s3a *S3ApiServer) checkAccessForWriteObject(r *http.Request, bucket, object, accountId string) s3err.ErrorCode { +func (s3a *S3ApiServer) checkAccessForWriteObject(r *http.Request, bucket, object, requestAccountId string) s3err.ErrorCode { bucketMetadata, errCode := s3a.bucketRegistry.GetBucketMetadata(bucket) if errCode != s3err.ErrNone { return errCode } + requestOwnerId, grants, errCode := s3acl.ExtractObjectAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, requestAccountId, true) + if errCode != s3err.ErrNone { + return errCode + } if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { - // validate grants (only bucketOwnerFullControl acl is allowed) - _, grants, errCode := s3acl.ParseAndValidateAclHeaders(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false) - if errCode != s3err.ErrNone { - return errCode - } - if len(grants) > 1 { - return s3err.AccessControlListNotSupported - } - bucketOwnerFullControlGrant := &s3.Grant{ - Permission: &s3_constants.PermissionFullControl, - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: bucketMetadata.Owner.ID, - }, - } - if len(grants) == 0 { - return s3err.ErrNone - } + return s3err.ErrNone + } - if s3acl.GrantEquals(bucketOwnerFullControlGrant, grants[0]) { + if !CheckBucketAccess(requestAccountId, bucketMetadata, s3_constants.PermissionWrite) { + return s3err.ErrAccessDenied + } + + if requestOwnerId == "" { + requestOwnerId = requestAccountId + } + entry, err := getObjectEntry(s3a, bucket, object) + if err != nil { + if err == filer_pb.ErrNotFound { + s3acl.SetAcpOwnerHeader(r, requestOwnerId) + s3acl.SetAcpGrantsHeader(r, grants) return s3err.ErrNone } + return s3err.ErrInternalError + } - return s3err.AccessControlListNotSupported + objectOwnerId := s3acl.GetAcpOwner(entry.Extended, *bucketMetadata.Owner.ID) + //object owner is immutable + if requestOwnerId != "" && objectOwnerId != requestOwnerId { + return s3err.ErrAccessDenied } - //bucket access allowed - bucketAclAllowed := false - if accountId == *bucketMetadata.Owner.ID { - bucketAclAllowed = true + //Only the owner of the bucket and the owner of the object can overwrite the object + if s3acl.ValidateAccount(requestOwnerId, objectOwnerId, *bucketMetadata.Owner.ID) { + glog.V(3).Infof("checkAccessForWriteObject denied! request account id: %s, expect account id: %s", requestAccountId, *bucketMetadata.Owner.ID) + return s3err.ErrAccessDenied + } + + s3acl.SetAcpOwnerHeader(r, objectOwnerId) + s3acl.SetAcpGrantsHeader(r, grants) + return s3err.ErrNone +} + +func CheckBucketAccess(requestAccountId string, bucketMetadata *BucketMetaData, permission string) bool { + if s3acl.ValidateAccount(requestAccountId, *bucketMetadata.Owner.ID) { + return true } else { if len(bucketMetadata.Acl) > 0 { - reqGrants := s3acl.DetermineReqGrants(accountId, s3_constants.PermissionWrite) - bucketLoop: + reqGrants := s3acl.DetermineRequiredGrants(requestAccountId, permission) for _, bucketGrant := range bucketMetadata.Acl { for _, requiredGrant := range reqGrants { if s3acl.GrantEquals(bucketGrant, requiredGrant) { - bucketAclAllowed = true - break bucketLoop + return true } } } } } - if !bucketAclAllowed { - glog.V(3).Infof("acl denied! request account id: %s", accountId) - return s3err.ErrAccessDenied - } - - //object access allowed - entry, err := getObjectEntry(s3a, bucket, object) - if err != nil { - if err != filer_pb.ErrNotFound { - return s3err.ErrInternalError - } - } else { - if entry.IsDirectory { - return s3err.ErrExistingObjectIsDirectory - } - - //Only the owner of the bucket and the owner of the object can overwrite the object - objectOwner := s3acl.GetAcpOwner(entry.Extended, *bucketMetadata.Owner.ID) - if accountId != objectOwner && accountId != *bucketMetadata.Owner.ID { - glog.V(3).Infof("acl denied! request account id: %s, expect account id: %s", accountId, *bucketMetadata.Owner.ID) - return s3err.ErrAccessDenied - } - } - - ownerId, grants, errCode := s3acl.ParseAndValidateAclHeadersOrElseDefault(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false) - if errCode != s3err.ErrNone { - return errCode - } - - s3acl.SetAcpOwnerHeader(r, ownerId) - s3acl.SetAcpGrantsHeader(r, grants) - - return s3err.ErrNone + glog.V(3).Infof("CheckBucketAccess denied! request account id: %s", requestAccountId) + return false } func getObjectEntry(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) { return s3a.getEntry(util.Join(s3a.option.BucketsPath, bucket), object) } -func (s3a *S3ApiServer) ExtractBucketAcp(r *http.Request, objectOwnership string) (owner string, grants []*s3.Grant, errCode s3err.ErrorCode) { - accountId := s3acl.GetAccountId(r) - - if objectOwnership == "" || objectOwnership == s3_constants.OwnershipBucketOwnerEnforced { - return accountId, []*s3.Grant{ - { - Permission: &s3_constants.PermissionFullControl, - Grantee: &s3.Grantee{ - Type: &s3_constants.GrantTypeCanonicalUser, - ID: &accountId, - }, - }, - }, s3err.ErrNone - } else { - return s3acl.ParseAndValidateAclHeadersOrElseDefault(r, s3a.accountManager, objectOwnership, accountId, accountId, false) - } +func getMultipartUpload(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) { + return s3a.getEntry(s3a.genUploadsFolder(bucket), object) } diff --git a/weed/s3api/s3api_bucket_handlers.go b/weed/s3api/s3api_bucket_handlers.go index 159f5b7bc..515073142 100644 --- a/weed/s3api/s3api_bucket_handlers.go +++ b/weed/s3api/s3api_bucket_handlers.go @@ -123,8 +123,9 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) } } - objectOwnership := r.Header.Get("ObjectOwnership") - acpOwner, acpGrants, errCode := s3a.ExtractBucketAcp(r, objectOwnership) + objectOwnership := r.Header.Get("X-Amz-Object-Ownership") + requestAccountId := s3acl.GetAccountId(r) + grants, errCode := s3acl.ExtractBucketAcl(r, s3a.accountManager, objectOwnership, requestAccountId, requestAccountId, true) if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return @@ -138,9 +139,12 @@ func (s3a *S3ApiServer) PutBucketHandler(w http.ResponseWriter, r *http.Request) entry.Extended[s3_constants.AmzIdentityId] = []byte(identityId) } if objectOwnership != "" { + if entry.Extended == nil { + entry.Extended = make(map[string][]byte) + } entry.Extended[s3_constants.ExtOwnershipKey] = []byte(objectOwnership) } - s3acl.AssembleEntryWithAcp(entry, acpOwner, acpGrants) + s3acl.AssembleEntryWithAcp(entry, requestAccountId, grants) } // create the folder for bucket, but lazily create actual collection @@ -269,7 +273,7 @@ func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Reque return } - grants, errCode := s3acl.ExtractAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, "", *bucketMetadata.Owner.ID, accountId) + grants, errCode := s3acl.ExtractBucketAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, accountId, false) if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return @@ -293,6 +297,8 @@ func (s3a *S3ApiServer) PutBucketAclHandler(w http.ResponseWriter, r *http.Reque s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) return } + //update local cache + bucketMetadata.Acl = grants s3err.WriteEmptyResponse(w, r, http.StatusOK) } @@ -402,9 +408,8 @@ func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *htt return } - var v s3.OwnershipControls defer util.CloseRequest(r) - + var v s3.OwnershipControls err := xmlutil.UnmarshalXML(&v, xml.NewDecoder(r.Body), "") if err != nil { s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) @@ -417,12 +422,11 @@ func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *htt } printOwnership := true - ownership := *v.Rules[0].ObjectOwnership - switch ownership { + newObjectOwnership := *v.Rules[0].ObjectOwnership + switch newObjectOwnership { case s3_constants.OwnershipObjectWriter: case s3_constants.OwnershipBucketOwnerPreferred: case s3_constants.OwnershipBucketOwnerEnforced: - printOwnership = false default: s3err.WriteErrorResponse(w, r, s3err.ErrInvalidRequest) return @@ -439,29 +443,34 @@ func (s3a *S3ApiServer) PutBucketOwnershipControls(w http.ResponseWriter, r *htt } oldOwnership, ok := bucketEntry.Extended[s3_constants.ExtOwnershipKey] - if !ok || string(oldOwnership) != ownership { + if !ok || string(oldOwnership) != newObjectOwnership { // must reset bucket acl to default(bucket owner with full control permission) before setting ownership // to `OwnershipBucketOwnerEnforced` (bucket cannot have ACLs set with ObjectOwnership's BucketOwnerEnforced setting) - if ownership == s3_constants.OwnershipBucketOwnerEnforced { - acpGrants := s3acl.GetAcpGrants(bucketEntry.Extended) - if len(acpGrants) != 1 { - s3err.WriteErrorResponse(w, r, s3err.InvalidBucketAclWithObjectOwnership) - return - } - - bucketOwner := s3acl.GetAcpOwner(bucketEntry.Extended, s3account.AccountAdmin.Id) - expectGrant := s3acl.GrantWithFullControl(bucketOwner) - if s3acl.GrantEquals(acpGrants[0], expectGrant) { + if newObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { + acpGrants := s3acl.GetAcpGrants(nil, bucketEntry.Extended) + if len(acpGrants) > 1 { s3err.WriteErrorResponse(w, r, s3err.InvalidBucketAclWithObjectOwnership) return + } else if len(acpGrants) == 1 { + bucketOwner := s3acl.GetAcpOwner(bucketEntry.Extended, s3account.AccountAdmin.Id) + expectGrant := s3acl.GrantWithFullControl(bucketOwner) + if !s3acl.GrantEquals(acpGrants[0], expectGrant) { + s3err.WriteErrorResponse(w, r, s3err.InvalidBucketAclWithObjectOwnership) + return + } } } if bucketEntry.Extended == nil { bucketEntry.Extended = make(map[string][]byte) } - bucketEntry.Extended[s3_constants.ExtOwnershipKey] = []byte(ownership) + //update local cache + bucketMetadata, eCode := s3a.bucketRegistry.GetBucketMetadata(bucket) + if eCode == s3err.ErrNone { + bucketMetadata.ObjectOwnership = newObjectOwnership + } + bucketEntry.Extended[s3_constants.ExtOwnershipKey] = []byte(newObjectOwnership) err = s3a.updateEntry(s3a.option.BucketsPath, bucketEntry) if err != nil { s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 2e0b64ea3..fb8974b30 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -219,12 +219,16 @@ func (s3a *S3ApiServer) GetObjectAclHandler(w http.ResponseWriter, r *http.Reque } func (s3a *S3ApiServer) HeadObjectHandler(w http.ResponseWriter, r *http.Request) { - bucket, object := s3_constants.GetBucketAndObject(r) glog.V(3).Infof("HeadObjectHandler %s %s", bucket, object) - destUrl := s3a.toFilerUrl(bucket, object) + errCode := s3a.checkBucketAccessForReadObject(r, bucket) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + destUrl := s3a.toFilerUrl(bucket, object) s3a.proxyToFiler(w, r, destUrl, false, passThroughResponse) } @@ -592,20 +596,18 @@ func (s3a *S3ApiServer) maybeGetFilerJwtAuthorizationToken(isWrite bool) string func (s3a *S3ApiServer) PutObjectAclHandler(w http.ResponseWriter, r *http.Request) { bucket, object := s3_constants.GetBucketAndObject(r) - accountId := s3acl.GetAccountId(r) - bucketMetadata, objectEntry, objectOwner, errCode := s3a.checkAccessForWriteObjectAcl(accountId, bucket, object) + objectEntry, ownerId, grants, errCode := s3a.checkAccessForWriteObjectAcl(r, bucket, object) if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return } - grants, errCode := s3acl.ExtractAcl(r, s3a.accountManager, bucketMetadata.ObjectOwnership, *bucketMetadata.Owner.ID, objectOwner, accountId) if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return } - errCode = s3acl.AssembleEntryWithAcp(objectEntry, objectOwner, grants) + errCode = s3acl.AssembleEntryWithAcp(objectEntry, ownerId, grants) if errCode != s3err.ErrNone { return } diff --git a/weed/s3api/s3api_object_multipart_handlers.go b/weed/s3api/s3api_object_multipart_handlers.go index 881bb81d2..2cfa3e51c 100644 --- a/weed/s3api/s3api_object_multipart_handlers.go +++ b/weed/s3api/s3api_object_multipart_handlers.go @@ -30,8 +30,7 @@ const ( func (s3a *S3ApiServer) NewMultipartUploadHandler(w http.ResponseWriter, r *http.Request) { bucket, object := s3_constants.GetBucketAndObject(r) - //acl - errCode := s3a.CheckAccessForNewMultipartUpload(r, bucket, object) + errCode, initiatorId := s3a.CheckAccessForNewMultipartUpload(r, bucket, object) if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return @@ -52,7 +51,7 @@ func (s3a *S3ApiServer) NewMultipartUploadHandler(w http.ResponseWriter, r *http if contentType != "" { createMultipartUploadInput.ContentType = &contentType } - response, errCode := s3a.createMultipartUpload(createMultipartUploadInput) + response, errCode := s3a.createMultipartUpload(initiatorId, createMultipartUploadInput) glog.V(2).Info("NewMultipartUploadHandler", string(s3err.EncodeXMLResponse(response)), errCode) @@ -70,7 +69,6 @@ func (s3a *S3ApiServer) CompleteMultipartUploadHandler(w http.ResponseWriter, r // https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html bucket, object := s3_constants.GetBucketAndObject(r) - s3a.CheckAccessForCompleteMultipartUpload(r, bucket, object) parts := &CompleteMultipartUpload{} if err := xmlDecoder(r.Body, parts, r.ContentLength); err != nil { @@ -86,6 +84,12 @@ func (s3a *S3ApiServer) CompleteMultipartUploadHandler(w http.ResponseWriter, r return } + errCode := s3a.CheckAccessForCompleteMultipartUpload(r, bucket, uploadID) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + response, errCode := s3a.completeMultipartUpload(&s3.CompleteMultipartUploadInput{ Bucket: aws.String(bucket), Key: objectKey(aws.String(object)), @@ -115,6 +119,24 @@ func (s3a *S3ApiServer) AbortMultipartUploadHandler(w http.ResponseWriter, r *ht return } + errCode := s3a.CheckAccessForAbortMultipartUpload(r, bucket, uploadID) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + exists, err := s3a.exists(s3a.genUploadsFolder(bucket), uploadID, true) + if err != nil { + glog.V(1).Infof("list parts error: %v, request url: %s", err, r.RequestURI) + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchUpload) + return + } + if !exists { + glog.V(1).Infof("list parts not found, request url: %s", r.RequestURI) + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchUpload) + return + } + response, errCode := s3a.abortMultipartUpload(&s3.AbortMultipartUploadInput{ Bucket: aws.String(bucket), Key: objectKey(aws.String(object)), @@ -151,7 +173,12 @@ func (s3a *S3ApiServer) ListMultipartUploadsHandler(w http.ResponseWriter, r *ht } } - response, errCode := s3a.listMultipartUploads(&s3.ListMultipartUploadsInput{ + bucketMetaData, errorCode := s3a.checkAccessForReadBucket(r, bucket, s3_constants.PermissionRead) + if errorCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errorCode) + return + } + response, errCode := s3a.listMultipartUploads(bucketMetaData, &s3.ListMultipartUploadsInput{ Bucket: aws.String(bucket), Delimiter: aws.String(delimiter), EncodingType: aws.String(encodingType), @@ -193,6 +220,23 @@ func (s3a *S3ApiServer) ListObjectPartsHandler(w http.ResponseWriter, r *http.Re return } + errCode := s3a.CheckAccessForListMultipartUploadParts(r, bucket, uploadID) + if errCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errCode) + return + } + + exists, err := s3a.exists(s3a.genUploadsFolder(bucket), uploadID, true) + if err != nil { + glog.V(1).Infof("list parts error: %v, request url: %s", err, r.RequestURI) + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchUpload) + return + } + if !exists { + glog.V(1).Infof("list parts not found, request url: %s", r.RequestURI) + s3err.WriteErrorResponse(w, r, s3err.ErrNoSuchUpload) + return + } response, errCode := s3a.listObjectParts(&s3.ListPartsInput{ Bucket: aws.String(bucket), Key: objectKey(aws.String(object)), @@ -253,6 +297,12 @@ func (s3a *S3ApiServer) PutObjectPartHandler(w http.ResponseWriter, r *http.Requ } defer dataReader.Close() + errorCode := s3a.CheckAccessForPutObjectPartHandler(r, bucket) + if errorCode != s3err.ErrNone { + s3err.WriteErrorResponse(w, r, errorCode) + return + } + glog.V(2).Infof("PutObjectPartHandler %s %s %04d", bucket, uploadID, partID) uploadUrl := fmt.Sprintf("http://%s%s/%s/%04d.part", s3a.option.Filer.ToHttpAddress(), s3a.genUploadsFolder(bucket), uploadID, partID) @@ -260,9 +310,8 @@ func (s3a *S3ApiServer) PutObjectPartHandler(w http.ResponseWriter, r *http.Requ if partID == 1 && r.Header.Get("Content-Type") == "" { dataReader = mimeDetect(r, dataReader) } - destination := fmt.Sprintf("%s/%s%s", s3a.option.BucketsPath, bucket, object) - etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader, destination) + etag, errCode := s3a.putToFiler(r, uploadUrl, dataReader, "") if errCode != s3err.ErrNone { s3err.WriteErrorResponse(w, r, errCode) return diff --git a/weed/s3api/s3api_server.go b/weed/s3api/s3api_server.go index a0f7c1baa..ac181eac2 100644 --- a/weed/s3api/s3api_server.go +++ b/weed/s3api/s3api_server.go @@ -128,15 +128,15 @@ func (s3a *S3ApiServer) registerRouter(router *mux.Router) { // PutObjectPart bucket.Methods("PUT").Path("/{object:.+}").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.PutObjectPartHandler, ACTION_WRITE)), "PUT")).Queries("partNumber", "{partNumber:[0-9]+}", "uploadId", "{uploadId:.*}") // CompleteMultipartUpload - bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.CompleteMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploadId", "{uploadId:.*}") + bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.CompleteMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploadId", "{uploadId:.*}") // NewMultipartUpload - bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.NewMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploads", "") + bucket.Methods("POST").Path("/{object:.+}").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.NewMultipartUploadHandler, ACTION_WRITE)), "POST")).Queries("uploads", "") // AbortMultipartUpload - bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.AbortMultipartUploadHandler, ACTION_WRITE)), "DELETE")).Queries("uploadId", "{uploadId:.*}") + bucket.Methods("DELETE").Path("/{object:.+}").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.AbortMultipartUploadHandler, ACTION_WRITE)), "DELETE")).Queries("uploadId", "{uploadId:.*}") // ListObjectParts - bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListObjectPartsHandler, ACTION_READ)), "GET")).Queries("uploadId", "{uploadId:.*}") + bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.ListObjectPartsHandler, ACTION_READ)), "GET")).Queries("uploadId", "{uploadId:.*}") // ListMultipartUploads - bucket.Methods("GET").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.ListMultipartUploadsHandler, ACTION_READ)), "GET")).Queries("uploads", "") + bucket.Methods("GET").HandlerFunc(track(s3a.Auth(withAcl(s3a.cb.Limit, s3a.ListMultipartUploadsHandler, ACTION_READ)), "GET")).Queries("uploads", "") // GetObjectTagging bucket.Methods("GET").Path("/{object:.+}").HandlerFunc(track(s3a.iam.Auth(s3a.cb.Limit(s3a.GetObjectTaggingHandler, ACTION_READ)), "GET")).Queries("tagging", "") diff --git a/weed/s3api/s3err/s3api_errors.go b/weed/s3api/s3err/s3api_errors.go index 117631d1b..f3d44a31a 100644 --- a/weed/s3api/s3err/s3api_errors.go +++ b/weed/s3api/s3err/s3api_errors.go @@ -111,6 +111,8 @@ const ( OwnershipControlsNotFoundError InvalidBucketAclWithObjectOwnership AccessControlListNotSupported + ErrUnexpectedContent + ErrInvalidAclArgument ) // error code to APIError structure, these fields carry respective @@ -426,13 +428,23 @@ var errorCodeResponse = map[ErrorCode]APIError{ InvalidBucketAclWithObjectOwnership: { Code: "InvalidBucketAclWithObjectOwnership", Description: "Bucket cannot have ACLs set with ObjectOwnership's BucketOwnerEnforced setting", - HTTPStatusCode: http.StatusNotFound, + HTTPStatusCode: http.StatusBadRequest, }, AccessControlListNotSupported: { Code: "AccessControlListNotSupported", Description: "The bucket does not allow ACLs", HTTPStatusCode: http.StatusBadRequest, }, + ErrUnexpectedContent: { + Code: "UnexpectedContent", + Description: "This request does not support content", + HTTPStatusCode: http.StatusBadRequest, + }, + ErrInvalidAclArgument: { + Code: "InvalidArgument", + Description: "ACL argument is invalid", + HTTPStatusCode: http.StatusBadRequest, + }, } // GetAPIError provides API Error for input API error code. diff --git a/weed/server/filer_server_handlers_read.go b/weed/server/filer_server_handlers_read.go index 9e4ef61a1..335c3f1cb 100644 --- a/weed/server/filer_server_handlers_read.go +++ b/weed/server/filer_server_handlers_read.go @@ -12,7 +12,7 @@ import ( "mime" "net/http" "path/filepath" - "strconv" + strconv "strconv" "strings" "time" @@ -99,7 +99,11 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) if err == filer_pb.ErrNotFound { glog.V(2).Infof("Not found %s: %v", path, err) stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadNotFound).Inc() - w.WriteHeader(http.StatusNotFound) + if r.Header.Get(s3_constants.XSeaweedFSHeaderAmzBucketAccessDenied) == "true" { + w.WriteHeader(http.StatusForbidden) + } else { + w.WriteHeader(http.StatusNotFound) + } } else { glog.Errorf("Internal %s: %v", path, err) stats.FilerRequestCounter.WithLabelValues(stats.ErrorReadInternal).Inc() @@ -174,10 +178,15 @@ func (fs *FilerServer) GetOrHeadHandler(w http.ResponseWriter, r *http.Request) // print out the header from extended properties for k, v := range entry.Extended { - if !strings.HasPrefix(k, "xattr-") { + if strings.HasPrefix(k, "xattr-") { // "xattr-" prefix is set in filesys.XATTR_PREFIX - w.Header().Set(k, string(v)) + continue + } + if strings.HasPrefix(k, "Seaweed-X-") { + // key with "Seaweed-X-" prefix is builtin and should not expose to user + continue } + w.Header().Set(k, string(v)) } //Seaweed custom header are not visible to Vue or javascript