You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							363 lines
						
					
					
						
							11 KiB
						
					
					
				
			
		
		
		
			
			
			
		
		
	
	
							363 lines
						
					
					
						
							11 KiB
						
					
					
				
								package s3api
							 | 
						|
								
							 | 
						|
								import (
							 | 
						|
									"encoding/xml"
							 | 
						|
									"fmt"
							 | 
						|
									"strconv"
							 | 
						|
									"time"
							 | 
						|
								
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/glog"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
							 | 
						|
									"github.com/seaweedfs/seaweedfs/weed/s3api/s3_constants"
							 | 
						|
								)
							 | 
						|
								
							 | 
						|
								// ObjectLockUtils provides shared utilities for Object Lock configuration
							 | 
						|
								// These functions are used by both Admin UI and S3 API handlers to ensure consistency
							 | 
						|
								
							 | 
						|
								// VersioningUtils provides shared utilities for bucket versioning configuration
							 | 
						|
								// These functions ensure Admin UI and S3 API use the same versioning keys
							 | 
						|
								
							 | 
						|
								// StoreVersioningInExtended stores versioning configuration in entry extended attributes
							 | 
						|
								func StoreVersioningInExtended(entry *filer_pb.Entry, enabled bool) error {
							 | 
						|
									if entry.Extended == nil {
							 | 
						|
										entry.Extended = make(map[string][]byte)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if enabled {
							 | 
						|
										entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningEnabled)
							 | 
						|
									} else {
							 | 
						|
										entry.Extended[s3_constants.ExtVersioningKey] = []byte(s3_constants.VersioningSuspended)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// LoadVersioningFromExtended loads versioning configuration from entry extended attributes
							 | 
						|
								func LoadVersioningFromExtended(entry *filer_pb.Entry) (bool, bool) {
							 | 
						|
									if entry == nil || entry.Extended == nil {
							 | 
						|
										return false, false // not found, default to suspended
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for S3 API compatible key
							 | 
						|
									if versioningBytes, exists := entry.Extended[s3_constants.ExtVersioningKey]; exists {
							 | 
						|
										enabled := string(versioningBytes) == s3_constants.VersioningEnabled
							 | 
						|
										return enabled, true
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return false, false // not found
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CreateObjectLockConfiguration creates a new ObjectLockConfiguration with the specified parameters
							 | 
						|
								func CreateObjectLockConfiguration(enabled bool, mode string, days int, years int) *ObjectLockConfiguration {
							 | 
						|
									if !enabled {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									config := &ObjectLockConfiguration{
							 | 
						|
										ObjectLockEnabled: s3_constants.ObjectLockEnabled,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Add default retention rule if mode and period are specified
							 | 
						|
									if mode != "" && (days > 0 || years > 0) {
							 | 
						|
										config.Rule = &ObjectLockRule{
							 | 
						|
											DefaultRetention: &DefaultRetention{
							 | 
						|
												Mode:     mode,
							 | 
						|
												Days:     days,
							 | 
						|
												Years:    years,
							 | 
						|
												DaysSet:  days > 0,
							 | 
						|
												YearsSet: years > 0,
							 | 
						|
											},
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return config
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ObjectLockConfigurationToXML converts ObjectLockConfiguration to XML bytes
							 | 
						|
								func ObjectLockConfigurationToXML(config *ObjectLockConfiguration) ([]byte, error) {
							 | 
						|
									if config == nil {
							 | 
						|
										return nil, fmt.Errorf("object lock configuration is nil")
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return xml.Marshal(config)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// StoreObjectLockConfigurationInExtended stores Object Lock configuration in entry extended attributes
							 | 
						|
								func StoreObjectLockConfigurationInExtended(entry *filer_pb.Entry, config *ObjectLockConfiguration) error {
							 | 
						|
									if entry.Extended == nil {
							 | 
						|
										entry.Extended = make(map[string][]byte)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config == nil {
							 | 
						|
										// Remove Object Lock configuration
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockEnabledKey)
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey)
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey)
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey)
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Store the enabled flag
							 | 
						|
									entry.Extended[s3_constants.ExtObjectLockEnabledKey] = []byte(config.ObjectLockEnabled)
							 | 
						|
								
							 | 
						|
									// Store default retention configuration if present
							 | 
						|
									if config.Rule != nil && config.Rule.DefaultRetention != nil {
							 | 
						|
										defaultRetention := config.Rule.DefaultRetention
							 | 
						|
								
							 | 
						|
										// Store mode
							 | 
						|
										if defaultRetention.Mode != "" {
							 | 
						|
											entry.Extended[s3_constants.ExtObjectLockDefaultModeKey] = []byte(defaultRetention.Mode)
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Store days
							 | 
						|
										if defaultRetention.DaysSet && defaultRetention.Days > 0 {
							 | 
						|
											entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey] = []byte(strconv.Itoa(defaultRetention.Days))
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Store years
							 | 
						|
										if defaultRetention.YearsSet && defaultRetention.Years > 0 {
							 | 
						|
											entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey] = []byte(strconv.Itoa(defaultRetention.Years))
							 | 
						|
										}
							 | 
						|
									} else {
							 | 
						|
										// Remove default retention if not present
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultModeKey)
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultDaysKey)
							 | 
						|
										delete(entry.Extended, s3_constants.ExtObjectLockDefaultYearsKey)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// LoadObjectLockConfigurationFromExtended loads Object Lock configuration from entry extended attributes
							 | 
						|
								func LoadObjectLockConfigurationFromExtended(entry *filer_pb.Entry) (*ObjectLockConfiguration, bool) {
							 | 
						|
									if entry == nil || entry.Extended == nil {
							 | 
						|
										return nil, false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if Object Lock is enabled
							 | 
						|
									enabledBytes, exists := entry.Extended[s3_constants.ExtObjectLockEnabledKey]
							 | 
						|
									if !exists {
							 | 
						|
										return nil, false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									enabled := string(enabledBytes)
							 | 
						|
									if enabled != s3_constants.ObjectLockEnabled && enabled != "true" {
							 | 
						|
										return nil, false
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Create basic configuration
							 | 
						|
									config := &ObjectLockConfiguration{
							 | 
						|
										ObjectLockEnabled: s3_constants.ObjectLockEnabled,
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Load default retention configuration if present
							 | 
						|
									if modeBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultModeKey]; exists {
							 | 
						|
										mode := string(modeBytes)
							 | 
						|
								
							 | 
						|
										// Parse days and years
							 | 
						|
										var days, years int
							 | 
						|
										if daysBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultDaysKey]; exists {
							 | 
						|
											if parsed, err := strconv.Atoi(string(daysBytes)); err == nil {
							 | 
						|
												days = parsed
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
										if yearsBytes, exists := entry.Extended[s3_constants.ExtObjectLockDefaultYearsKey]; exists {
							 | 
						|
											if parsed, err := strconv.Atoi(string(yearsBytes)); err == nil {
							 | 
						|
												years = parsed
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
								
							 | 
						|
										// Create rule if we have a mode and at least days or years
							 | 
						|
										if mode != "" && (days > 0 || years > 0) {
							 | 
						|
											config.Rule = &ObjectLockRule{
							 | 
						|
												DefaultRetention: &DefaultRetention{
							 | 
						|
													Mode:     mode,
							 | 
						|
													Days:     days,
							 | 
						|
													Years:    years,
							 | 
						|
													DaysSet:  days > 0,
							 | 
						|
													YearsSet: years > 0,
							 | 
						|
												},
							 | 
						|
											}
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return config, true
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ExtractObjectLockInfoFromConfig extracts basic Object Lock information from configuration
							 | 
						|
								// Returns: enabled, mode, duration (for UI display)
							 | 
						|
								func ExtractObjectLockInfoFromConfig(config *ObjectLockConfiguration) (bool, string, int32) {
							 | 
						|
									if config == nil || config.ObjectLockEnabled != s3_constants.ObjectLockEnabled {
							 | 
						|
										return false, "", 0
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if config.Rule == nil || config.Rule.DefaultRetention == nil {
							 | 
						|
										return true, "", 0
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									defaultRetention := config.Rule.DefaultRetention
							 | 
						|
								
							 | 
						|
									// Convert years to days for consistent representation
							 | 
						|
									days := 0
							 | 
						|
									if defaultRetention.DaysSet {
							 | 
						|
										days = defaultRetention.Days
							 | 
						|
									}
							 | 
						|
									if defaultRetention.YearsSet && defaultRetention.Years > 0 {
							 | 
						|
										days += defaultRetention.Years * 365
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return true, defaultRetention.Mode, int32(days)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// CreateObjectLockConfigurationFromParams creates ObjectLockConfiguration from individual parameters
							 | 
						|
								// This is a convenience function for Admin UI usage
							 | 
						|
								func CreateObjectLockConfigurationFromParams(enabled bool, mode string, duration int32) *ObjectLockConfiguration {
							 | 
						|
									if !enabled {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return CreateObjectLockConfiguration(enabled, mode, int(duration), 0)
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateObjectLockParameters validates Object Lock parameters before creating configuration
							 | 
						|
								func ValidateObjectLockParameters(enabled bool, mode string, duration int32) error {
							 | 
						|
									if !enabled {
							 | 
						|
										return nil
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if mode != s3_constants.RetentionModeGovernance && mode != s3_constants.RetentionModeCompliance {
							 | 
						|
										return ErrInvalidObjectLockMode
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if duration <= 0 {
							 | 
						|
										return ErrInvalidObjectLockDuration
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									if duration > MaxRetentionDays {
							 | 
						|
										return ErrObjectLockDurationExceeded
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ====================================================================
							 | 
						|
								// OBJECT LOCK VALIDATION FUNCTIONS
							 | 
						|
								// ====================================================================
							 | 
						|
								// These validation functions provide comprehensive validation for
							 | 
						|
								// all Object Lock related configurations and requests.
							 | 
						|
								
							 | 
						|
								// ValidateRetention validates retention configuration for object-level retention
							 | 
						|
								func ValidateRetention(retention *ObjectRetention) error {
							 | 
						|
									// Check if mode is specified
							 | 
						|
									if retention.Mode == "" {
							 | 
						|
										return ErrRetentionMissingMode
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if retain until date is specified
							 | 
						|
									if retention.RetainUntilDate == nil {
							 | 
						|
										return ErrRetentionMissingRetainUntilDate
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if mode is valid
							 | 
						|
									if retention.Mode != s3_constants.RetentionModeGovernance && retention.Mode != s3_constants.RetentionModeCompliance {
							 | 
						|
										return ErrInvalidRetentionModeValue
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check if retain until date is in the future
							 | 
						|
									if retention.RetainUntilDate.Before(time.Now()) {
							 | 
						|
										return ErrRetentionDateMustBeFuture
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateLegalHold validates legal hold configuration
							 | 
						|
								func ValidateLegalHold(legalHold *ObjectLegalHold) error {
							 | 
						|
									// Check if status is valid
							 | 
						|
									if legalHold.Status != s3_constants.LegalHoldOn && legalHold.Status != s3_constants.LegalHoldOff {
							 | 
						|
										return ErrInvalidLegalHoldStatus
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// ValidateObjectLockConfiguration validates object lock configuration at bucket level
							 | 
						|
								func ValidateObjectLockConfiguration(config *ObjectLockConfiguration) error {
							 | 
						|
									// ObjectLockEnabled is required for bucket-level configuration
							 | 
						|
									if config.ObjectLockEnabled == "" {
							 | 
						|
										return ErrObjectLockConfigurationMissingEnabled
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate ObjectLockEnabled value
							 | 
						|
									if config.ObjectLockEnabled != s3_constants.ObjectLockEnabled {
							 | 
						|
										// ObjectLockEnabled can only be 'Enabled', any other value (including 'Disabled') is malformed XML
							 | 
						|
										return ErrInvalidObjectLockEnabledValue
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate Rule if present
							 | 
						|
									if config.Rule != nil {
							 | 
						|
										if config.Rule.DefaultRetention == nil {
							 | 
						|
											return ErrRuleMissingDefaultRetention
							 | 
						|
										}
							 | 
						|
										return validateDefaultRetention(config.Rule.DefaultRetention)
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 | 
						|
								
							 | 
						|
								// validateDefaultRetention validates default retention configuration for bucket-level settings
							 | 
						|
								func validateDefaultRetention(retention *DefaultRetention) error {
							 | 
						|
									glog.V(2).Infof("validateDefaultRetention: Mode=%s, Days=%d (set=%v), Years=%d (set=%v)",
							 | 
						|
										retention.Mode, retention.Days, retention.DaysSet, retention.Years, retention.YearsSet)
							 | 
						|
								
							 | 
						|
									// Mode is required
							 | 
						|
									if retention.Mode == "" {
							 | 
						|
										return ErrDefaultRetentionMissingMode
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Mode must be valid
							 | 
						|
									if retention.Mode != s3_constants.RetentionModeGovernance && retention.Mode != s3_constants.RetentionModeCompliance {
							 | 
						|
										return ErrInvalidDefaultRetentionMode
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for invalid Years value (negative values are always invalid)
							 | 
						|
									if retention.YearsSet && retention.Years < 0 {
							 | 
						|
										return ErrInvalidRetentionPeriod
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for invalid Days value (negative values are invalid)
							 | 
						|
									if retention.DaysSet && retention.Days < 0 {
							 | 
						|
										return ErrInvalidRetentionPeriod
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for invalid Days value (zero is invalid when explicitly provided)
							 | 
						|
									if retention.DaysSet && retention.Days == 0 {
							 | 
						|
										return ErrInvalidRetentionPeriod
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for neither Days nor Years being specified
							 | 
						|
									if !retention.DaysSet && !retention.YearsSet {
							 | 
						|
										return ErrDefaultRetentionMissingPeriod
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Check for both Days and Years being specified
							 | 
						|
									if retention.DaysSet && retention.YearsSet {
							 | 
						|
										return ErrDefaultRetentionBothDaysAndYears
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate Days if specified
							 | 
						|
									if retention.DaysSet && retention.Days > 0 {
							 | 
						|
										if retention.Days > MaxRetentionDays {
							 | 
						|
											return ErrDefaultRetentionDaysOutOfRange
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									// Validate Years if specified
							 | 
						|
									if retention.YearsSet && retention.Years > 0 {
							 | 
						|
										if retention.Years > MaxRetentionYears {
							 | 
						|
											return ErrDefaultRetentionYearsOutOfRange
							 | 
						|
										}
							 | 
						|
									}
							 | 
						|
								
							 | 
						|
									return nil
							 | 
						|
								}
							 |