138 lines
3.6 KiB
Go
138 lines
3.6 KiB
Go
package layer
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
errorsStd "errors"
|
|
"fmt"
|
|
"io"
|
|
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/data"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/errors"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/layer/tree"
|
|
"git.frostfs.info/TrueCloudLab/frostfs-s3-gw/internal/logs"
|
|
cid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/container/id"
|
|
oid "git.frostfs.info/TrueCloudLab/frostfs-sdk-go/object/id"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const wildcard = "*"
|
|
|
|
var supportedMethods = map[string]struct{}{"GET": {}, "HEAD": {}, "POST": {}, "PUT": {}, "DELETE": {}}
|
|
|
|
func (n *Layer) PutBucketCORS(ctx context.Context, p *PutCORSParams) error {
|
|
var (
|
|
buf bytes.Buffer
|
|
tee = io.TeeReader(p.Reader, &buf)
|
|
cors = &data.CORSConfiguration{}
|
|
)
|
|
|
|
if err := p.NewDecoder(tee).Decode(cors); err != nil {
|
|
return fmt.Errorf("xml decode cors: %w", err)
|
|
}
|
|
|
|
if cors.CORSRules == nil {
|
|
return errors.GetAPIError(errors.ErrMalformedXML)
|
|
}
|
|
|
|
if err := checkCORS(cors); err != nil {
|
|
return err
|
|
}
|
|
|
|
prm := PrmObjectCreate{
|
|
Payload: &buf,
|
|
Filepath: p.BktInfo.CORSObjectName(),
|
|
CreationTime: TimeNow(ctx),
|
|
}
|
|
|
|
var corsBkt *data.BucketInfo
|
|
if n.corsCnrInfo == nil {
|
|
corsBkt = p.BktInfo
|
|
prm.CopiesNumber = p.CopiesNumbers
|
|
} else {
|
|
corsBkt = n.corsCnrInfo
|
|
prm.PrmAuth.PrivateKey = &n.gateKey.PrivateKey
|
|
}
|
|
|
|
prm.Container = corsBkt.CID
|
|
|
|
createdObj, err := n.objectPutAndHash(ctx, prm, corsBkt)
|
|
if err != nil {
|
|
return fmt.Errorf("put cors object: %w", err)
|
|
}
|
|
|
|
objsToDelete, err := n.treeService.PutBucketCORS(ctx, p.BktInfo, newAddress(corsBkt.CID, createdObj.ID))
|
|
objToDeleteNotFound := errorsStd.Is(err, tree.ErrNoNodeToRemove)
|
|
if err != nil && !objToDeleteNotFound {
|
|
return err
|
|
}
|
|
|
|
if !objToDeleteNotFound {
|
|
for _, addr := range objsToDelete {
|
|
n.deleteCORSObject(ctx, p.BktInfo, addr)
|
|
}
|
|
}
|
|
|
|
n.cache.PutCORS(n.BearerOwner(ctx), p.BktInfo, cors)
|
|
|
|
return nil
|
|
}
|
|
|
|
// deleteCORSObject removes object and logs in case of error.
|
|
func (n *Layer) deleteCORSObject(ctx context.Context, bktInfo *data.BucketInfo, addr oid.Address) {
|
|
var prmAuth PrmAuth
|
|
corsBkt := bktInfo
|
|
if !addr.Container().Equals(bktInfo.CID) && !addr.Container().Equals(cid.ID{}) {
|
|
corsBkt = &data.BucketInfo{CID: addr.Container()}
|
|
prmAuth.PrivateKey = &n.gateKey.PrivateKey
|
|
}
|
|
|
|
if err := n.objectDeleteWithAuth(ctx, corsBkt, addr.Object(), prmAuth); err != nil {
|
|
n.reqLogger(ctx).Error(logs.CouldntDeleteCorsObject, zap.Error(err),
|
|
zap.String("cnrID", corsBkt.CID.EncodeToString()),
|
|
zap.String("objID", addr.Object().EncodeToString()))
|
|
}
|
|
}
|
|
|
|
func (n *Layer) GetBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) (*data.CORSConfiguration, error) {
|
|
cors, err := n.getCORS(ctx, bktInfo)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return cors, nil
|
|
}
|
|
|
|
func (n *Layer) DeleteBucketCORS(ctx context.Context, bktInfo *data.BucketInfo) error {
|
|
objs, err := n.treeService.DeleteBucketCORS(ctx, bktInfo)
|
|
objNotFound := errorsStd.Is(err, tree.ErrNoNodeToRemove)
|
|
if err != nil && !objNotFound {
|
|
return err
|
|
}
|
|
|
|
if !objNotFound {
|
|
for _, addr := range objs {
|
|
n.deleteCORSObject(ctx, bktInfo, addr)
|
|
}
|
|
}
|
|
|
|
n.cache.DeleteCORS(bktInfo)
|
|
|
|
return nil
|
|
}
|
|
|
|
func checkCORS(cors *data.CORSConfiguration) error {
|
|
for _, r := range cors.CORSRules {
|
|
for _, m := range r.AllowedMethods {
|
|
if _, ok := supportedMethods[m]; !ok {
|
|
return errors.GetAPIErrorWithError(errors.ErrCORSUnsupportedMethod, fmt.Errorf("unsupported method is %s", m))
|
|
}
|
|
}
|
|
for _, h := range r.ExposeHeaders {
|
|
if h == wildcard {
|
|
return errors.GetAPIError(errors.ErrCORSWildcardExposeHeaders)
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|