package handler import ( "fmt" "net/http" "git.frostfs.info/TrueCloudLab/frostfs-observability/tracing" "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" "git.frostfs.info/TrueCloudLab/frostfs-s3-gw/api/middleware" "github.com/pquerna/otp/totp" ) func (h *handler) PutBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.PutBucketVersioning") defer span.End() reqInfo := middleware.GetReqInfo(ctx) var serialNumber, token string configuration := new(VersioningConfiguration) if err := h.cfg.NewXMLDecoder(r.Body, r.UserAgent()).Decode(configuration); err != nil { h.logAndSendError(ctx, w, "couldn't decode versioning configuration", reqInfo, errors.GetAPIError(errors.ErrIllegalVersioningConfigurationException)) return } newMfa := len(configuration.MfaDelete) > 0 newStatus := len(configuration.Status) > 0 if !newStatus { h.logAndSendError(ctx, w, "failed to put versioning", reqInfo, errors.GetAPIError(errors.ErrVersioningNotSpecified)) } bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err) return } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { h.logAndSendError(ctx, w, "couldn't get bucket settings", reqInfo, err) return } lifecycleCfg, err := h.obj.GetBucketLifecycleConfiguration(ctx, bktInfo) if err != nil && !errors.IsS3Error(err, errors.ErrNoSuchLifecycleConfiguration) { h.logAndSendError(ctx, w, "couldn't get lifecycle config", reqInfo, err) return } if lifecycleCfg != nil && newMfa { h.logAndSendError(ctx, w, "couldn't put versioning", reqInfo, errors.GetAPIError(errors.ErrMFAAuthIsNotSupported)) return } if settings.MFADeleteEnabled() || newMfa { serialNumber, token, err = h.getMFAHeader(r) if err != nil { h.logAndSendError(ctx, w, "invalid x-amz-mfa header", reqInfo, errors.GetAPIError(errors.ErrInvalidMFAHeader)) return } device, err := h.mfa.GetMFADevice(ctx, reqInfo.Namespace, nameFromArn(serialNumber)) if err != nil { h.logAndSendError(ctx, w, "could not get mfa device", reqInfo, err) return } validate := totp.Validate(token, device.Key.Secret()) if !validate { h.logAndSendError(ctx, w, "could not validate token", reqInfo, fmt.Errorf("mfa Authentication must be used for this request")) return } } // settings pointer is stored in the cache, so modify a copy of the settings newSettings := *settings if newMfa { switch configuration.MfaDelete { case data.MFADeleteEnabled: newSettings.Versioning.MFADeleteStatus = data.MFADeleteEnabled newSettings.Versioning.MFASerialNumber = serialNumber case data.MFADeleteDisabled: newSettings.Versioning.MFADeleteStatus = data.MFADeleteDisabled newSettings.Versioning.MFASerialNumber = "" default: h.logAndSendError(ctx, w, "failed to get mfa configuration", reqInfo, nil) return } } if configuration.Status != data.VersioningEnabled && configuration.Status != data.VersioningSuspended { h.logAndSendError(ctx, w, "invalid versioning configuration", reqInfo, errors.GetAPIError(errors.ErrMalformedXML)) return } newSettings.Versioning.VersioningStatus = configuration.Status p := &layer.PutSettingsParams{ BktInfo: bktInfo, Settings: &newSettings, } if p.Settings.VersioningSuspended() && bktInfo.ObjectLockEnabled { h.logAndSendError(ctx, w, "couldn't suspend bucket versioning", reqInfo, errors.GetAPIError(errors.ErrObjectLockConfigurationVersioningCannotBeChanged)) return } if err = h.obj.PutBucketSettings(ctx, p); err != nil { h.logAndSendError(ctx, w, "couldn't put update versioning settings", reqInfo, err) } } // GetBucketVersioningHandler implements bucket versioning getter handler. func (h *handler) GetBucketVersioningHandler(w http.ResponseWriter, r *http.Request) { ctx, span := tracing.StartSpanFromContext(r.Context(), "handler.GetBucketVersioning") defer span.End() reqInfo := middleware.GetReqInfo(ctx) bktInfo, err := h.getBucketAndCheckOwner(r, reqInfo.BucketName) if err != nil { h.logAndSendError(ctx, w, "could not get bucket info", reqInfo, err) return } settings, err := h.obj.GetBucketSettings(ctx, bktInfo) if err != nil { h.logAndSendError(ctx, w, "couldn't get version settings", reqInfo, err) return } if err = middleware.EncodeToResponse(w, formVersioningConfiguration(settings)); err != nil { h.logAndSendError(ctx, w, "something went wrong", reqInfo, err) } } func formVersioningConfiguration(settings *data.BucketSettings) *VersioningConfiguration { res := &VersioningConfiguration{} if !settings.Unversioned() { res.Status = settings.Versioning.VersioningStatus } if settings.MFADeleteEnabled() { res.MfaDelete = data.MFADeleteEnabled } else if settings.MFADeleteDisabled() { res.MfaDelete = data.MFADeleteDisabled } return res }