Compare commits
1 Commits
self-daemo
...
master
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