forked from TrueCloudLab/frostfs-s3-gw
[#49] Add basic ACL translation
Implement functions: GetBucketACL, PutBucketACL, GetObjectACL, PutObjectACL, GetBucketPolicy, PutBucketPolicy Signed-off-by: Denis Kirillov <denis@nspcc.ru>
This commit is contained in:
parent
b1c6629b10
commit
efe11c271f
10 changed files with 2046 additions and 100 deletions
20
README.md
20
README.md
|
@ -448,6 +448,14 @@ $ aws s3api delete-object --bucket %BUCKET_NAME --key %FILE_NAME
|
|||
Reference:
|
||||
* [AWS S3 API Reference](https://docs.aws.amazon.com/AmazonS3/latest/API/s3-api.pdf)
|
||||
|
||||
### Limitations
|
||||
#### ACL
|
||||
For now there are some restrictions:
|
||||
* [Bucket policy](https://docs.aws.amazon.com/AmazonS3/latest/userguide/bucket-policies.html)
|
||||
support only one `Principal` (type `AWS`) per `Statement`. To refer all users use `"AWS": "*"`
|
||||
* AWS conditions and wildcard are not supported in [resources](https://docs.aws.amazon.com/AmazonS3/latest/userguide/s3-arn-format.html)
|
||||
* Only `CanonicalUser` (with hex encoded public key) and `All Users Group` are supported in [ACL](https://docs.aws.amazon.com/AmazonS3/latest/userguide/acl-overview.html)
|
||||
|
||||
### Object
|
||||
|
||||
| Method | Status |
|
||||
|
@ -469,8 +477,8 @@ Reference:
|
|||
|
||||
| Method | Status |
|
||||
| ------------------------- | ----------------------- |
|
||||
| GetObjectAcl | Unsupported |
|
||||
| PutObjectAcl | Unsupported |
|
||||
| GetObjectAcl | Supported |
|
||||
| PutObjectAcl | Supported |
|
||||
|
||||
#### Locking
|
||||
|
||||
|
@ -540,8 +548,8 @@ See also `GetObject` and other method parameters.
|
|||
|
||||
| Method | Status |
|
||||
| ------------------------- | ----------------------- |
|
||||
| GetBucketAcl | Unsupported |
|
||||
| PutBucketAcl | Unsupported |
|
||||
| GetBucketAcl | Supported |
|
||||
| PutBucketAcl | Supported |
|
||||
|
||||
#### Analytics
|
||||
|
||||
|
@ -630,11 +638,11 @@ See also `GetObject` and other method parameters.
|
|||
| DeleteBucketPolicy | Unsupported |
|
||||
| DeleteBucketReplication | Unsupported |
|
||||
| DeletePublicAccessBlock | Unsupported |
|
||||
| GetBucketPolicy | Unsupported |
|
||||
| GetBucketPolicy | Supported |
|
||||
| GetBucketPolicyStatus | Unsupported |
|
||||
| GetBucketReplication | Unsupported |
|
||||
| PostPolicyBucket | Unsupported, non-standard? |
|
||||
| PutBucketPolicy | Unsupported |
|
||||
| PutBucketPolicy | Supported |
|
||||
| PutBucketReplication | Unsupported |
|
||||
|
||||
#### Request payment
|
||||
|
|
|
@ -298,6 +298,7 @@ const (
|
|||
ErrEvaluatorInvalidTimestampFormatPatternSymbol
|
||||
ErrEvaluatorBindingDoesNotExist
|
||||
ErrMissingHeaders
|
||||
ErrInvalidArgument
|
||||
ErrInvalidColumnIndex
|
||||
|
||||
ErrAdminConfigNotificationTargetsFailed
|
||||
|
@ -1838,6 +1839,12 @@ var errorCodes = errorCodeMap{
|
|||
Description: "Some headers in the query are missing from the file. Check the file and try again.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidArgument: {
|
||||
ErrCode: ErrInvalidArgument,
|
||||
Code: "InvalidArgument",
|
||||
Description: "The specified argument was invalid.",
|
||||
HTTPStatusCode: http.StatusBadRequest,
|
||||
},
|
||||
ErrInvalidColumnIndex: {
|
||||
ErrCode: ErrInvalidColumnIndex,
|
||||
Code: "InvalidColumnIndex",
|
||||
|
|
1158
api/handler/acl.go
Normal file
1158
api/handler/acl.go
Normal file
File diff suppressed because it is too large
Load diff
698
api/handler/acl_test.go
Normal file
698
api/handler/acl_test.go
Normal file
|
@ -0,0 +1,698 @@
|
|||
package handler
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"encoding/hex"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/object"
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||
"github.com/nspcc-dev/neofs-s3-gw/creds/accessbox"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestTableToAst(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
key2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
table := new(eacl.Table)
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(eacl.ActionAllow)
|
||||
record.SetOperation(eacl.OperationGet)
|
||||
eacl.AddFormedTarget(record, eacl.RoleOthers)
|
||||
table.AddRecord(record)
|
||||
record2 := eacl.NewRecord()
|
||||
record2.SetAction(eacl.ActionDeny)
|
||||
record2.SetOperation(eacl.OperationPut)
|
||||
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key.PublicKey()))
|
||||
eacl.AddFormedTarget(record2, eacl.RoleUser, *(*ecdsa.PublicKey)(key2.PublicKey()))
|
||||
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
||||
table.AddRecord(record2)
|
||||
|
||||
expectedAst := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "bucketName",
|
||||
Operations: []*astOperation{{
|
||||
Role: eacl.RoleOthers,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionAllow,
|
||||
}}},
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{
|
||||
hex.EncodeToString(key.PublicKey().Bytes()),
|
||||
hex.EncodeToString(key2.PublicKey().Bytes()),
|
||||
},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}}},
|
||||
},
|
||||
}
|
||||
|
||||
actualAst := tableToAst(table, expectedAst.Resources[0].Name)
|
||||
|
||||
if actualAst.Resources[0].Name == expectedAst.Resources[0].Name {
|
||||
require.Equal(t, expectedAst, actualAst)
|
||||
} else {
|
||||
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
||||
require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1])
|
||||
require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestPolicyToAst(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
policy := &bucketPolicy{
|
||||
Statement: []statement{
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: principal{AWS: allUsersWildcard},
|
||||
Action: []string{"s3:PutObject"},
|
||||
Resource: []string{"arn:aws:s3:::bucketName"},
|
||||
},
|
||||
{
|
||||
Effect: "Deny",
|
||||
Principal: principal{
|
||||
CanonicalUser: hex.EncodeToString(key.PublicKey().Bytes()),
|
||||
},
|
||||
Action: []string{"s3:GetObject"},
|
||||
Resource: []string{"arn:aws:s3:::bucketName/object"},
|
||||
}},
|
||||
}
|
||||
|
||||
expectedAst := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "bucketName",
|
||||
Operations: []*astOperation{{
|
||||
Role: eacl.RoleOthers,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionAllow,
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "bucketName/object",
|
||||
Operations: getReadOps(key, eacl.RoleUser, eacl.ActionDeny),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualAst, err := policyToAst(policy)
|
||||
require.NoError(t, err)
|
||||
|
||||
if actualAst.Resources[0].Name == expectedAst.Resources[0].Name {
|
||||
require.Equal(t, expectedAst, actualAst)
|
||||
} else {
|
||||
require.Equal(t, len(expectedAst.Resources), len(actualAst.Resources))
|
||||
require.Equal(t, expectedAst.Resources[0], actualAst.Resources[1])
|
||||
require.Equal(t, expectedAst.Resources[1], actualAst.Resources[0])
|
||||
}
|
||||
}
|
||||
|
||||
func getReadOps(key *keys.PrivateKey, role eacl.Role, action eacl.Action) []*astOperation {
|
||||
var result []*astOperation
|
||||
|
||||
for _, op := range readOps {
|
||||
result = append(result, &astOperation{
|
||||
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
||||
Role: role,
|
||||
Op: op,
|
||||
Action: action,
|
||||
})
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func TestMergeAstUnModified(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
child := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parent := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "bucket",
|
||||
Operations: []*astOperation{{
|
||||
Role: eacl.RoleOthers,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionAllow,
|
||||
}},
|
||||
},
|
||||
child.Resources[0],
|
||||
},
|
||||
}
|
||||
|
||||
result, updated := mergeAst(parent, child)
|
||||
require.False(t, updated)
|
||||
require.Equal(t, parent, result)
|
||||
}
|
||||
|
||||
func TestMergeAstModified(t *testing.T) {
|
||||
child := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Role: eacl.RoleOthers,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}, {
|
||||
Users: []string{"user2"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionDeny,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parent := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{"user1"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionDeny,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{
|
||||
child.Resources[0].Operations[0],
|
||||
{
|
||||
Users: []string{"user1", "user2"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionDeny,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual, updated := mergeAst(parent, child)
|
||||
require.True(t, updated)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestMergeAstModifiedConflict(t *testing.T) {
|
||||
child := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{"user1"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}, {
|
||||
Users: []string{"user3"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionAllow,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
parent := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{"user1"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionAllow,
|
||||
}, {
|
||||
Users: []string{"user2"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}, {
|
||||
Users: []string{"user3"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionDeny,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expected := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "objectName",
|
||||
Operations: []*astOperation{
|
||||
{
|
||||
Users: []string{"user2", "user1"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionDeny,
|
||||
}, {
|
||||
Users: []string{"user3"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionAllow,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actual, updated := mergeAst(parent, child)
|
||||
require.True(t, updated)
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
||||
|
||||
func TestAstToTable(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
ast := &ast{
|
||||
Resources: []*astResource{
|
||||
{
|
||||
Name: "bucketName",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{hex.EncodeToString(key.PublicKey().Bytes())},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionAllow,
|
||||
}},
|
||||
},
|
||||
{
|
||||
Name: "bucketName/objectName",
|
||||
Operations: []*astOperation{{
|
||||
Role: eacl.RoleOthers,
|
||||
Op: eacl.OperationGet,
|
||||
Action: eacl.ActionDeny,
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
expectedTable := eacl.NewTable()
|
||||
record := eacl.NewRecord()
|
||||
record.SetAction(eacl.ActionAllow)
|
||||
record.SetOperation(eacl.OperationPut)
|
||||
eacl.AddFormedTarget(record, eacl.RoleUser, *(*ecdsa.PublicKey)(key.PublicKey()))
|
||||
expectedTable.AddRecord(record)
|
||||
record2 := eacl.NewRecord()
|
||||
record2.SetAction(eacl.ActionDeny)
|
||||
record2.SetOperation(eacl.OperationGet)
|
||||
eacl.AddFormedTarget(record2, eacl.RoleOthers)
|
||||
record2.AddObjectAttributeFilter(eacl.MatchStringEqual, object.AttributeFileName, "objectName")
|
||||
expectedTable.AddRecord(record2)
|
||||
|
||||
actualTable, err := astToTable(ast, "bucketName")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedTable, actualTable)
|
||||
}
|
||||
|
||||
func TestRemoveUsers(t *testing.T) {
|
||||
resource := &astResource{
|
||||
Name: "name",
|
||||
Operations: []*astOperation{{
|
||||
Users: []string{"user1", "user3"},
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionAllow,
|
||||
}},
|
||||
}
|
||||
|
||||
op := &astOperation{
|
||||
Role: eacl.RoleUser,
|
||||
Op: eacl.OperationPut,
|
||||
Action: eacl.ActionAllow,
|
||||
}
|
||||
|
||||
removeUsers(resource, op, []string{"user1", "user2"})
|
||||
|
||||
require.Equal(t, len(resource.Operations), 1)
|
||||
require.Equal(t, resource.Name, resource.Name)
|
||||
require.Equal(t, resource.Operations[0].Users, []string{"user3"})
|
||||
}
|
||||
|
||||
func TestBucketAclToPolicy(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
key2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||
id2 := hex.EncodeToString(key2.PublicKey().Bytes())
|
||||
|
||||
acl := &AccessControlPolicy{
|
||||
Owner: Owner{
|
||||
ID: id,
|
||||
DisplayName: "user1",
|
||||
},
|
||||
AccessControlList: []*Grant{{
|
||||
Grantee: &Grantee{
|
||||
URI: allUsersGroup,
|
||||
Type: acpGroup,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: id2,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclWrite,
|
||||
}},
|
||||
Resource: "bucketName",
|
||||
IsBucket: true,
|
||||
}
|
||||
|
||||
expectedPolicy := &bucketPolicy{
|
||||
Statement: []statement{
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: principal{
|
||||
CanonicalUser: id,
|
||||
},
|
||||
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads", "s3:PutObject", "s3:DeleteObject"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
}, {
|
||||
Effect: "Allow",
|
||||
Principal: principal{AWS: allUsersWildcard},
|
||||
Action: []string{"s3:ListBucket", "s3:ListBucketVersions", "s3:ListBucketMultipartUploads"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
}, {
|
||||
Effect: "Allow",
|
||||
Principal: principal{
|
||||
CanonicalUser: id2,
|
||||
},
|
||||
Action: []string{"s3:PutObject", "s3:DeleteObject"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualPolicy, err := aclToPolicy(acl)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedPolicy, actualPolicy)
|
||||
}
|
||||
|
||||
func TestObjectAclToPolicy(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
key2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||
id2 := hex.EncodeToString(key2.PublicKey().Bytes())
|
||||
|
||||
acl := &AccessControlPolicy{
|
||||
Owner: Owner{
|
||||
ID: id,
|
||||
DisplayName: "user1",
|
||||
},
|
||||
AccessControlList: []*Grant{{
|
||||
Grantee: &Grantee{
|
||||
ID: id,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclFullControl,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: id2,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclFullControl,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
URI: allUsersGroup,
|
||||
Type: acpGroup,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}},
|
||||
Resource: "bucketName/object",
|
||||
IsBucket: false,
|
||||
}
|
||||
|
||||
expectedPolicy := &bucketPolicy{
|
||||
Statement: []statement{
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: principal{
|
||||
CanonicalUser: id,
|
||||
},
|
||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
},
|
||||
{
|
||||
Effect: "Allow",
|
||||
Principal: principal{
|
||||
CanonicalUser: id2,
|
||||
},
|
||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
}, {
|
||||
Effect: "Allow",
|
||||
Principal: principal{AWS: allUsersWildcard},
|
||||
Action: []string{"s3:GetObject", "s3:GetObjectVersion"},
|
||||
Resource: []string{arnAwsPrefix + acl.Resource},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
actualPolicy, err := aclToPolicy(acl)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedPolicy, actualPolicy)
|
||||
}
|
||||
|
||||
func TestParseCannedACLHeaders(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||
address := key.PublicKey().Address()
|
||||
|
||||
req := &http.Request{
|
||||
Header: map[string][]string{
|
||||
api.AmzACL: {basicACLReadOnly},
|
||||
},
|
||||
}
|
||||
|
||||
box := &accessbox.Box{
|
||||
Gate: &accessbox.GateData{
|
||||
GateKey: key.PublicKey(),
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, api.BoxData, box)
|
||||
|
||||
expectedACL := &AccessControlPolicy{
|
||||
Owner: Owner{
|
||||
ID: id,
|
||||
DisplayName: address,
|
||||
},
|
||||
AccessControlList: []*Grant{{
|
||||
Grantee: &Grantee{
|
||||
ID: id,
|
||||
DisplayName: address,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclFullControl,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
URI: allUsersGroup,
|
||||
Type: acpGroup,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}},
|
||||
}
|
||||
|
||||
actualACL, err := parseACLHeaders(req.WithContext(ctx))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedACL, actualACL)
|
||||
}
|
||||
|
||||
func TestParseACLHeaders(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||
address := key.PublicKey().Address()
|
||||
|
||||
req := &http.Request{
|
||||
Header: map[string][]string{
|
||||
api.AmzGrantFullControl: {"id=\"user1\""},
|
||||
api.AmzGrantRead: {"uri=\"" + allUsersGroup + "\", id=\"user2\""},
|
||||
api.AmzGrantWrite: {"id=\"user2\", id=\"user3\""},
|
||||
},
|
||||
}
|
||||
|
||||
box := &accessbox.Box{
|
||||
Gate: &accessbox.GateData{
|
||||
GateKey: key.PublicKey(),
|
||||
},
|
||||
}
|
||||
ctx := context.Background()
|
||||
ctx = context.WithValue(ctx, api.BoxData, box)
|
||||
|
||||
expectedACL := &AccessControlPolicy{
|
||||
Owner: Owner{
|
||||
ID: id,
|
||||
DisplayName: address,
|
||||
},
|
||||
AccessControlList: []*Grant{{
|
||||
Grantee: &Grantee{
|
||||
ID: id,
|
||||
DisplayName: address,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclFullControl,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: "user1",
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclFullControl,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
URI: allUsersGroup,
|
||||
Type: acpGroup,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: "user2",
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: "user2",
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclWrite,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: "user3",
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclWrite,
|
||||
}},
|
||||
}
|
||||
|
||||
actualACL, err := parseACLHeaders(req.WithContext(ctx))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedACL, actualACL)
|
||||
}
|
||||
|
||||
func TestAddGranteeError(t *testing.T) {
|
||||
headers := map[string][]string{
|
||||
api.AmzGrantFullControl: {"i=\"user1\""},
|
||||
api.AmzGrantRead: {"uri, id=\"user2\""},
|
||||
api.AmzGrantWrite: {"emailAddress=\"user2\""},
|
||||
"unknown header": {"something"},
|
||||
}
|
||||
|
||||
expectedList := []*Grant{{
|
||||
Permission: "predefined",
|
||||
}}
|
||||
|
||||
actualList, err := addGrantees(expectedList, headers, "unknown header1")
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedList, actualList)
|
||||
|
||||
actualList, err = addGrantees(expectedList, headers, "unknown header")
|
||||
require.Error(t, err)
|
||||
require.Nil(t, actualList)
|
||||
|
||||
actualList, err = addGrantees(expectedList, headers, api.AmzGrantFullControl)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, actualList)
|
||||
|
||||
actualList, err = addGrantees(expectedList, headers, api.AmzGrantRead)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, actualList)
|
||||
|
||||
actualList, err = addGrantees(expectedList, headers, api.AmzGrantWrite)
|
||||
require.Error(t, err)
|
||||
require.Nil(t, actualList)
|
||||
}
|
||||
|
||||
func TestBucketAclToTable(t *testing.T) {
|
||||
key, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
key2, err := keys.NewPrivateKey()
|
||||
require.NoError(t, err)
|
||||
|
||||
id := hex.EncodeToString(key.PublicKey().Bytes())
|
||||
id2 := hex.EncodeToString(key2.PublicKey().Bytes())
|
||||
|
||||
acl := &AccessControlPolicy{
|
||||
Owner: Owner{
|
||||
ID: id,
|
||||
DisplayName: "user1",
|
||||
},
|
||||
AccessControlList: []*Grant{{
|
||||
Grantee: &Grantee{
|
||||
URI: allUsersGroup,
|
||||
Type: acpGroup,
|
||||
},
|
||||
Permission: aclRead,
|
||||
}, {
|
||||
Grantee: &Grantee{
|
||||
ID: id2,
|
||||
Type: acpCanonicalUser,
|
||||
},
|
||||
Permission: aclWrite,
|
||||
}},
|
||||
Resource: "bucketName",
|
||||
IsBucket: true,
|
||||
}
|
||||
|
||||
expectedTable := new(eacl.Table)
|
||||
for _, op := range readOps {
|
||||
expectedTable.AddRecord(getOthersRecord(op, eacl.ActionAllow))
|
||||
}
|
||||
for _, op := range writeOps {
|
||||
expectedTable.AddRecord(getAllowRecord(op, key2.PublicKey()))
|
||||
}
|
||||
for _, op := range fullOps {
|
||||
expectedTable.AddRecord(getAllowRecord(op, key.PublicKey()))
|
||||
}
|
||||
for _, op := range fullOps {
|
||||
expectedTable.AddRecord(getOthersRecord(op, eacl.ActionDeny))
|
||||
}
|
||||
|
||||
actualTable, err := bucketACLToTable(acl)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expectedTable.Records(), actualTable.Records())
|
||||
}
|
|
@ -2,27 +2,24 @@ package handler
|
|||
|
||||
import (
|
||||
"encoding/xml"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl"
|
||||
"github.com/nspcc-dev/neofs-node/pkg/policy"
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api"
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api/errors"
|
||||
"github.com/nspcc-dev/neofs-s3-gw/api/layer"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// keywords of predefined basic ACL values.
|
||||
const (
|
||||
basicACLPrivate = "private"
|
||||
basicACLReadOnly = "public-read"
|
||||
basicACLPublic = "public-read-write"
|
||||
defaultPolicy = "REP 3"
|
||||
basicACLPrivate = "private"
|
||||
basicACLReadOnly = "public-read"
|
||||
basicACLPublic = "public-read-write"
|
||||
cannedACLAuthRead = "authenticated-read"
|
||||
defaultPolicy = "REP 3"
|
||||
|
||||
publicBasicRule = 0x0FFFFFFF
|
||||
)
|
||||
|
@ -39,6 +36,50 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
reqInfo = api.GetReqInfo(r.Context())
|
||||
)
|
||||
|
||||
objectACL, err := parseACLHeaders(r)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not parse object acl", reqInfo, err)
|
||||
return
|
||||
}
|
||||
objectACL.Resource = reqInfo.BucketName + "/" + reqInfo.ObjectName
|
||||
|
||||
bktPolicy, err := aclToPolicy(objectACL)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not translate object acl to bucket policy", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
astChild, err := policyToAst(bktPolicy)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not translate policy to ast", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
bacl, err := h.obj.GetBucketACL(r.Context(), reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not get bucket eacl", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if err = checkOwner(bacl.Info, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil {
|
||||
h.logAndSendError(w, "expected owner doesn't match", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
parentAst := tableToAst(bacl.EACL, reqInfo.BucketName)
|
||||
for _, resource := range parentAst.Resources {
|
||||
if resource.Name == bacl.Info.CID.String() {
|
||||
resource.Name = reqInfo.BucketName
|
||||
}
|
||||
}
|
||||
|
||||
resAst, updated := mergeAst(parentAst, astChild)
|
||||
table, err := astToTable(resAst, reqInfo.BucketName)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not translate ast to table", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
metadata := parseMetadata(r)
|
||||
if contentType := r.Header.Get(api.ContentType); len(contentType) > 0 {
|
||||
metadata[api.ContentType] = contentType
|
||||
|
@ -57,6 +98,18 @@ func (h *handler) PutObjectHandler(w http.ResponseWriter, r *http.Request) {
|
|||
return
|
||||
}
|
||||
|
||||
if updated {
|
||||
p := &layer.PutBucketACLParams{
|
||||
Name: reqInfo.BucketName,
|
||||
EACL: table,
|
||||
}
|
||||
|
||||
if err = h.obj.PutBucketACL(r.Context(), p); err != nil {
|
||||
h.logAndSendError(w, "could not put bucket acl", reqInfo, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
w.Header().Set(api.ETag, info.HashSum)
|
||||
api.WriteSuccessResponseHeadersOnly(w)
|
||||
}
|
||||
|
@ -74,24 +127,25 @@ func parseMetadata(r *http.Request) map[string]string {
|
|||
|
||||
func (h *handler) CreateBucketHandler(w http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
err error
|
||||
reqInfo = api.GetReqInfo(r.Context())
|
||||
p = layer.CreateBucketParams{Name: reqInfo.BucketName}
|
||||
p = layer.CreateBucketParams{Name: reqInfo.BucketName, ACL: publicBasicRule}
|
||||
)
|
||||
|
||||
if err = checkBucketName(reqInfo.BucketName); err != nil {
|
||||
if err := checkBucketName(reqInfo.BucketName); err != nil {
|
||||
h.logAndSendError(w, "invalid bucket name", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
if val, ok := r.Header["X-Amz-Acl"]; ok {
|
||||
p.ACL, err = parseBasicACL(val[0])
|
||||
} else {
|
||||
p.ACL = publicBasicRule
|
||||
}
|
||||
|
||||
bktACL, err := parseACLHeaders(r)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could not parse basic ACL", reqInfo, err)
|
||||
h.logAndSendError(w, "could not parse bucket acl", reqInfo, err)
|
||||
return
|
||||
}
|
||||
bktACL.IsBucket = true
|
||||
|
||||
p.EACL, err = bucketACLToTable(bktACL)
|
||||
if err != nil {
|
||||
h.logAndSendError(w, "could translate bucket acl to eacl", reqInfo, err)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -180,23 +234,3 @@ func parseLocationConstraint(r *http.Request) (*createBucketParams, error) {
|
|||
}
|
||||
return params, nil
|
||||
}
|
||||
|
||||
func parseBasicACL(basicACL string) (uint32, error) {
|
||||
switch basicACL {
|
||||
case basicACLPublic:
|
||||
return acl.PublicBasicRule, nil
|
||||
case basicACLPrivate:
|
||||
return acl.PrivateBasicRule, nil
|
||||
case basicACLReadOnly:
|
||||
return acl.ReadOnlyBasicRule, nil
|
||||
default:
|
||||
basicACL = strings.Trim(strings.ToLower(basicACL), "0x")
|
||||
|
||||
value, err := strconv.ParseUint(basicACL, 16, 32)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("can't parse basic ACL: %s", basicACL)
|
||||
}
|
||||
|
||||
return uint32(value), nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -52,6 +52,41 @@ type Bucket struct {
|
|||
CreationDate string // time string of format "2006-01-02T15:04:05.000Z"
|
||||
}
|
||||
|
||||
// AccessControlPolicy contains ACL.
|
||||
type AccessControlPolicy struct {
|
||||
XMLName xml.Name `xml:"http://s3.amazonaws.com/doc/2006-03-01/ AccessControlPolicy" json:"-"`
|
||||
Owner Owner
|
||||
AccessControlList []*Grant `xml:"AccessControlList>Grant"`
|
||||
Resource string `xml:"-"`
|
||||
IsBucket bool `xml:"-"`
|
||||
}
|
||||
|
||||
// Grant is container for Grantee data.
|
||||
type Grant struct {
|
||||
Grantee *Grantee
|
||||
Permission AWSACL
|
||||
}
|
||||
|
||||
// Grantee is info about access rights of some actor.
|
||||
type Grantee struct {
|
||||
XMLName xml.Name `xml:"Grantee"`
|
||||
XMLNS string `xml:"xmlns:xsi,attr"`
|
||||
ID string `xml:"ID,omitempty"`
|
||||
DisplayName string `xml:"DisplayName,omitempty"`
|
||||
EmailAddress string `xml:"EmailAddress,omitempty"`
|
||||
URI string `xml:"URI,omitempty"`
|
||||
Type GranteeType `xml:"xsi:type,attr"`
|
||||
}
|
||||
|
||||
// NewGrantee creates new grantee using workaround
|
||||
// https://github.com/golang/go/issues/9519#issuecomment-252196382
|
||||
func NewGrantee(t GranteeType) *Grantee {
|
||||
return &Grantee{
|
||||
XMLNS: "http://www.w3.org/2001/XMLSchema-instance",
|
||||
Type: t,
|
||||
}
|
||||
}
|
||||
|
||||
// Owner - bucket owner/principal.
|
||||
type Owner struct {
|
||||
ID string
|
||||
|
|
|
@ -31,14 +31,6 @@ func (h *handler) AbortMultipartUploadHandler(w http.ResponseWriter, r *http.Req
|
|||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) PutObjectACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetObjectTaggingHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
@ -71,10 +63,6 @@ func (h *handler) PutObjectLegalHoldHandler(w http.ResponseWriter, r *http.Reque
|
|||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetBucketLifecycleHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
@ -83,14 +71,6 @@ func (h *handler) GetBucketEncryptionHandler(w http.ResponseWriter, r *http.Requ
|
|||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) PutBucketACLHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) GetBucketCorsHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
@ -151,10 +131,6 @@ func (h *handler) PutBucketEncryptionHandler(w http.ResponseWriter, r *http.Requ
|
|||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) PutBucketPolicyHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
||||
func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) {
|
||||
h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented))
|
||||
}
|
||||
|
|
|
@ -34,4 +34,9 @@ const (
|
|||
AmzCopyIfUnmodifiedSince = "X-Amz-Copy-Source-If-Unmodified-Since"
|
||||
AmzCopyIfMatch = "X-Amz-Copy-Source-If-Match"
|
||||
AmzCopyIfNoneMatch = "X-Amz-Copy-Source-If-None-Match"
|
||||
AmzACL = "X-Amz-Acl"
|
||||
AmzGrantFullControl = "X-Amz-Grant-Full-Control"
|
||||
AmzGrantRead = "X-Amz-Grant-Read"
|
||||
AmzGrantWrite = "X-Amz-Grant-Write"
|
||||
AmzExpectedBucketOwner = "X-Amz-Expected-Bucket-Owner"
|
||||
)
|
||||
|
|
|
@ -3,13 +3,11 @@ package layer
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/ecdsa"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/container"
|
||||
cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id"
|
||||
|
@ -23,10 +21,16 @@ import (
|
|||
type (
|
||||
// BucketInfo stores basic bucket data.
|
||||
BucketInfo struct {
|
||||
Name string
|
||||
CID *cid.ID
|
||||
Owner *owner.ID
|
||||
Created time.Time
|
||||
Name string
|
||||
CID *cid.ID
|
||||
Owner *owner.ID
|
||||
Created time.Time
|
||||
BasicACL uint32
|
||||
}
|
||||
// BucketACL extends BucketInfo by eacl.Table.
|
||||
BucketACL struct {
|
||||
Info *BucketInfo
|
||||
EACL *eacl.Table
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -56,6 +60,7 @@ func (n *layer) containerInfo(ctx context.Context, cid *cid.ID) (*BucketInfo, er
|
|||
}
|
||||
|
||||
info.Owner = res.OwnerID()
|
||||
info.BasicACL = res.BasicACL()
|
||||
|
||||
for _, attr := range res.Attributes() {
|
||||
switch key, val := attr.Key(), attr.Value(); key {
|
||||
|
@ -131,19 +136,15 @@ func (n *layer) createContainer(ctx context.Context, p *CreateBucketParams) (*ci
|
|||
return nil, err
|
||||
}
|
||||
|
||||
if err := n.setContainerEACL(ctx, cid, p.BoxData.Gate.GateKey); err != nil {
|
||||
if err := n.setContainerEACLTable(ctx, cid, p.EACL); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return cid, nil
|
||||
}
|
||||
|
||||
func (n *layer) setContainerEACL(ctx context.Context, cid *cid.ID, gateKey *keys.PublicKey) error {
|
||||
if gateKey == nil {
|
||||
return fmt.Errorf("gate key must not be nil")
|
||||
}
|
||||
|
||||
table := formDefaultTable(cid, *(*ecdsa.PublicKey)(gateKey))
|
||||
func (n *layer) setContainerEACLTable(ctx context.Context, cid *cid.ID, table *eacl.Table) error {
|
||||
table.SetCID(cid)
|
||||
if err := n.pool.SetEACL(ctx, table, n.SessionOpt(ctx)); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -155,25 +156,12 @@ func (n *layer) setContainerEACL(ctx context.Context, cid *cid.ID, gateKey *keys
|
|||
return nil
|
||||
}
|
||||
|
||||
func formDefaultTable(cid *cid.ID, gateKey ecdsa.PublicKey) *eacl.Table {
|
||||
table := eacl.NewTable()
|
||||
table.SetCID(cid)
|
||||
|
||||
for op := eacl.OperationGet; op <= eacl.OperationRangeHash; op++ {
|
||||
record := eacl.NewRecord()
|
||||
record.SetOperation(op)
|
||||
record.SetAction(eacl.ActionAllow)
|
||||
eacl.AddFormedTarget(record, eacl.RoleUser, gateKey)
|
||||
table.AddRecord(record)
|
||||
|
||||
record2 := eacl.NewRecord()
|
||||
record2.SetOperation(op)
|
||||
record2.SetAction(eacl.ActionDeny)
|
||||
eacl.AddFormedTarget(record2, eacl.RoleOthers)
|
||||
table.AddRecord(record2)
|
||||
func (n *layer) GetContainerEACL(ctx context.Context, cid *cid.ID) (*eacl.Table, error) {
|
||||
signedEacl, err := n.pool.GetEACL(ctx, cid)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return table
|
||||
return signedEacl.EACL(), nil
|
||||
}
|
||||
|
||||
type waitParams struct {
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/acl/eacl"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/client"
|
||||
cid "github.com/nspcc-dev/neofs-api-go/pkg/container/id"
|
||||
"github.com/nspcc-dev/neofs-api-go/pkg/netmap"
|
||||
|
@ -84,8 +85,14 @@ type (
|
|||
Name string
|
||||
ACL uint32
|
||||
Policy *netmap.PlacementPolicy
|
||||
EACL *eacl.Table
|
||||
BoxData *accessbox.Box
|
||||
}
|
||||
// PutBucketACLParams stores put bucket acl request parameters.
|
||||
PutBucketACLParams struct {
|
||||
Name string
|
||||
EACL *eacl.Table
|
||||
}
|
||||
// DeleteBucketParams stores delete bucket request parameters.
|
||||
DeleteBucketParams struct {
|
||||
Name string
|
||||
|
@ -112,6 +119,8 @@ type (
|
|||
|
||||
ListBuckets(ctx context.Context) ([]*BucketInfo, error)
|
||||
GetBucketInfo(ctx context.Context, name string) (*BucketInfo, error)
|
||||
GetBucketACL(ctx context.Context, name string) (*BucketACL, error)
|
||||
PutBucketACL(ctx context.Context, p *PutBucketACLParams) error
|
||||
CreateBucket(ctx context.Context, p *CreateBucketParams) (*cid.ID, error)
|
||||
DeleteBucket(ctx context.Context, p *DeleteBucketParams) error
|
||||
|
||||
|
@ -204,6 +213,34 @@ func (n *layer) GetBucketInfo(ctx context.Context, name string) (*BucketInfo, er
|
|||
return n.containerInfo(ctx, containerID)
|
||||
}
|
||||
|
||||
// GetBucketACL returns bucket acl info by name.
|
||||
func (n *layer) GetBucketACL(ctx context.Context, name string) (*BucketACL, error) {
|
||||
inf, err := n.GetBucketInfo(ctx, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
eacl, err := n.GetContainerEACL(ctx, inf.CID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &BucketACL{
|
||||
Info: inf,
|
||||
EACL: eacl,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// PutBucketACL put bucket acl by name.
|
||||
func (n *layer) PutBucketACL(ctx context.Context, param *PutBucketACLParams) error {
|
||||
inf, err := n.GetBucketInfo(ctx, param.Name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return n.setContainerEACLTable(ctx, inf.CID, param.EACL)
|
||||
}
|
||||
|
||||
// ListBuckets returns all user containers. Name of the bucket is a container
|
||||
// id. Timestamp is omitted since it is not saved in neofs container.
|
||||
func (n *layer) ListBuckets(ctx context.Context) ([]*BucketInfo, error) {
|
||||
|
|
Loading…
Reference in a new issue