Files
seaweedFS/weed/admin/handlers/user_handlers.go
Chris Lu 67a551fd62 admin UI: add anonymous user creation checkbox (#8773)
Add an "Anonymous" checkbox next to the username field in the Create User
modal. When checked, the username is set to "anonymous" and the credential
generation checkbox is disabled since anonymous users do not need keys.

The checkbox is only shown when no anonymous user exists yet. The
manage-access-keys button in the users table is hidden for the anonymous
user.
2026-03-25 21:24:10 -07:00

330 lines
10 KiB
Go

package handlers
import (
"errors"
"fmt"
"io"
"net/http"
"time"
"github.com/gorilla/mux"
"github.com/seaweedfs/seaweedfs/weed/admin/dash"
"github.com/seaweedfs/seaweedfs/weed/admin/view/app"
"github.com/seaweedfs/seaweedfs/weed/admin/view/layout"
"github.com/seaweedfs/seaweedfs/weed/glog"
)
// UserHandlers contains all the HTTP handlers for user management
type UserHandlers struct {
adminServer *dash.AdminServer
}
// NewUserHandlers creates a new instance of UserHandlers
func NewUserHandlers(adminServer *dash.AdminServer) *UserHandlers {
return &UserHandlers{
adminServer: adminServer,
}
}
// ShowObjectStoreUsers renders the object store users management page
func (h *UserHandlers) ShowObjectStoreUsers(w http.ResponseWriter, r *http.Request) {
// Get object store users data from the server
usersData := h.getObjectStoreUsersData(r)
// Render HTML template
// Add cache-control headers to prevent browser caching of inline JavaScript
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate")
w.Header().Set("Pragma", "no-cache")
w.Header().Set("Expires", "0")
w.Header().Set("ETag", fmt.Sprintf("\"%d\"", time.Now().Unix()))
usersComponent := app.ObjectStoreUsers(usersData)
viewCtx := layout.NewViewContext(r, dash.UsernameFromContext(r.Context()), dash.CSRFTokenFromContext(r.Context()))
layoutComponent := layout.Layout(viewCtx, usersComponent)
err := layoutComponent.Render(r.Context(), w)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, "Failed to render template: "+err.Error())
return
}
}
// GetUsers returns the list of users as JSON
func (h *UserHandlers) GetUsers(w http.ResponseWriter, r *http.Request) {
users, err := h.adminServer.GetObjectStoreUsers(r.Context())
if err != nil {
writeJSONError(w, http.StatusInternalServerError, "Failed to get users: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"users": users})
}
// CreateUser handles user creation
func (h *UserHandlers) CreateUser(w http.ResponseWriter, r *http.Request) {
var req dash.CreateUserRequest
if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
// Validate required fields
if req.Username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
user, err := h.adminServer.CreateObjectStoreUser(req)
if err != nil {
glog.Errorf("Failed to create user %s: %v", req.Username, err)
writeJSONError(w, http.StatusInternalServerError, "Failed to create user: "+err.Error())
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"message": "User created successfully",
"user": user,
})
}
// UpdateUser handles user updates
func (h *UserHandlers) UpdateUser(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
var req dash.UpdateUserRequest
if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
user, err := h.adminServer.UpdateObjectStoreUser(username, req)
if err != nil {
glog.Errorf("Failed to update user %s: %v", username, err)
writeJSONError(w, http.StatusInternalServerError, "Failed to update user: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "User updated successfully",
"user": user,
})
}
// DeleteUser handles user deletion
func (h *UserHandlers) DeleteUser(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
err := h.adminServer.DeleteObjectStoreUser(username)
if err != nil {
glog.Errorf("Failed to delete user %s: %v", username, err)
writeJSONError(w, http.StatusInternalServerError, "Failed to delete user: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "User deleted successfully",
})
}
// GetUserDetails returns detailed information about a specific user
func (h *UserHandlers) GetUserDetails(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
user, err := h.adminServer.GetObjectStoreUserDetails(username)
if err != nil {
writeJSONError(w, http.StatusNotFound, "User not found: "+err.Error())
return
}
writeJSON(w, http.StatusOK, user)
}
// CreateAccessKey creates a new access key for a user
func (h *UserHandlers) CreateAccessKey(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
var req *dash.CreateAccessKeyRequest
var body dash.CreateAccessKeyRequest
if err := decodeJSONBody(newJSONMaxReader(w, r), &body); err != nil {
if !errors.Is(err, io.EOF) {
writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
// Empty body: auto-generate both keys
} else {
req = &body
}
accessKey, err := h.adminServer.CreateAccessKey(username, req)
if err != nil {
glog.Errorf("Failed to create access key for user %s: %v", username, err)
if errors.Is(err, dash.ErrAccessKeyInUse) {
writeJSONError(w, http.StatusConflict, err.Error())
} else if errors.Is(err, dash.ErrUserNotFound) {
writeJSONError(w, http.StatusNotFound, err.Error())
} else if errors.Is(err, dash.ErrInvalidInput) {
writeJSONError(w, http.StatusBadRequest, err.Error())
} else {
writeJSONError(w, http.StatusInternalServerError, "Failed to create access key: "+err.Error())
}
return
}
writeJSON(w, http.StatusCreated, map[string]interface{}{
"message": "Access key created successfully",
"access_key": accessKey,
})
}
// DeleteAccessKey deletes an access key for a user
func (h *UserHandlers) DeleteAccessKey(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
username := vars["username"]
accessKeyId := vars["accessKeyId"]
if username == "" || accessKeyId == "" {
writeJSONError(w, http.StatusBadRequest, "Username and access key ID are required")
return
}
err := h.adminServer.DeleteAccessKey(username, accessKeyId)
if err != nil {
glog.Errorf("Failed to delete access key %s for user %s: %v", accessKeyId, username, err)
writeJSONError(w, http.StatusInternalServerError, "Failed to delete access key: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Access key deleted successfully",
})
}
// UpdateAccessKeyStatus updates the status of an access key for a user
func (h *UserHandlers) UpdateAccessKeyStatus(w http.ResponseWriter, r *http.Request) {
vars := mux.Vars(r)
username := vars["username"]
accessKeyId := vars["accessKeyId"]
if username == "" || accessKeyId == "" {
writeJSONError(w, http.StatusBadRequest, "Username and access key ID are required")
return
}
var req dash.UpdateAccessKeyStatusRequest
if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
// Validate status
if req.Status != dash.AccessKeyStatusActive && req.Status != dash.AccessKeyStatusInactive {
writeJSONError(w, http.StatusBadRequest, 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)
writeJSONError(w, http.StatusInternalServerError, "Failed to update access key status: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "Access key updated successfully",
})
}
// GetUserPolicies returns the policies for a user
func (h *UserHandlers) GetUserPolicies(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
policies, err := h.adminServer.GetUserPolicies(username)
if err != nil {
writeJSONError(w, http.StatusInternalServerError, "Failed to get user policies: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{"policies": policies})
}
// UpdateUserPolicies updates the policies for a user
func (h *UserHandlers) UpdateUserPolicies(w http.ResponseWriter, r *http.Request) {
username := mux.Vars(r)["username"]
if username == "" {
writeJSONError(w, http.StatusBadRequest, "Username is required")
return
}
var req dash.UpdateUserPoliciesRequest
if err := decodeJSONBody(newJSONMaxReader(w, r), &req); err != nil {
writeJSONError(w, http.StatusBadRequest, "Invalid request: "+err.Error())
return
}
err := h.adminServer.UpdateUserPolicies(username, req.Actions)
if err != nil {
glog.Errorf("Failed to update policies for user %s: %v", username, err)
writeJSONError(w, http.StatusInternalServerError, "Failed to update user policies: "+err.Error())
return
}
writeJSON(w, http.StatusOK, map[string]interface{}{
"message": "User policies updated successfully",
})
}
// getObjectStoreUsersData retrieves object store users data from the server
func (h *UserHandlers) getObjectStoreUsersData(r *http.Request) dash.ObjectStoreUsersData {
username := dash.UsernameFromContext(r.Context())
if username == "" {
username = "admin"
}
// Get object store users
users, err := h.adminServer.GetObjectStoreUsers(r.Context())
if err != nil {
glog.Errorf("Failed to get object store users: %v", err)
// Return empty data on error
return dash.ObjectStoreUsersData{
Username: username,
Users: []dash.ObjectStoreUser{},
TotalUsers: 0,
LastUpdated: time.Now(),
}
}
hasAnonymous := false
for _, u := range users {
if u.Username == "anonymous" {
hasAnonymous = true
break
}
}
return dash.ObjectStoreUsersData{
Username: username,
Users: users,
TotalUsers: len(users),
HasAnonymousUser: hasAnonymous,
LastUpdated: time.Now(),
}
}