diff --git a/weed/s3api/s3api_acp.go b/weed/s3api/s3api_acp.go index 8febdd155..b929a5d57 100644 --- a/weed/s3api/s3api_acp.go +++ b/weed/s3api/s3api_acp.go @@ -1,9 +1,9 @@ package s3api import ( + "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/glog" "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb" - "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" "github.com/seaweedfs/seaweedfs/weed/s3api/s3account" "github.com/seaweedfs/seaweedfs/weed/s3api/s3acl" @@ -169,10 +169,6 @@ func (s3a *S3ApiServer) checkAccessForReadObjectAcl(r *http.Request, bucket, obj } } -func getObjectEntry(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) { - return s3a.getEntry(util.Join(s3a.option.BucketsPath, bucket), object) -} - // Check Object-Read related access // includes: // - GetObjectHandler @@ -192,3 +188,77 @@ func (s3a *S3ApiServer) checkBucketAccessForReadObject(r *http.Request, bucket s return s3err.ErrNone } + +// 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) + if errCode != s3err.ErrNone { + return nil, nil, "", errCode + } + + if bucketMetadata.ObjectOwnership == s3_constants.OwnershipBucketOwnerEnforced { + return nil, nil, "", s3err.AccessControlListNotSupported + } + + //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 + } + + //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.ErrInternalError + } + + if objectEntry.IsDirectory { + return nil, nil, "", s3err.ErrExistingObjectIsDirectory + } + + objectOwner = s3acl.GetAcpOwner(objectEntry.Extended, *bucketMetadata.Owner.ID) + if accountId == objectOwner { + return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + } + + objectGrants := s3acl.GetAcpGrants(objectEntry.Extended) + if objectGrants != nil { + for _, objectGrant := range objectGrants { + for _, requiredGrant := range reqGrants { + if s3acl.GrantEquals(objectGrant, requiredGrant) { + return bucketMetadata, objectEntry, objectOwner, s3err.ErrNone + } + } + } + } + + glog.V(3).Infof("acl denied! request account id: %s", accountId) + return nil, nil, "", s3err.ErrAccessDenied +} + +func getObjectEntry(s3a *S3ApiServer, bucket, object string) (*filer_pb.Entry, error) { + return s3a.getEntry(util.Join(s3a.option.BucketsPath, bucket), object) +} + +func updateObjectEntry(s3a *S3ApiServer, bucket string, entry *filer_pb.Entry) error { + return s3a.updateEntry(util.Join(s3a.option.BucketsPath, bucket), entry) +} diff --git a/weed/s3api/s3api_object_handlers.go b/weed/s3api/s3api_object_handlers.go index 4e9370f97..64118920c 100644 --- a/weed/s3api/s3api_object_handlers.go +++ b/weed/s3api/s3api_object_handlers.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/aws/aws-sdk-go/service/s3" "github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants" + "github.com/seaweedfs/seaweedfs/weed/s3api/s3acl" "github.com/seaweedfs/seaweedfs/weed/security" "github.com/seaweedfs/seaweedfs/weed/util/mem" "golang.org/x/exp/slices" @@ -550,3 +551,34 @@ func (s3a *S3ApiServer) maybeGetFilerJwtAuthorizationToken(isWrite bool) string } return string(encodedJwt) } + +// PutObjectAclHandler Put object ACL +// https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjecthtml +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) + 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) + if errCode != s3err.ErrNone { + return + } + + err := updateObjectEntry(s3a, bucket, objectEntry) + if err != nil { + s3err.WriteErrorResponse(w, r, s3err.ErrInternalError) + return + } + w.WriteHeader(http.StatusOK) +}