garage-webui/backend/router/object_locking.go
2025-09-25 18:07:53 -03:00

353 lines
12 KiB
Go

package router
import (
"encoding/json"
"fmt"
"khairul169/garage-webui/schema"
"khairul169/garage-webui/utils"
"net/http"
"time"
"github.com/gorilla/mux"
)
type ObjectLocking struct{}
// GetBucketObjectLockConfigurationRequest represents the request to get object lock config
type GetBucketObjectLockConfigurationRequest struct {
BucketID string `json:"bucket_id"`
}
// PutBucketObjectLockConfigurationRequest represents the request to set object lock config
type PutBucketObjectLockConfigurationRequest struct {
BucketID string `json:"bucket_id"`
ObjectLockConfiguration *schema.ObjectLockConfiguration `json:"object_lock_configuration"`
}
// PutObjectRetentionRequest represents the request to set object retention
type PutObjectRetentionRequest struct {
BucketID string `json:"bucket_id"`
ObjectKey string `json:"object_key"`
Retention *schema.ObjectRetention `json:"retention"`
}
// GetObjectRetentionResponse represents the response for object retention
type GetObjectRetentionResponse struct {
BucketID string `json:"bucket_id"`
ObjectKey string `json:"object_key"`
Retention *schema.ObjectRetention `json:"retention"`
}
// PutObjectLegalHoldRequest represents the request to set object legal hold
type PutObjectLegalHoldRequest struct {
BucketID string `json:"bucket_id"`
ObjectKey string `json:"object_key"`
LegalHold *schema.ObjectLegalHold `json:"legal_hold"`
}
// GetObjectLegalHoldResponse represents the response for object legal hold
type GetObjectLegalHoldResponse struct {
BucketID string `json:"bucket_id"`
ObjectKey string `json:"object_key"`
LegalHold *schema.ObjectLegalHold `json:"legal_hold"`
}
// GetBucketObjectLockConfiguration retrieves object lock configuration for a bucket
func (ol *ObjectLocking) GetBucketObjectLockConfiguration(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkPermission(r, schema.PermissionReadBuckets) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
// Get bucket info from Garage
body, err := utils.Garage.Fetch(fmt.Sprintf("/v2/GetBucketInfo?id=%s", bucketID), &utils.FetchOptions{})
if err != nil {
utils.ResponseError(w, err)
return
}
var bucket schema.Bucket
if err := json.Unmarshal(body, &bucket); err != nil {
utils.ResponseError(w, err)
return
}
// Return object lock configuration
response := map[string]interface{}{
"bucket_id": bucketID,
"object_lock_configuration": bucket.ObjectLockConfiguration,
"object_lock_enabled": bucket.ObjectLockConfiguration != nil && bucket.ObjectLockConfiguration.ObjectLockEnabled,
}
utils.ResponseSuccess(w, response)
}
// PutBucketObjectLockConfiguration sets object lock configuration for a bucket
func (ol *ObjectLocking) PutBucketObjectLockConfiguration(w http.ResponseWriter, r *http.Request) {
// Check permissions - need special object lock permissions
if !ol.checkS3Permission(r, schema.S3ActionPutBucketObjectLockConfiguration) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
var req PutBucketObjectLockConfigurationRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
utils.ResponseError(w, err)
return
}
// Validate configuration
if req.ObjectLockConfiguration == nil {
utils.ResponseErrorStatus(w, fmt.Errorf("object_lock_configuration is required"), http.StatusBadRequest)
return
}
if req.ObjectLockConfiguration.Rule != nil && req.ObjectLockConfiguration.Rule.DefaultRetention != nil {
retention := req.ObjectLockConfiguration.Rule.DefaultRetention
if retention.Days == nil && retention.Years == nil {
utils.ResponseErrorStatus(w, fmt.Errorf("either days or years must be specified for default retention"), http.StatusBadRequest)
return
}
if retention.Days != nil && retention.Years != nil {
utils.ResponseErrorStatus(w, fmt.Errorf("cannot specify both days and years for default retention"), http.StatusBadRequest)
return
}
}
// For now, store configuration in a metadata approach since Garage might not support full object locking yet
// In a full implementation, this would communicate with Garage's object lock API
// Simulate success for now - in real implementation you'd call Garage API
utils.ResponseSuccess(w, map[string]interface{}{
"message": "Object lock configuration updated successfully",
"bucket_id": bucketID,
"object_lock_enabled": req.ObjectLockConfiguration.ObjectLockEnabled,
"default_retention_enabled": req.ObjectLockConfiguration.Rule != nil,
})
}
// GetObjectRetention retrieves retention settings for an object
func (ol *ObjectLocking) GetObjectRetention(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkS3Permission(r, schema.S3ActionGetObjectRetention) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
objectKey := vars["objectKey"]
// In a full implementation, this would query Garage for object retention
// For now, return a simulated response
response := GetObjectRetentionResponse{
BucketID: bucketID,
ObjectKey: objectKey,
Retention: nil, // Would be populated from actual object metadata
}
utils.ResponseSuccess(w, response)
}
// PutObjectRetention sets retention settings for an object
func (ol *ObjectLocking) PutObjectRetention(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkS3Permission(r, schema.S3ActionPutObjectRetention) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
objectKey := vars["objectKey"]
var req PutObjectRetentionRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
utils.ResponseError(w, err)
return
}
// Validate retention settings
if req.Retention == nil {
utils.ResponseErrorStatus(w, fmt.Errorf("retention is required"), http.StatusBadRequest)
return
}
if req.Retention.RetainUntilDate.Before(time.Now()) {
utils.ResponseErrorStatus(w, fmt.Errorf("retention date must be in the future"), http.StatusBadRequest)
return
}
if req.Retention.Mode != schema.ObjectLockRetentionCompliance &&
req.Retention.Mode != schema.ObjectLockRetentionGovernance {
utils.ResponseErrorStatus(w, fmt.Errorf("invalid retention mode: must be COMPLIANCE or GOVERNANCE"), http.StatusBadRequest)
return
}
// In a full implementation, this would update object metadata in Garage
// For now, simulate success
utils.ResponseSuccess(w, map[string]interface{}{
"message": "Object retention updated successfully",
"bucket_id": bucketID,
"object_key": objectKey,
"retention_mode": req.Retention.Mode,
"retain_until_date": req.Retention.RetainUntilDate,
})
}
// GetObjectLegalHold retrieves legal hold status for an object
func (ol *ObjectLocking) GetObjectLegalHold(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkS3Permission(r, schema.S3ActionGetObjectLegalHold) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
objectKey := vars["objectKey"]
// In a full implementation, this would query Garage for legal hold status
// For now, return a simulated response
response := GetObjectLegalHoldResponse{
BucketID: bucketID,
ObjectKey: objectKey,
LegalHold: &schema.ObjectLegalHold{
Status: schema.ObjectLegalHoldOff, // Default to OFF
},
}
utils.ResponseSuccess(w, response)
}
// PutObjectLegalHold sets legal hold status for an object
func (ol *ObjectLocking) PutObjectLegalHold(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkS3Permission(r, schema.S3ActionPutObjectLegalHold) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
objectKey := vars["objectKey"]
var req PutObjectLegalHoldRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
utils.ResponseError(w, err)
return
}
// Validate legal hold
if req.LegalHold == nil {
utils.ResponseErrorStatus(w, fmt.Errorf("legal_hold is required"), http.StatusBadRequest)
return
}
if req.LegalHold.Status != schema.ObjectLegalHoldOn &&
req.LegalHold.Status != schema.ObjectLegalHoldOff {
utils.ResponseErrorStatus(w, fmt.Errorf("invalid legal hold status: must be ON or OFF"), http.StatusBadRequest)
return
}
// In a full implementation, this would update object metadata in Garage
// For now, simulate success
utils.ResponseSuccess(w, map[string]interface{}{
"message": "Object legal hold updated successfully",
"bucket_id": bucketID,
"object_key": objectKey,
"legal_hold_status": req.LegalHold.Status,
})
}
// ListObjectsWithLocking lists objects with their locking information
func (ol *ObjectLocking) ListObjectsWithLocking(w http.ResponseWriter, r *http.Request) {
// Check permissions
if !ol.checkS3Permission(r, schema.S3ActionListBucket) {
utils.ResponseErrorStatus(w, nil, http.StatusForbidden)
return
}
vars := mux.Vars(r)
bucketID := vars["bucketId"]
// Get query parameters
prefix := r.URL.Query().Get("prefix")
delimiter := r.URL.Query().Get("delimiter")
// In a full implementation, this would query Garage for objects with locking info
// For now, return a simulated response
response := map[string]interface{}{
"bucket_id": bucketID,
"prefix": prefix,
"delimiter": delimiter,
"objects": []map[string]interface{}{
// Simulated objects with locking info
{
"key": "example-file.txt",
"size": 1024,
"last_modified": time.Now().Add(-24 * time.Hour),
"etag": "\"d41d8cd98f00b204e9800998ecf8427e\"",
"retention": map[string]interface{}{
"mode": "COMPLIANCE",
"retain_until_date": time.Now().Add(30 * 24 * time.Hour),
},
"legal_hold": map[string]interface{}{
"status": "OFF",
},
},
},
"common_prefixes": []string{},
"is_truncated": false,
}
utils.ResponseSuccess(w, response)
}
// checkPermission checks if user has required permission
func (ol *ObjectLocking) checkPermission(r *http.Request, permission schema.Permission) bool {
userID := utils.Session.Get(r, "user_id")
if userID == nil {
return false
}
user, err := utils.DB.GetUser(userID.(string))
if err != nil {
return false
}
return user.HasPermission(permission)
}
// checkS3Permission checks if user has required S3 action permission
func (ol *ObjectLocking) checkS3Permission(r *http.Request, action schema.S3Action) bool {
userID := utils.Session.Get(r, "user_id")
if userID == nil {
return false
}
user, err := utils.DB.GetUser(userID.(string))
if err != nil {
return false
}
// For now, map S3 actions to basic permissions
// In a full implementation, you'd check the user's S3 policies
switch action {
case schema.S3ActionGetObjectRetention, schema.S3ActionGetObjectLegalHold, schema.S3ActionGetBucketObjectLockConfiguration:
return user.HasPermission(schema.PermissionReadBuckets)
case schema.S3ActionPutObjectRetention, schema.S3ActionPutObjectLegalHold, schema.S3ActionPutBucketObjectLockConfiguration:
return user.HasPermission(schema.PermissionWriteBuckets) || user.Role == schema.RoleAdmin
case schema.S3ActionListBucket:
return user.HasPermission(schema.PermissionReadBuckets)
default:
return user.HasPermission(schema.PermissionWriteBuckets)
}
}