forked from ryyst/kalzu-value-store
Add API endpoints for resource metadata management (ownership & permissions)
New types: UpdateResourceMetadataRequest and GetResourceMetadataResponse in types.go AuthService methods: StoreResourceMetadata and GetResourceMetadata in auth/auth.go Handlers: getResourceMetadataHandler and updateResourceMetadataHandler in server/handlers.go Routes: /kv/{path}/metadata (GET for read, PUT for update) with auth middleware in server/routes.go Enables fine-grained control over KV path ownership, group assignments, and POSIX-inspired permissions.
This commit is contained in:
@@ -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")
|
||||
}
|
||||
|
Reference in New Issue
Block a user