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 } if h.adminServer.IsStaticUser(username) { writeJSONError(w, http.StatusForbidden, "Cannot modify static user "+username+" (loaded from config file)") 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 } if h.adminServer.IsStaticUser(username) { writeJSONError(w, http.StatusForbidden, "Cannot delete static user "+username+" (loaded from config file)") 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(), } }