fix: allow admin access to sensitive configs (#511)

This commit is contained in:
sagit
2026-05-17 23:09:31 +08:00
committed by GitHub
2 changed files with 118 additions and 8 deletions
@@ -67,7 +67,22 @@ func TestConfigGetRejectsSensitiveKeysWithoutAuth(t *testing.T) {
assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置")
} }
func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) { func TestConfigGetAllowsSensitiveKeysForAdmin(t *testing.T) {
router, r := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
seedConfigValue(t, r, "jwt_secret", "jwt-secret")
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/get", bytes.NewBufferString(`{"name":"jwt_secret"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", adminToken)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerConfigValue(t, resp, "jwt_secret", "jwt-secret")
}
func TestConfigUpdateAllowsSensitiveKeysForAdmin(t *testing.T) {
router, _ := setupConfigAccessTestRouter(t) router, _ := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
@@ -78,10 +93,10 @@ func TestConfigUpdateRejectsSensitiveKeys(t *testing.T) {
router.ServeHTTP(resp, req) router.ServeHTTP(resp, req)
assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") assertHandlerCode(t, resp, 0)
} }
func TestConfigUpdateSingleRejectsSensitiveKeys(t *testing.T) { func TestConfigUpdateSingleAllowsSensitiveKeysForAdmin(t *testing.T) {
router, _ := setupConfigAccessTestRouter(t) router, _ := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0) adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
@@ -92,7 +107,95 @@ func TestConfigUpdateSingleRejectsSensitiveKeys(t *testing.T) {
router.ServeHTTP(resp, req) router.ServeHTTP(resp, req)
assertHandlerCodeMsg(t, resp, 403, "禁止访问敏感配置") assertHandlerCode(t, resp, 0)
}
func TestConfigUpdateAllowsCloudflareSecretKeyWrite(t *testing.T) {
router, r := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update", bytes.NewBufferString(`{"cloudflare_secret_key":"turnstile-secret"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", adminToken)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerCode(t, resp, 0)
cfg, err := r.GetConfigByName("cloudflare_secret_key")
if err != nil {
t.Fatalf("get config: %v", err)
}
if cfg == nil || cfg.Value != "turnstile-secret" {
t.Fatalf("expected cloudflare_secret_key to be updated, got %#v", cfg)
}
}
func TestConfigUpdateSingleAllowsCloudflareSecretKeyWrite(t *testing.T) {
router, r := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update-single", bytes.NewBufferString(`{"name":"cloudflare_secret_key","value":"turnstile-secret"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", adminToken)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerCode(t, resp, 0)
cfg, err := r.GetConfigByName("cloudflare_secret_key")
if err != nil {
t.Fatalf("get config: %v", err)
}
if cfg == nil || cfg.Value != "turnstile-secret" {
t.Fatalf("expected cloudflare_secret_key to be updated, got %#v", cfg)
}
}
func TestConfigUpdateAllowsLicenseKeyWrite(t *testing.T) {
router, r := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update", bytes.NewBufferString(`{"license_key":"license-secret"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", adminToken)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerCode(t, resp, 0)
cfg, err := r.GetConfigByName("license_key")
if err != nil {
t.Fatalf("get config: %v", err)
}
if cfg == nil || cfg.Value != "license-secret" {
t.Fatalf("expected license_key to be updated, got %#v", cfg)
}
}
func TestConfigUpdateSingleAllowsLicenseKeyWrite(t *testing.T) {
router, r := setupConfigAccessTestRouter(t)
adminToken := mustGenerateConfigAccessToken(t, 1, "admin_user", 0)
req := httptest.NewRequest(http.MethodPost, "/api/v1/config/update-single", bytes.NewBufferString(`{"name":"license_key","value":"license-secret"}`))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", adminToken)
resp := httptest.NewRecorder()
router.ServeHTTP(resp, req)
assertHandlerCode(t, resp, 0)
cfg, err := r.GetConfigByName("license_key")
if err != nil {
t.Fatalf("get config: %v", err)
}
if cfg == nil || cfg.Value != "license-secret" {
t.Fatalf("expected license_key to be updated, got %#v", cfg)
}
} }
func setupConfigAccessTestRouter(t *testing.T) (http.Handler, *repo.Repository) { func setupConfigAccessTestRouter(t *testing.T) (http.Handler, *repo.Repository) {
+11 -4
View File
@@ -393,8 +393,7 @@ func (h *Handler) getConfigByName(w http.ResponseWriter, r *http.Request) {
return return
} }
configName := strings.ToLower(strings.TrimSpace(req.Name)) configName := strings.ToLower(strings.TrimSpace(req.Name))
switch configName { if repo.IsSensitiveConfigKey(configName) && !isAdminRequest(r) {
case "license_key", "cloudflare_secret_key", "jwt_secret":
response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) response.WriteJSON(w, response.Err(403, "禁止访问敏感配置"))
return return
} }
@@ -989,7 +988,7 @@ func (h *Handler) updateConfigs(w http.ResponseWriter, r *http.Request) {
if key == "" { if key == "" {
continue continue
} }
if repo.IsSensitiveConfigKey(key) { if repo.IsSensitiveConfigKey(key) && !isAdminRequest(r) {
response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) response.WriteJSON(w, response.Err(403, "禁止访问敏感配置"))
return return
} }
@@ -1030,7 +1029,7 @@ func (h *Handler) updateSingleConfig(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, response.ErrDefault("配置名称不能为空")) response.WriteJSON(w, response.ErrDefault("配置名称不能为空"))
return return
} }
if repo.IsSensitiveConfigKey(name) { if repo.IsSensitiveConfigKey(name) && !isAdminRequest(r) {
response.WriteJSON(w, response.Err(403, "禁止访问敏感配置")) response.WriteJSON(w, response.Err(403, "禁止访问敏感配置"))
return return
} }
@@ -1060,6 +1059,14 @@ func (h *Handler) updateSingleConfig(w http.ResponseWriter, r *http.Request) {
response.WriteJSON(w, response.OKEmpty()) response.WriteJSON(w, response.OKEmpty())
} }
func isAdminRequest(r *http.Request) bool {
if r == nil {
return false
}
claims, ok := r.Context().Value(middleware.ClaimsContextKey).(auth.Claims)
return ok && claims.RoleID == 0
}
func normalizeAndValidateConfigValue(key, value string) (string, error) { func normalizeAndValidateConfigValue(key, value string) (string, error) {
switch strings.TrimSpace(key) { switch strings.TrimSpace(key) {
case "app_logo", "app_favicon": case "app_logo", "app_favicon":