diff --git a/weed/admin/dash/admin_data.go b/weed/admin/dash/admin_data.go index 7a9e50964..9d33ee158 100644 --- a/weed/admin/dash/admin_data.go +++ b/weed/admin/dash/admin_data.go @@ -12,6 +12,12 @@ import ( "github.com/seaweedfs/seaweedfs/weed/pb/master_pb" ) +// Access key status constants +const ( + AccessKeyStatusActive = "Active" + AccessKeyStatusInactive = "Inactive" +) + type AdminData struct { Username string `json:"username"` TotalVolumes int `json:"total_volumes"` @@ -69,9 +75,14 @@ type UpdateUserPoliciesRequest struct { type AccessKeyInfo struct { AccessKey string `json:"access_key"` SecretKey string `json:"secret_key"` + Status string `json:"status"` CreatedAt time.Time `json:"created_at"` } +type UpdateAccessKeyStatusRequest struct { + Status string `json:"status" binding:"required"` +} + type UserDetails struct { Username string `json:"username"` Email string `json:"email"` diff --git a/weed/admin/dash/user_management.go b/weed/admin/dash/user_management.go index 38e1f316d..3f2d48feb 100644 --- a/weed/admin/dash/user_management.go +++ b/weed/admin/dash/user_management.go @@ -192,6 +192,7 @@ func (s *AdminServer) GetObjectStoreUserDetails(username string) (*UserDetails, details.AccessKeys = append(details.AccessKeys, AccessKeyInfo{ AccessKey: cred.AccessKey, SecretKey: cred.SecretKey, + Status: cred.Status, CreatedAt: time.Now().AddDate(0, -1, 0), // Mock creation date }) } @@ -223,6 +224,7 @@ func (s *AdminServer) CreateAccessKey(username string) (*AccessKeyInfo, error) { credential := &iam_pb.Credential{ AccessKey: accessKey, SecretKey: secretKey, + Status: AccessKeyStatusActive, } // Create access key using credential manager @@ -234,6 +236,7 @@ func (s *AdminServer) CreateAccessKey(username string) (*AccessKeyInfo, error) { return &AccessKeyInfo{ AccessKey: accessKey, SecretKey: secretKey, + Status: AccessKeyStatusActive, CreatedAt: time.Now(), }, nil } @@ -261,6 +264,51 @@ func (s *AdminServer) DeleteAccessKey(username, accessKeyId string) error { return nil } +// UpdateAccessKeyStatus updates the status of an access key for a user +func (s *AdminServer) UpdateAccessKeyStatus(username, accessKeyId, status string) error { + if s.credentialManager == nil { + return fmt.Errorf("credential manager not available") + } + + // Validate status against allowed values + if status != AccessKeyStatusActive && status != AccessKeyStatusInactive { + return fmt.Errorf("invalid status '%s': must be '%s' or '%s'", status, AccessKeyStatusActive, AccessKeyStatusInactive) + } + + ctx := context.Background() + + // Get user using credential manager + identity, err := s.credentialManager.GetUser(ctx, username) + if err != nil { + if err == credential.ErrUserNotFound { + return fmt.Errorf("user %s not found", username) + } + return fmt.Errorf("failed to get user: %w", err) + } + + // Find and update the access key status + found := false + for _, cred := range identity.Credentials { + if cred.AccessKey == accessKeyId { + cred.Status = status + found = true + break + } + } + + if !found { + return fmt.Errorf("access key %s not found for user %s", accessKeyId, username) + } + + // Update user using credential manager + err = s.credentialManager.UpdateUser(ctx, username, identity) + if err != nil { + return fmt.Errorf("failed to update user access key status: %w", err) + } + + return nil +} + // GetUserPolicies returns the policies for a user (actions) func (s *AdminServer) GetUserPolicies(username string) ([]string, error) { if s.credentialManager == nil { diff --git a/weed/admin/handlers/admin_handlers.go b/weed/admin/handlers/admin_handlers.go index 919526c4c..52d8f9f66 100644 --- a/weed/admin/handlers/admin_handlers.go +++ b/weed/admin/handlers/admin_handlers.go @@ -148,6 +148,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser, usersApi.DELETE("/:username", dash.RequireWriteAccess(), h.userHandlers.DeleteUser) usersApi.POST("/:username/access-keys", dash.RequireWriteAccess(), h.userHandlers.CreateAccessKey) usersApi.DELETE("/:username/access-keys/:accessKeyId", dash.RequireWriteAccess(), h.userHandlers.DeleteAccessKey) + usersApi.PUT("/:username/access-keys/:accessKeyId/status", dash.RequireWriteAccess(), h.userHandlers.UpdateAccessKeyStatus) usersApi.GET("/:username/policies", h.userHandlers.GetUserPolicies) usersApi.PUT("/:username/policies", dash.RequireWriteAccess(), h.userHandlers.UpdateUserPolicies) } @@ -288,6 +289,7 @@ func (h *AdminHandlers) SetupRoutes(r *gin.Engine, authRequired bool, adminUser, usersApi.DELETE("/:username", h.userHandlers.DeleteUser) usersApi.POST("/:username/access-keys", h.userHandlers.CreateAccessKey) usersApi.DELETE("/:username/access-keys/:accessKeyId", h.userHandlers.DeleteAccessKey) + usersApi.PUT("/:username/access-keys/:accessKeyId/status", h.userHandlers.UpdateAccessKeyStatus) usersApi.GET("/:username/policies", h.userHandlers.GetUserPolicies) usersApi.PUT("/:username/policies", h.userHandlers.UpdateUserPolicies) } diff --git a/weed/admin/handlers/user_handlers.go b/weed/admin/handlers/user_handlers.go index 89b07bf75..827e21dc1 100644 --- a/weed/admin/handlers/user_handlers.go +++ b/weed/admin/handlers/user_handlers.go @@ -189,6 +189,40 @@ func (h *UserHandlers) DeleteAccessKey(c *gin.Context) { }) } +// UpdateAccessKeyStatus updates the status of an access key for a user +func (h *UserHandlers) UpdateAccessKeyStatus(c *gin.Context) { + username := c.Param("username") + accessKeyId := c.Param("accessKeyId") + + if username == "" || accessKeyId == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "Username and access key ID are required"}) + return + } + + var req dash.UpdateAccessKeyStatusRequest + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request: " + err.Error()}) + return + } + + // Validate status + if req.Status != dash.AccessKeyStatusActive && req.Status != dash.AccessKeyStatusInactive { + c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("Status must be '%s' or '%s'", dash.AccessKeyStatusActive, dash.AccessKeyStatusInactive)}) + return + } + + err := h.adminServer.UpdateAccessKeyStatus(username, accessKeyId, req.Status) + if err != nil { + glog.Errorf("Failed to update access key status %s for user %s: %v", accessKeyId, username, err) + c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update access key status: " + err.Error()}) + return + } + + c.JSON(http.StatusOK, gin.H{ + "message": "Access key updated successfully", + }) +} + // GetUserPolicies returns the policies for a user func (h *UserHandlers) GetUserPolicies(c *gin.Context) { username := c.Param("username") diff --git a/weed/admin/view/app/object_store_users.templ b/weed/admin/view/app/object_store_users.templ index 0f9a2d693..b3b7d92de 100644 --- a/weed/admin/view/app/object_store_users.templ +++ b/weed/admin/view/app/object_store_users.templ @@ -396,6 +396,10 @@ templ ObjectStoreUsers(data dash.ObjectStoreUsersData) { ") + templ_7745c5c3_Err = templruntime.WriteString(templ_7745c5c3_Buffer, 15, "
Create New User
Hold Ctrl/Cmd to select multiple permissions
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
Hold Ctrl/Cmd to select multiple policies
Edit User
Apply selected permissions to specific buckets or all buckets
Hold Ctrl/Cmd to select multiple buckets
User Details
Manage Access Keys
Access Keys for
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err }