Compare commits
	
		
			1 Commits
		
	
	
		
			secure-clu
			...
			kalzu/issu
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					32b347f1fd | 
							
								
								
									
										42
									
								
								auth/auth.go
									
									
									
									
									
								
							
							
						
						
									
										42
									
								
								auth/auth.go
									
									
									
									
									
								
							@@ -227,4 +227,44 @@ func (s *AuthService) HasUsers() (bool, error) {
 | 
			
		||||
	})
 | 
			
		||||
	
 | 
			
		||||
	return hasUsers, err
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// StoreResourceMetadata stores or updates resource metadata in BadgerDB
 | 
			
		||||
func (s *AuthService) StoreResourceMetadata(path string, metadata *types.ResourceMetadata) error {
 | 
			
		||||
	now := time.Now().Unix()
 | 
			
		||||
	if metadata.CreatedAt == 0 {
 | 
			
		||||
		metadata.CreatedAt = now
 | 
			
		||||
	}
 | 
			
		||||
	metadata.UpdatedAt = now
 | 
			
		||||
 | 
			
		||||
	metadataData, err := json.Marshal(metadata)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return s.db.Update(func(txn *badger.Txn) error {
 | 
			
		||||
		return txn.Set([]byte(ResourceMetadataKey(path)), metadataData)
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetResourceMetadata retrieves resource metadata from BadgerDB
 | 
			
		||||
func (s *AuthService) GetResourceMetadata(path string) (*types.ResourceMetadata, error) {
 | 
			
		||||
	var metadata types.ResourceMetadata
 | 
			
		||||
	
 | 
			
		||||
	err := s.db.View(func(txn *badger.Txn) error {
 | 
			
		||||
		item, err := txn.Get([]byte(ResourceMetadataKey(path)))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		return item.Value(func(val []byte) error {
 | 
			
		||||
			return json.Unmarshal(val, &metadata)
 | 
			
		||||
		})
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, err
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return &metadata, nil
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1271,3 +1271,142 @@ func (s *Server) getRevisionHistory(key string) ([]map[string]interface{}, error
 | 
			
		||||
func (s *Server) getSpecificRevision(key string, revision int) (*types.StoredValue, error) {
 | 
			
		||||
	return s.revisionService.GetSpecificRevision(key, revision)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getResourceMetadataHandler retrieves metadata for a resource path
 | 
			
		||||
func (s *Server) getResourceMetadataHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	vars := mux.Vars(r)
 | 
			
		||||
	path := vars["path"]
 | 
			
		||||
 | 
			
		||||
	authCtx := auth.GetAuthContext(r.Context())
 | 
			
		||||
	if authCtx == nil {
 | 
			
		||||
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check read permission on the resource
 | 
			
		||||
	if !s.authService.CheckResourcePermission(authCtx, path, "read") {
 | 
			
		||||
		http.Error(w, "Forbidden", http.StatusForbidden)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	metadata, err := s.authService.GetResourceMetadata(path)
 | 
			
		||||
	if err == badger.ErrKeyNotFound {
 | 
			
		||||
		// Return default metadata if not found
 | 
			
		||||
		defaultMetadata := types.ResourceMetadata{
 | 
			
		||||
			OwnerUUID:   authCtx.UserUUID,
 | 
			
		||||
			GroupUUID:   "",
 | 
			
		||||
			Permissions: types.DefaultPermissions,
 | 
			
		||||
			CreatedAt:   time.Now().Unix(),
 | 
			
		||||
			UpdatedAt:   time.Now().Unix(),
 | 
			
		||||
		}
 | 
			
		||||
		metadata = &defaultMetadata
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		s.logger.WithError(err).WithField("path", path).Error("Failed to get resource metadata")
 | 
			
		||||
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response := types.GetResourceMetadataResponse{
 | 
			
		||||
		OwnerUUID:   metadata.OwnerUUID,
 | 
			
		||||
		GroupUUID:   metadata.GroupUUID,
 | 
			
		||||
		Permissions: metadata.Permissions,
 | 
			
		||||
		TTL:         metadata.TTL,
 | 
			
		||||
		CreatedAt:   metadata.CreatedAt,
 | 
			
		||||
		UpdatedAt:   metadata.UpdatedAt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
	json.NewEncoder(w).Encode(response)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// updateResourceMetadataHandler updates metadata for a resource path
 | 
			
		||||
func (s *Server) updateResourceMetadataHandler(w http.ResponseWriter, r *http.Request) {
 | 
			
		||||
	vars := mux.Vars(r)
 | 
			
		||||
	path := vars["path"]
 | 
			
		||||
 | 
			
		||||
	authCtx := auth.GetAuthContext(r.Context())
 | 
			
		||||
	if authCtx == nil {
 | 
			
		||||
		http.Error(w, "Unauthorized", http.StatusUnauthorized)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Check write permission on the resource (owner write required for metadata changes)
 | 
			
		||||
	if !s.authService.CheckResourcePermission(authCtx, path, "write") {
 | 
			
		||||
		http.Error(w, "Forbidden", http.StatusForbidden)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var req types.UpdateResourceMetadataRequest
 | 
			
		||||
	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
 | 
			
		||||
		http.Error(w, "Bad Request", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Get current metadata (or default if not exists)
 | 
			
		||||
	currentMetadata, err := s.authService.GetResourceMetadata(path)
 | 
			
		||||
	if err == badger.ErrKeyNotFound {
 | 
			
		||||
		currentMetadata = &types.ResourceMetadata{
 | 
			
		||||
			OwnerUUID:   authCtx.UserUUID,
 | 
			
		||||
			GroupUUID:   "",
 | 
			
		||||
			Permissions: types.DefaultPermissions,
 | 
			
		||||
			CreatedAt:   time.Now().Unix(),
 | 
			
		||||
			UpdatedAt:   time.Now().Unix(),
 | 
			
		||||
		}
 | 
			
		||||
	} else if err != nil {
 | 
			
		||||
		s.logger.WithError(err).WithField("path", path).Error("Failed to get current resource metadata")
 | 
			
		||||
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Apply updates only to provided fields
 | 
			
		||||
	updated := false
 | 
			
		||||
	if req.OwnerUUID != "" {
 | 
			
		||||
		currentMetadata.OwnerUUID = req.OwnerUUID
 | 
			
		||||
		updated = true
 | 
			
		||||
	}
 | 
			
		||||
	if req.GroupUUID != "" {
 | 
			
		||||
		currentMetadata.GroupUUID = req.GroupUUID
 | 
			
		||||
		updated = true
 | 
			
		||||
	}
 | 
			
		||||
	if req.Permissions != 0 {
 | 
			
		||||
		currentMetadata.Permissions = req.Permissions
 | 
			
		||||
		updated = true
 | 
			
		||||
	}
 | 
			
		||||
	if req.TTL != "" {
 | 
			
		||||
		currentMetadata.TTL = req.TTL
 | 
			
		||||
		updated = true
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if !updated {
 | 
			
		||||
		http.Error(w, "No fields provided for update", http.StatusBadRequest)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Store updated metadata
 | 
			
		||||
	if err := s.authService.StoreResourceMetadata(path, currentMetadata); err != nil {
 | 
			
		||||
		s.logger.WithError(err).WithField("path", path).Error("Failed to store resource metadata")
 | 
			
		||||
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
 | 
			
		||||
		return
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	response := types.GetResourceMetadataResponse{
 | 
			
		||||
		OwnerUUID:   currentMetadata.OwnerUUID,
 | 
			
		||||
		GroupUUID:   currentMetadata.GroupUUID,
 | 
			
		||||
		Permissions: currentMetadata.Permissions,
 | 
			
		||||
		TTL:         currentMetadata.TTL,
 | 
			
		||||
		CreatedAt:   currentMetadata.CreatedAt,
 | 
			
		||||
		UpdatedAt:   currentMetadata.UpdatedAt,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	w.Header().Set("Content-Type", "application/json")
 | 
			
		||||
	w.WriteHeader(http.StatusOK)
 | 
			
		||||
	json.NewEncoder(w).Encode(response)
 | 
			
		||||
 | 
			
		||||
	s.logger.WithFields(logrus.Fields{
 | 
			
		||||
		"path":        path,
 | 
			
		||||
		"user_uuid":   authCtx.UserUUID,
 | 
			
		||||
		"owner_uuid":  currentMetadata.OwnerUUID,
 | 
			
		||||
		"group_uuid":  currentMetadata.GroupUUID,
 | 
			
		||||
		"permissions": currentMetadata.Permissions,
 | 
			
		||||
	}).Info("Resource metadata updated")
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,19 @@ func (s *Server) setupRoutes() *mux.Router {
 | 
			
		||||
		router.HandleFunc("/kv/{path:.+}", s.deleteKVHandler).Methods("DELETE")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Resource Metadata endpoints (available when auth is enabled)
 | 
			
		||||
	if s.config.AuthEnabled {
 | 
			
		||||
		// GET metadata - require read permission
 | 
			
		||||
		router.Handle("/kv/{path:.+}/metadata", s.authService.Middleware(
 | 
			
		||||
			[]string{"read"}, func(r *http.Request) string { return mux.Vars(r)["path"] }, "read",
 | 
			
		||||
		)(s.getResourceMetadataHandler)).Methods("GET")
 | 
			
		||||
		
 | 
			
		||||
		// PUT metadata - require write permission (owner write)
 | 
			
		||||
		router.Handle("/kv/{path:.+}/metadata", s.authService.Middleware(
 | 
			
		||||
			[]string{"write"}, func(r *http.Request) string { return mux.Vars(r)["path"] }, "write",
 | 
			
		||||
		)(s.updateResourceMetadataHandler)).Methods("PUT")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Member endpoints (available when clustering is enabled)
 | 
			
		||||
	if s.config.ClusteringEnabled {
 | 
			
		||||
		router.HandleFunc("/members/", s.getMembersHandler).Methods("GET")
 | 
			
		||||
 
 | 
			
		||||
@@ -131,6 +131,23 @@ type CreateTokenResponse struct {
 | 
			
		||||
	ExpiresAt int64  `json:"expires_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Resource Metadata Management API structures
 | 
			
		||||
type UpdateResourceMetadataRequest struct {
 | 
			
		||||
	OwnerUUID   string `json:"owner_uuid,omitempty"`
 | 
			
		||||
	GroupUUID   string `json:"group_uuid,omitempty"`
 | 
			
		||||
	Permissions int    `json:"permissions,omitempty"`
 | 
			
		||||
	TTL         string `json:"ttl,omitempty"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
type GetResourceMetadataResponse struct {
 | 
			
		||||
	OwnerUUID   string `json:"owner_uuid"`
 | 
			
		||||
	GroupUUID   string `json:"group_uuid"`
 | 
			
		||||
	Permissions int    `json:"permissions"`
 | 
			
		||||
	TTL         string `json:"ttl"`
 | 
			
		||||
	CreatedAt   int64  `json:"created_at"`
 | 
			
		||||
	UpdatedAt   int64  `json:"updated_at"`
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Cluster and member management types
 | 
			
		||||
type Member struct {
 | 
			
		||||
	ID              string `json:"id"`
 | 
			
		||||
@@ -277,4 +294,4 @@ type Config struct {
 | 
			
		||||
	// Anonymous access control (Issue #5)
 | 
			
		||||
	AllowAnonymousRead     bool `yaml:"allow_anonymous_read"`     // Allow unauthenticated read access to KV endpoints
 | 
			
		||||
	AllowAnonymousWrite    bool `yaml:"allow_anonymous_write"`    // Allow unauthenticated write access to KV endpoints
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user