From fe9eb9cedc4ad011a33b036633fa40a41e879248 Mon Sep 17 00:00:00 2001 From: Denis Kirillov Date: Mon, 28 Feb 2022 11:29:03 +0300 Subject: [PATCH] [#195] Add PUT and GET default lock configuration Signed-off-by: Denis Kirillov --- api/data/info.go | 3 +- api/data/locking.go | 18 +++++ api/handler/locking.go | 123 +++++++++++++++++++++++++++++++++++ api/handler/response.go | 4 ++ api/handler/unimplemented.go | 8 --- 5 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 api/data/locking.go create mode 100644 api/handler/locking.go diff --git a/api/data/info.go b/api/data/info.go index 89823eac6..f8ede5a71 100644 --- a/api/data/info.go +++ b/api/data/info.go @@ -47,7 +47,8 @@ type ( // BucketSettings stores settings such as versioning. BucketSettings struct { - VersioningEnabled bool `json:"versioning_enabled"` + VersioningEnabled bool `json:"versioning_enabled"` + LockConfiguration *ObjectLockConfiguration `json:"lock_configuration"` } // CORSConfiguration stores CORS configuration of a request. diff --git a/api/data/locking.go b/api/data/locking.go new file mode 100644 index 000000000..039eebe54 --- /dev/null +++ b/api/data/locking.go @@ -0,0 +1,18 @@ +package data + +type ( + ObjectLockConfiguration struct { + ObjectLockEnabled string `xml:"ObjectLockEnabled" json:"ObjectLockEnabled"` + Rule *ObjectLockRule `xml:"Rule" json:"Rule"` + } + + ObjectLockRule struct { + DefaultRetention *DefaultRetention `xml:"DefaultRetention" json:"DefaultRetention"` + } + + DefaultRetention struct { + Days int64 `xml:"Days" json:"Days"` + Mode string `xml:"Mode" json:"Mode"` + Years int64 `xml:"Years" json:"Years"` + } +) diff --git a/api/handler/locking.go b/api/handler/locking.go new file mode 100644 index 000000000..4fdde2edd --- /dev/null +++ b/api/handler/locking.go @@ -0,0 +1,123 @@ +package handler + +import ( + "encoding/xml" + "fmt" + "net/http" + + "github.com/nspcc-dev/neofs-s3-gw/api" + "github.com/nspcc-dev/neofs-s3-gw/api/data" + apiErrors "github.com/nspcc-dev/neofs-s3-gw/api/errors" + "github.com/nspcc-dev/neofs-s3-gw/api/layer" +) + +func (h *handler) PutBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { + reqInfo := api.GetReqInfo(r.Context()) + + bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName) + if err != nil { + h.logAndSendError(w, "could not get bucket info", reqInfo, err) + return + } + if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil { + h.logAndSendError(w, "expected owner doesn't match", reqInfo, err) + return + } + + if !bktInfo.ObjectLockEnabled { + h.logAndSendError(w, "couldn't put object locking configuration", reqInfo, + apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotAllowed)) + return + } + + lockingConf := &data.ObjectLockConfiguration{} + if err = xml.NewDecoder(r.Body).Decode(lockingConf); err != nil { + h.logAndSendError(w, "couldn't parse locking configuration", reqInfo, err) + return + } + + if err = checkLockConfiguration(lockingConf); err != nil { + h.logAndSendError(w, "invalid lock configuration", reqInfo, err) + return + } + + settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) + if err != nil { + h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) + return + } + + settings.LockConfiguration = lockingConf + + sp := &layer.PutSettingsParams{ + BktInfo: bktInfo, + Settings: settings, + } + + if err = h.obj.PutBucketSettings(r.Context(), sp); err != nil { + h.logAndSendError(w, "couldn't put bucket settings", reqInfo, err) + return + } +} + +func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { + reqInfo := api.GetReqInfo(r.Context()) + + bktInfo, err := h.obj.GetBucketInfo(r.Context(), reqInfo.BucketName) + if err != nil { + h.logAndSendError(w, "could not get bucket info", reqInfo, err) + return + } + if err = checkOwner(bktInfo, r.Header.Get(api.AmzExpectedBucketOwner)); err != nil { + h.logAndSendError(w, "expected owner doesn't match", reqInfo, err) + return + } + + settings, err := h.obj.GetBucketSettings(r.Context(), bktInfo) + if err != nil { + h.logAndSendError(w, "couldn't get bucket settings", reqInfo, err) + return + } + + if !bktInfo.ObjectLockEnabled { + h.logAndSendError(w, "object lock disabled", reqInfo, + apiErrors.GetAPIError(apiErrors.ErrObjectLockConfigurationNotFound)) + return + } + + if settings.LockConfiguration == nil { + settings.LockConfiguration = &data.ObjectLockConfiguration{} + } + if settings.LockConfiguration.ObjectLockEnabled == "" { + settings.LockConfiguration.ObjectLockEnabled = enabledValue + } + + if err = api.EncodeToResponse(w, settings.LockConfiguration); err != nil { + h.logAndSendError(w, "something went wrong", reqInfo, err) + } +} + +func checkLockConfiguration(conf *data.ObjectLockConfiguration) error { + if conf.ObjectLockEnabled != "" && conf.ObjectLockEnabled != enabledValue { + return fmt.Errorf("invalid ObjectLockEnabled value: %s", conf.ObjectLockEnabled) + } + + if conf.Rule == nil || conf.Rule.DefaultRetention == nil { + return nil + } + + retention := conf.Rule.DefaultRetention + if retention.Mode != "GOVERNANCE" && retention.Mode != "COMPLIANCE" { + return fmt.Errorf("invalid Mode value: %s", retention.Mode) + } + + if retention.Days == 0 && retention.Years == 0 { + return fmt.Errorf("you must specify Days or Years") + } + + if retention.Days != 0 && retention.Years != 0 { + return fmt.Errorf("you cannot specify Days and Years at the same time") + } + + return nil +} diff --git a/api/handler/response.go b/api/handler/response.go index d50a8ec23..1fce9c32b 100644 --- a/api/handler/response.go +++ b/api/handler/response.go @@ -188,6 +188,10 @@ type Tag struct { Value string } +const ( + enabledValue = "Enabled" +) + // MarshalXML - StringMap marshals into XML. func (s StringMap) MarshalXML(e *xml.Encoder, start xml.StartElement) error { tokens := []xml.Token{start} diff --git a/api/handler/unimplemented.go b/api/handler/unimplemented.go index 0615d62c3..b849e53a7 100644 --- a/api/handler/unimplemented.go +++ b/api/handler/unimplemented.go @@ -59,10 +59,6 @@ func (h *handler) DeleteBucketWebsiteHandler(w http.ResponseWriter, r *http.Requ h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented)) } -func (h *handler) GetBucketObjectLockConfigHandler(w http.ResponseWriter, r *http.Request) { - h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented)) -} - func (h *handler) ListenBucketNotificationHandler(w http.ResponseWriter, r *http.Request) { h.logAndSendError(w, "not implemented", api.GetReqInfo(r.Context()), errors.GetAPIError(errors.ErrNotImplemented)) } @@ -78,7 +74,3 @@ func (h *handler) PutBucketLifecycleHandler(w http.ResponseWriter, r *http.Reque func (h *handler) PutBucketEncryptionHandler(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)) -}