diff --git a/auth/access-control.go b/auth/access-control.go index 81cc0e2..283a290 100644 --- a/auth/access-control.go +++ b/auth/access-control.go @@ -70,20 +70,20 @@ func VerifyObjectCopyAccess(ctx context.Context, be backend.Backend, copySource } type AccessOptions struct { - Acl ACL - AclPermission Permission - IsRoot bool - Acc Account - Bucket string - Object string - Action Action - Readonly bool - IsBucketPublic bool + Acl ACL + AclPermission Permission + IsRoot bool + Acc Account + Bucket string + Object string + Action Action + Readonly bool + IsPublicRequest bool } func VerifyAccess(ctx context.Context, be backend.Backend, opts AccessOptions) error { - // Skip the access check for public buckets - if opts.IsBucketPublic { + // Skip the access check for public bucket requests + if opts.IsPublicRequest { return nil } if opts.Readonly { diff --git a/s3api/controllers/bucket-delete.go b/s3api/controllers/bucket-delete.go index 22ef928..7b062b0 100644 --- a/s3api/controllers/bucket-delete.go +++ b/s3api/controllers/bucket-delete.go @@ -31,14 +31,14 @@ func (c S3ApiController) DeleteBucketTagging(ctx *fiber.Ctx) (*Response, error) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketTaggingAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketTaggingAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ @@ -132,14 +132,14 @@ func (c S3ApiController) DeleteBucketCors(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketCorsAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketCorsAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ @@ -167,14 +167,14 @@ func (c S3ApiController) DeleteBucket(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.DeleteBucketAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.DeleteBucketAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ diff --git a/s3api/controllers/bucket-get.go b/s3api/controllers/bucket-get.go index c3116c5..2e3c75d 100644 --- a/s3api/controllers/bucket-get.go +++ b/s3api/controllers/bucket-get.go @@ -35,14 +35,14 @@ func (c S3ApiController) GetBucketTagging(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketTaggingAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketTaggingAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -87,14 +87,14 @@ func (c S3ApiController) GetBucketOwnershipControls(ctx *fiber.Ctx) (*Response, parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketOwnershipControlsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketOwnershipControlsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -127,14 +127,14 @@ func (c S3ApiController) GetBucketVersioning(ctx *fiber.Ctx) (*Response, error) parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketVersioningAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketVersioningAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -169,14 +169,14 @@ func (c S3ApiController) GetBucketCors(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketCorsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketCorsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -212,14 +212,14 @@ func (c S3ApiController) GetBucketPolicy(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketPolicyAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketPolicyAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -246,14 +246,14 @@ func (c S3ApiController) GetBucketPolicyStatus(ctx *fiber.Ctx) (*Response, error parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketPolicyStatusAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketPolicyStatusAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -307,14 +307,14 @@ func (c S3ApiController) ListObjectVersions(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.ListBucketVersionsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketVersionsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -362,14 +362,14 @@ func (c S3ApiController) GetObjectLockConfiguration(ctx *fiber.Ctx) (*Response, parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketObjectLockConfigurationAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketObjectLockConfigurationAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -407,14 +407,14 @@ func (c S3ApiController) GetBucketAcl(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionReadAcp, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.GetBucketAclAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionReadAcp, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.GetBucketAclAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -458,14 +458,14 @@ func (c S3ApiController) ListMultipartUploads(ctx *fiber.Ctx) (*Response, error) parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.ListBucketMultipartUploadsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketMultipartUploadsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -517,14 +517,14 @@ func (c S3ApiController) ListObjectsV2(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.ListBucketAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -576,14 +576,14 @@ func (c S3ApiController) ListObjects(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.ListBucketAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ diff --git a/s3api/controllers/bucket-head.go b/s3api/controllers/bucket-head.go index f4f063d..9d3d58e 100644 --- a/s3api/controllers/bucket-head.go +++ b/s3api/controllers/bucket-head.go @@ -31,14 +31,14 @@ func (c S3ApiController) HeadBucket(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.ListBucketAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.ListBucketAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ diff --git a/s3api/controllers/bucket-post.go b/s3api/controllers/bucket-post.go index 22e5f2f..a78340e 100644 --- a/s3api/controllers/bucket-post.go +++ b/s3api/controllers/bucket-post.go @@ -39,14 +39,14 @@ func (c S3ApiController) DeleteObjects(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.DeleteObjectAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.DeleteObjectAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ diff --git a/s3api/controllers/bucket-put.go b/s3api/controllers/bucket-put.go index 2290993..d55315e 100644 --- a/s3api/controllers/bucket-put.go +++ b/s3api/controllers/bucket-put.go @@ -40,14 +40,14 @@ func (c S3ApiController) PutBucketTagging(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketTaggingAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketTaggingAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -136,14 +136,14 @@ func (c S3ApiController) PutBucketVersioning(ctx *fiber.Ctx) (*Response, error) isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketVersioningAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketVersioningAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -190,14 +190,14 @@ func (c S3ApiController) PutObjectLockConfiguration(ctx *fiber.Ctx) (*Response, isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketObjectLockConfigurationAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketObjectLockConfigurationAction, + IsPublicRequest: isPublicBucket, }); err != nil { return &Response{ MetaOpts: &MetaOptions{ @@ -231,14 +231,14 @@ func (c S3ApiController) PutBucketCors(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Action: auth.PutBucketCorsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Action: auth.PutBucketCorsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ diff --git a/s3api/controllers/object-delete.go b/s3api/controllers/object-delete.go index 76dfb16..d625fe6 100644 --- a/s3api/controllers/object-delete.go +++ b/s3api/controllers/object-delete.go @@ -37,15 +37,15 @@ func (c S3ApiController) DeleteObjectTagging(ctx *fiber.Ctx) (*Response, error) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.DeleteObjectTaggingAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.DeleteObjectTaggingAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ @@ -76,15 +76,15 @@ func (c S3ApiController) AbortMultipartUpload(ctx *fiber.Ctx) (*Response, error) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.AbortMultipartUploadAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.AbortMultipartUploadAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ @@ -123,15 +123,15 @@ func (c S3ApiController) DeleteObject(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.DeleteObjectAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.DeleteObjectAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ diff --git a/s3api/controllers/object-get.go b/s3api/controllers/object-get.go index 422d578..e87f29d 100644 --- a/s3api/controllers/object-get.go +++ b/s3api/controllers/object-get.go @@ -41,15 +41,15 @@ func (c S3ApiController) GetObjectTagging(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectTaggingAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectTaggingAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -95,15 +95,15 @@ func (c S3ApiController) GetObjectRetention(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectRetentionAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectRetentionAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -142,15 +142,15 @@ func (c S3ApiController) GetObjectLegalHold(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectLegalHoldAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectLegalHoldAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -179,15 +179,15 @@ func (c S3ApiController) GetObjectAcl(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionReadAcp, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectAclAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionReadAcp, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectAclAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -221,15 +221,15 @@ func (c S3ApiController) ListParts(ctx *fiber.Ctx) (*Response, error) { isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.ListMultipartUploadPartsAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.ListMultipartUploadPartsAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -294,15 +294,15 @@ func (c S3ApiController) GetObjectAttributes(ctx *fiber.Ctx) (*Response, error) isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectAttributesAction, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectAttributesAction, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ @@ -380,28 +380,57 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) { versionId := ctx.Query("versionId") acceptRange := ctx.Get("Range") checksumMode := types.ChecksumMode(ctx.Get("x-amz-checksum-mode")) + + // Extract response override query parameters + responseOverrides := map[string]*string{ + "Cache-Control": utils.GetQueryParam(ctx, "response-cache-control"), + "Content-Disposition": utils.GetQueryParam(ctx, "response-content-disposition"), + "Content-Encoding": utils.GetQueryParam(ctx, "response-content-encoding"), + "Content-Language": utils.GetQueryParam(ctx, "response-content-language"), + "Content-Type": utils.GetQueryParam(ctx, "response-content-type"), + "Expires": utils.GetQueryParam(ctx, "response-expires"), + } + + // Check if any response override parameters are present + hasResponseOverrides := false + for _, override := range responseOverrides { + if override != nil { + hasResponseOverrides = true + break + } + } + // context locals acct := utils.ContextKeyAccount.Get(ctx).(auth.Account) isRoot := utils.ContextKeyIsRoot.Get(ctx).(bool) parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) - isPublicBucket := utils.ContextKeyPublicBucket.IsSet(ctx) + isPublicBucketRequest := utils.ContextKeyPublicBucket.IsSet(ctx) utils.ContextKeySkipResBodyLog.Set(ctx, true) + // Validate that response override parameters are not used with anonymous requests + if hasResponseOverrides && isPublicBucketRequest { + return &Response{ + MetaOpts: &MetaOptions{ + BucketOwner: parsedAcl.Owner, + }, + }, s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders) + } + action := auth.GetObjectAction if ctx.Request().URI().QueryArgs().Has("versionId") { action = auth.GetObjectVersionAction } err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: action, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: action, + IsPublicRequest: isPublicBucketRequest, }) if err != nil { return &Response{ @@ -478,17 +507,17 @@ func (c S3ApiController) GetObject(ctx *fiber.Ctx) (*Response, error) { "x-amz-restore": res.Restore, "accept-ranges": res.AcceptRanges, "Content-Range": res.ContentRange, - "Content-Disposition": res.ContentDisposition, - "Content-Encoding": res.ContentEncoding, - "Content-Language": res.ContentLanguage, - "Cache-Control": res.CacheControl, - "Expires": res.ExpiresString, + "Content-Disposition": utils.ApplyOverride(res.ContentDisposition, responseOverrides["Content-Disposition"]), + "Content-Encoding": utils.ApplyOverride(res.ContentEncoding, responseOverrides["Content-Encoding"]), + "Content-Language": utils.ApplyOverride(res.ContentLanguage, responseOverrides["Content-Language"]), + "Cache-Control": utils.ApplyOverride(res.CacheControl, responseOverrides["Cache-Control"]), + "Expires": utils.ApplyOverride(res.ExpiresString, responseOverrides["Expires"]), "x-amz-checksum-crc32": res.ChecksumCRC32, "x-amz-checksum-crc64nvme": res.ChecksumCRC64NVME, "x-amz-checksum-crc32c": res.ChecksumCRC32C, "x-amz-checksum-sha1": res.ChecksumSHA1, "x-amz-checksum-sha256": res.ChecksumSHA256, - "Content-Type": res.ContentType, + "Content-Type": utils.ApplyOverride(res.ContentType, responseOverrides["Content-Type"]), "x-amz-version-id": res.VersionId, "Content-Length": utils.ConvertPtrToStringPtr(res.ContentLength), "x-amz-mp-parts-count": utils.ConvertPtrToStringPtr(res.PartsCount), diff --git a/s3api/controllers/object-head.go b/s3api/controllers/object-head.go index 5599574..1361603 100644 --- a/s3api/controllers/object-head.go +++ b/s3api/controllers/object-head.go @@ -48,15 +48,15 @@ func (c S3ApiController) HeadObject(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: action, - IsBucketPublic: isPublicBucket, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: action, + IsPublicRequest: isPublicBucket, }) if err != nil { return &Response{ diff --git a/s3api/controllers/object-post.go b/s3api/controllers/object-post.go index 18b9280..0244a9c 100644 --- a/s3api/controllers/object-post.go +++ b/s3api/controllers/object-post.go @@ -41,15 +41,15 @@ func (c S3ApiController) RestoreObject(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.RestoreObjectAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.RestoreObjectAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ @@ -92,15 +92,15 @@ func (c S3ApiController) SelectObjectContent(ctx *fiber.Ctx) (*Response, error) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionRead, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.GetObjectAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionRead, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.GetObjectAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ @@ -243,15 +243,15 @@ func (c S3ApiController) CompleteMultipartUpload(ctx *fiber.Ctx) (*Response, err err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectAction, - IsBucketPublic: isBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectAction, + IsPublicRequest: isBucketPublic, }) if err != nil { return &Response{ diff --git a/s3api/controllers/object-put.go b/s3api/controllers/object-put.go index b2f8f9d..d5afd40 100644 --- a/s3api/controllers/object-put.go +++ b/s3api/controllers/object-put.go @@ -43,15 +43,15 @@ func (c S3ApiController) PutObjectTagging(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectTaggingAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectTaggingAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ @@ -90,15 +90,15 @@ func (c S3ApiController) PutObjectRetention(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectRetentionAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectRetentionAction, + IsPublicRequest: IsBucketPublic, }); err != nil { return &Response{ MetaOpts: &MetaOptions{ @@ -146,15 +146,15 @@ func (c S3ApiController) PutObjectLegalHold(ctx *fiber.Ctx) (*Response, error) { parsedAcl := utils.ContextKeyParsedAcl.Get(ctx).(auth.ACL) if err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectLegalHoldAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectLegalHoldAction, + IsPublicRequest: IsBucketPublic, }); err != nil { return &Response{ MetaOpts: &MetaOptions{ @@ -214,15 +214,15 @@ func (c S3ApiController) UploadPart(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ @@ -329,14 +329,14 @@ func (c S3ApiController) UploadPartCopy(ctx *fiber.Ctx) (*Response, error) { err = auth.VerifyObjectCopyAccess(ctx.Context(), c.be, copySource, auth.AccessOptions{ - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectAction, - IsBucketPublic: IsBucketPublic, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ @@ -628,15 +628,15 @@ func (c S3ApiController) PutObject(ctx *fiber.Ctx) (*Response, error) { err := auth.VerifyAccess(ctx.Context(), c.be, auth.AccessOptions{ - Readonly: c.readonly, - Acl: parsedAcl, - AclPermission: auth.PermissionWrite, - IsRoot: isRoot, - Acc: acct, - Bucket: bucket, - Object: key, - Action: auth.PutObjectAction, - IsBucketPublic: IsBucketPublic, + Readonly: c.readonly, + Acl: parsedAcl, + AclPermission: auth.PermissionWrite, + IsRoot: isRoot, + Acc: acct, + Bucket: bucket, + Object: key, + Action: auth.PutObjectAction, + IsPublicRequest: IsBucketPublic, }) if err != nil { return &Response{ diff --git a/s3api/middlewares/public-bucket.go b/s3api/middlewares/public-bucket.go index 5238308..11a62ec 100644 --- a/s3api/middlewares/public-bucket.go +++ b/s3api/middlewares/public-bucket.go @@ -30,7 +30,7 @@ import ( // access to anonymous requesters func AuthorizePublicBucketAccess(be backend.Backend, s3action string, policyPermission auth.Action, permission auth.Permission) fiber.Handler { return func(ctx *fiber.Ctx) error { - // skip for auhtneicated requests + // skip for authenticated requests if ctx.Query("X-Amz-Algorithm") != "" || ctx.Get("Authorization") != "" { return nil } diff --git a/s3api/utils/utils.go b/s3api/utils/utils.go index 9ce6670..e327810 100644 --- a/s3api/utils/utils.go +++ b/s3api/utils/utils.go @@ -779,3 +779,20 @@ func ValidateCopySource(copysource string) error { return nil } + +// GetQueryParam returns a pointer to the query parameter value if it exists +func GetQueryParam(ctx *fiber.Ctx, key string) *string { + value := ctx.Query(key) + if value == "" { + return nil + } + return &value +} + +// ApplyOverride returns the override value if it exists and status is 200, otherwise returns original +func ApplyOverride(original, override *string) *string { + if override != nil { + return override + } + return original +} diff --git a/s3err/s3err.go b/s3err/s3err.go index ce249fa..7b4d499 100644 --- a/s3err/s3err.go +++ b/s3err/s3err.go @@ -64,6 +64,7 @@ const ( ErrAnonymousCopyObject ErrAnonymousPutBucketOwnership ErrAnonymousGetBucketOwnership + ErrAnonymousResponseHeaders ErrMethodNotAllowed ErrBucketNotEmpty ErrVersionedBucketNotEmpty @@ -225,6 +226,11 @@ var errorCodeResponse = map[ErrorCode]APIError{ Description: "s3:GetBucketOwnershipControls does not support Anonymous requests!", HTTPStatusCode: http.StatusForbidden, }, + ErrAnonymousResponseHeaders: { + Code: "InvalidRequest", + Description: "Request specific response headers cannot be used for anonymous GET requests.", + HTTPStatusCode: http.StatusBadRequest, + }, ErrMethodNotAllowed: { Code: "MethodNotAllowed", Description: "The specified method is not allowed against this resource.", diff --git a/tests/integration/group-tests.go b/tests/integration/group-tests.go index 764aa21..b1be74b 100644 --- a/tests/integration/group-tests.go +++ b/tests/integration/group-tests.go @@ -211,6 +211,9 @@ func TestGetObject(s *S3Conf) { GetObject_directory_success(s) GetObject_by_range_resp_status(s) GetObject_non_existing_dir_object(s) + GetObject_overrides_success(s) + GetObject_overrides_presign_success(s) + GetObject_overrides_fail_public(s) } func TestListObjects(s *S3Conf) { @@ -896,7 +899,7 @@ func TestAccessControl(s *S3Conf) { } func TestPublicBuckets(s *S3Conf) { - PublicBucket_default_privet_bucket(s) + PublicBucket_default_private_bucket(s) PublicBucket_public_bucket_policy(s) PublicBucket_public_object_policy(s) PublicBucket_public_acl(s) @@ -1133,6 +1136,9 @@ func GetIntTests() IntTests { "GetObject_directory_success": GetObject_directory_success, "GetObject_by_range_resp_status": GetObject_by_range_resp_status, "GetObject_non_existing_dir_object": GetObject_non_existing_dir_object, + "GetObject_overrides_success": GetObject_overrides_success, + "GetObject_overrides_presign_success": GetObject_overrides_presign_success, + "GetObject_overrides_fail_public": GetObject_overrides_fail_public, "ListObjects_non_existing_bucket": ListObjects_non_existing_bucket, "ListObjects_with_prefix": ListObjects_with_prefix, "ListObjects_truncated": ListObjects_truncated, @@ -1484,7 +1490,7 @@ func GetIntTests() IntTests { "AccessControl_root_PutBucketAcl": AccessControl_root_PutBucketAcl, "AccessControl_user_PutBucketAcl_with_policy_access": AccessControl_user_PutBucketAcl_with_policy_access, "AccessControl_copy_object_with_starting_slash_for_user": AccessControl_copy_object_with_starting_slash_for_user, - "PublicBucket_default_privet_bucket": PublicBucket_default_privet_bucket, + "PublicBucket_default_private_bucket": PublicBucket_default_private_bucket, "PublicBucket_public_bucket_policy": PublicBucket_public_bucket_policy, "PublicBucket_public_object_policy": PublicBucket_public_object_policy, "PublicBucket_public_acl": PublicBucket_public_acl, diff --git a/tests/integration/tests.go b/tests/integration/tests.go index 07fd0b9..bc70f32 100644 --- a/tests/integration/tests.go +++ b/tests/integration/tests.go @@ -4773,6 +4773,405 @@ func GetObject_non_existing_dir_object(s *S3Conf) error { }) } +func GetObject_overrides_success(s *S3Conf) error { + testName := "GetObject_overrides_success" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + // Test data + objKey := "test-object" + objContent := "test content for response overrides" + exp := time.Now() + + // Put an object first + _, err := s3client.PutObject(context.Background(), &s3.PutObjectInput{ + Bucket: &bucket, + Key: &objKey, + Body: strings.NewReader(objContent), + }) + if err != nil { + return fmt.Errorf("failed to put object: %v", err) + } + + for _, test := range []PublicBucketTestCase{ + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseCacheControl: getPtr("max-age=90"), + }) + return err + }, + ExpectedErr: nil, + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentDisposition: getPtr("inline"), + }) + return err + }, + ExpectedErr: nil, + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentEncoding: getPtr("txt"), + }) + return err + }, + ExpectedErr: nil, + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentLanguage: getPtr("en"), + }) + return err + }, + ExpectedErr: nil, + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentType: getPtr("application/json"), + }) + return err + }, + ExpectedErr: nil, + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseExpires: &exp, + }) + return err + }, + ExpectedErr: nil, + }, + } { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + err := test.Call(ctx) + cancel() + if err == nil && test.ExpectedErr != nil { + return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) + } + if err != nil { + if test.ExpectedErr == nil { + return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) + } + + apiErr, ok := test.ExpectedErr.(s3err.APIError) + if !ok { + return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") + } + + if err := checkApiErr(err, apiErr); err != nil { + return err + } + } + } + + return nil + }) +} + +func GetObject_overrides_presign_success(s *S3Conf) error { + testName := "GetObject_overrides_presign_success" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + // Test data + objKey := "test-object" + objContent := "test content for response overrides" + + // Put an object first + _, err := s3client.PutObject(context.Background(), &s3.PutObjectInput{ + Bucket: &bucket, + Key: &objKey, + Body: strings.NewReader(objContent), + }) + if err != nil { + return fmt.Errorf("failed to put object: %v", err) + } + + // Test cases for each response override parameter + testCases := []struct { + name string + queryParam string + expectedHeader string + expectedValue string + }{ + { + name: "response-cache-control", + queryParam: "response-cache-control=no-cache", + expectedHeader: "Cache-Control", + expectedValue: "no-cache", + }, + { + name: "response-content-disposition", + queryParam: "response-content-disposition=attachment%3B%20filename%3D%22test.txt%22", + expectedHeader: "Content-Disposition", + expectedValue: "attachment; filename=\"test.txt\"", + }, + { + name: "response-content-encoding", + queryParam: "response-content-encoding=txt", + expectedHeader: "Content-Encoding", + expectedValue: "txt", + }, + { + name: "response-content-language", + queryParam: "response-content-language=en-US", + expectedHeader: "Content-Language", + expectedValue: "en-US", + }, + { + name: "response-content-type", + queryParam: "response-content-type=text%2Fplain", + expectedHeader: "Content-Type", + expectedValue: "text/plain", + }, + { + name: "response-expires", + queryParam: "response-expires=Thu%2C%2001%20Dec%202024%2016%3A00%3A00%20GMT", + expectedHeader: "Expires", + expectedValue: "Thu, 01 Dec 2024 16:00:00 GMT", + }, + } + + // Test each override parameter individually + for _, tc := range testCases { + // Create a signed request with the response override parameter + req, err := createSignedReq( + http.MethodGet, + s.endpoint, + fmt.Sprintf("%s/%s?%s", bucket, objKey, tc.queryParam), + s.awsID, + s.awsSecret, + "s3", + s.awsRegion, + nil, + time.Now(), + nil, + ) + if err != nil { + return fmt.Errorf("failed to create signed request for %s: %v", tc.name, err) + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request for %s: %v", tc.name, err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status 200 for %s, got %d", tc.name, resp.StatusCode) + } + + // Verify the response override header is set correctly + actualValue := resp.Header.Get(tc.expectedHeader) + if actualValue != tc.expectedValue { + return fmt.Errorf("expected %s header to be %q for %s, got %q", + tc.expectedHeader, tc.expectedValue, tc.name, actualValue) + } + + // Verify content is still correct + body, err := io.ReadAll(resp.Body) + if err != nil { + return fmt.Errorf("failed to read response body for %s: %v", tc.name, err) + } + + if string(body) != objContent { + return fmt.Errorf("expected content %q for %s, got %q", objContent, tc.name, string(body)) + } + } + + // Test multiple override parameters together + multiParam := "response-cache-control=max-age%3D3600&response-content-type=application%2Fjson&response-content-disposition=inline" + req, err := createSignedReq( + http.MethodGet, + s.endpoint, + fmt.Sprintf("%s/%s?%s", bucket, objKey, multiParam), + s.awsID, + s.awsSecret, + "s3", + s.awsRegion, + nil, + time.Now(), + nil, + ) + if err != nil { + return fmt.Errorf("failed to create signed request for multiple overrides: %v", err) + } + + resp, err := s.httpClient.Do(req) + if err != nil { + return fmt.Errorf("failed to execute request for multiple overrides: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("expected status 200 for multiple overrides, got %d", resp.StatusCode) + } + + // Verify all override headers are set correctly + expectedHeaders := map[string]string{ + "Cache-Control": "max-age=3600", + "Content-Type": "application/json", + "Content-Disposition": "inline", + } + + for headerName, expectedValue := range expectedHeaders { + actualValue := resp.Header.Get(headerName) + if actualValue != expectedValue { + return fmt.Errorf("expected %s header to be %q for multiple overrides, got %q", + headerName, expectedValue, actualValue) + } + } + + return nil + }) +} + +func GetObject_overrides_fail_public(s *S3Conf) error { + testName := "GetObject_overrides_fail_public" + return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { + rootClient := s.GetClient() + // Grant public access to the bucket for bucket operations + err := grantPublicBucketPolicy(rootClient, bucket, policyTypeObject) + if err != nil { + return err + } + + // Test data + objKey := "test-object" + objContent := "test content for response overrides" + exp := time.Now() + + // Put an object first + _, err = rootClient.PutObject(context.Background(), &s3.PutObjectInput{ + Bucket: &bucket, + Key: &objKey, + Body: strings.NewReader(objContent), + }) + if err != nil { + return fmt.Errorf("failed to put object: %v", err) + } + + for _, test := range []PublicBucketTestCase{ + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseCacheControl: getPtr("max-age=90"), + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentDisposition: getPtr("inline"), + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentEncoding: getPtr("txt"), + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentLanguage: getPtr("en"), + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseContentType: getPtr("application/json"), + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + { + Action: "GetObject", + Call: func(ctx context.Context) error { + _, err := s3client.GetObject(ctx, &s3.GetObjectInput{ + Bucket: &bucket, + Key: &objKey, + ResponseExpires: &exp, + }) + return err + }, + ExpectedErr: s3err.GetAPIError(s3err.ErrAnonymousResponseHeaders), + }, + } { + ctx, cancel := context.WithTimeout(context.Background(), shortTimeout) + err := test.Call(ctx) + cancel() + if err == nil && test.ExpectedErr != nil { + return fmt.Errorf("%v: expected err %v, instead got successful response", test.Action, test.ExpectedErr) + } + if err != nil { + if test.ExpectedErr == nil { + return fmt.Errorf("%v: expected no error, instead got %v", test.Action, err) + } + + apiErr, ok := test.ExpectedErr.(s3err.APIError) + if !ok { + return fmt.Errorf("invalid error type provided in the test, expected s3err.APIError") + } + + if err := checkApiErr(err, apiErr); err != nil { + return err + } + } + } + + return nil + }, withAnonymousClient()) +} + func ListObjects_non_existing_bucket(s *S3Conf) error { testName := "ListObjects_non_existing_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { @@ -17194,8 +17593,8 @@ func AccessControl_copy_object_with_starting_slash_for_user(s *S3Conf) error { } // Public bucket tests -func PublicBucket_default_privet_bucket(s *S3Conf) error { - testName := "PublicBucket_default_privet_bucket" +func PublicBucket_default_private_bucket(s *S3Conf) error { + testName := "PublicBucket_default_private_bucket" return actionHandler(s, testName, func(s3client *s3.Client, bucket string) error { partNumber := int32(1)