Ship SCUM user SQL management page

This commit is contained in:
npc0-hue
2026-08-24 16:43:00 +08:00
parent 3005e0510c
commit d4e3f68032
19 changed files with 351 additions and 115 deletions
+23 -2
View File
@@ -109,10 +109,14 @@ func validateRemoteAdapterInputs(field string, inputs map[string]string) []strin
}
var violations []string
for key, value := range inputs {
if !clientManagerIdentifierPattern.MatchString(key) || unsafeGameClientBridgePayloadKey(key) {
if !clientManagerIdentifierPattern.MatchString(key) || unsafeRemoteAdapterInputKey(key) {
violations = append(violations, field+" key is invalid or unsafe")
}
if len([]rune(value)) > 2048 {
limit := 2048
if remoteAdapterSQLInputKey(key) {
limit = 16 * 1024
}
if len([]rune(value)) > limit {
violations = append(violations, field+"."+key+" is too long")
}
for _, reason := range unsafePluginStringReasons(value) {
@@ -121,3 +125,20 @@ func validateRemoteAdapterInputs(field string, inputs map[string]string) []strin
}
return violations
}
func unsafeRemoteAdapterInputKey(key string) bool {
if remoteAdapterSQLInputKey(key) {
return false
}
return unsafeGameClientBridgePayloadKey(key)
}
func remoteAdapterSQLInputKey(key string) bool {
normalized := strings.ToLower(strings.NewReplacer(".", "", "_", "", "-", "", ":", "", "/", "").Replace(key))
switch normalized {
case "sql", "sqltext", "sqlstatement", "sqlquery", "rawsql", "rawquery", "statement":
return true
default:
return false
}
}
+3
View File
@@ -18,6 +18,9 @@ func TestObservabilityValidatorsBoundMetricsBackupsAndRemoteTargets(t *testing.T
if err := ValidateRemoteAdapterRequest(domain.RemoteAdapterRequest{ServerInstanceID: "server-1", DeclarationKey: "ftp", TargetKey: "tcp://host", Capability: "remote.ftp.read", IdempotencyKey: "request-1"}); err == nil {
t.Fatal("expected unsafe remote target rejection")
}
if err := ValidateRemoteAdapterRequest(domain.RemoteAdapterRequest{ServerInstanceID: "server-1", DeclarationKey: "sqlite-db", TargetKey: "scum-db", Capability: domain.JobCapabilityRemoteRunDBSQLiteExecute, IdempotencyKey: "sql-execute-1", Inputs: map[string]string{"mode": "execute", "sqlText": "UPDATE prisoner SET stamina = 855 WHERE id = 'steam-123';"}}); err != nil {
t.Fatalf("expected SQL text input to validate: %v", err)
}
}
func floatPtr(value float64) *float64 { return &value }
+15 -7
View File
@@ -21,7 +21,7 @@ const (
maxPluginDescriptionLength = 240
maxPluginPageTitleLength = 40
maxPluginBridgePayloadKeys = 16
maxPluginBridgePayloadSize = 4096
maxPluginBridgePayloadSize = 16 * 1024
maxProgressMessageLength = 256
maxServerConfigContentSize = 64 * 1024
maxJobExecutionContentSize = 64 * 1024
@@ -971,7 +971,11 @@ func ValidatePluginBridgeExecuteRequest(request domain.PluginBridgeExecuteReques
if strings.TrimSpace(key) == "" || strings.TrimSpace(key) != key || len([]rune(key)) > 80 {
violations = append(violations, "payload key is invalid")
}
if len([]rune(value)) > 1024 {
valueLimit := 1024
if remoteAdapterSQLInputKey(strings.TrimPrefix(key, "input.")) {
valueLimit = 16 * 1024
}
if len([]rune(value)) > valueLimit {
violations = append(violations, "payload value is too long")
}
for _, reason := range unsafePluginStringReasons(key) {
@@ -2053,12 +2057,14 @@ func validateRemoteAccess(field string, remote domain.GamePluginRemoteAccess, de
violations = append(violations, field+".logTransfer requires remote.run.logs.transfer")
}
for _, engine := range remote.DatabaseEngines {
required := domain.JobCapabilityRemoteRunDBMySQLQuery
queryCapability := domain.JobCapabilityRemoteRunDBMySQLQuery
executeCapability := domain.JobCapabilityRemoteRunDBMySQLExecute
if engine == "sqlite" {
required = domain.JobCapabilityRemoteRunDBSQLiteQuery
queryCapability = domain.JobCapabilityRemoteRunDBSQLiteQuery
executeCapability = domain.JobCapabilityRemoteRunDBSQLiteExecute
}
if !containsString(remote.RunCapabilities, required) {
violations = append(violations, fmt.Sprintf("%s.databaseEngines requires %s", field, required))
if !containsString(remote.RunCapabilities, queryCapability) && !containsString(remote.RunCapabilities, executeCapability) {
violations = append(violations, fmt.Sprintf("%s.databaseEngines requires %s or %s", field, queryCapability, executeCapability))
}
}
return violations
@@ -2352,7 +2358,7 @@ func validPluginRunCapability(capability string) bool {
domain.JobCapabilityRemoteRsyncRead, domain.JobCapabilityRemoteRsyncWrite,
domain.JobCapabilityRemoteRunFilesRead, domain.JobCapabilityRemoteRunFilesWrite,
domain.JobCapabilityRemoteRunProcessStart, domain.JobCapabilityRemoteRunProcessStop,
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBSQLiteQuery,
domain.JobCapabilityRemoteRunDBMySQLQuery, domain.JobCapabilityRemoteRunDBMySQLExecute, domain.JobCapabilityRemoteRunDBSQLiteQuery, domain.JobCapabilityRemoteRunDBSQLiteExecute,
domain.JobCapabilityRemoteRunLogsTransfer, domain.JobCapabilityRemoteRunRCONCommand,
domain.JobCapabilityRemoteRunProgram,
domain.JobCapabilityRunSelfUpdate, domain.JobCapabilityDependenciesCheck, domain.JobCapabilityDependenciesInstall,
@@ -2386,7 +2392,9 @@ func remoteCapabilityRequiresInputRef(capability string) bool {
domain.JobCapabilityRemoteRsyncWrite,
domain.JobCapabilityRemoteRunFilesWrite,
domain.JobCapabilityRemoteRunDBMySQLQuery,
domain.JobCapabilityRemoteRunDBMySQLExecute,
domain.JobCapabilityRemoteRunDBSQLiteQuery,
domain.JobCapabilityRemoteRunDBSQLiteExecute,
domain.JobCapabilityRemoteRunRCONCommand, domain.JobCapabilityRemoteRunProgram:
return true
default: