feat(access): 服务器没装 LuckPerms 时权限与用户组操作返回 409 luckperms_missing,面板说明原因
This commit is contained in:
9 files changed
+178
-10
No files matched your search
@@ -57,8 +57,32 @@ var (
|
||||
"invalid world (allowed: letters, digits, _ -)")
|
||||
errInvalidGroup = newError(http.StatusBadRequest, "bad_request",
|
||||
"invalid group (allowed: letters, digits, _ -)")
|
||||
errLuckPermsMissing = newError(http.StatusConflict, "luckperms_missing",
|
||||
"LuckPerms is not installed on this server, so permission and group changes have no effect; "+
|
||||
"the lobby gets it back by re-running the installer, another server needs the LuckPerms plugin added")
|
||||
)
|
||||
|
||||
// unknownCommandRe matches the server's reply to a command no plugin registered,
|
||||
// after color stripping. Brigadier servers (Paper, vanilla 1.13+) answer
|
||||
// "Unknown or incomplete command. See below for error" (older builds: ", see
|
||||
// below"), Spigot and legacy Bukkit "Unknown command. Type "/help" for help.".
|
||||
// With LuckPerms installed an lp command never gets this: LuckPerms answers
|
||||
// asynchronously, after RCON has flushed the reply, so the body is empty.
|
||||
var unknownCommandRe = regexp.MustCompile(`(?i)^\s*unknown (or incomplete )?command\b`)
|
||||
|
||||
// issueLuckPermsCommand runs an lp command through issueAccessCommand and turns
|
||||
// the reply of a server without LuckPerms into 409 luckperms_missing (#4). Without
|
||||
// it that reply went back as a success whose output nobody reads, and the change
|
||||
// silently did nothing.
|
||||
func (a *API) issueLuckPermsCommand(w http.ResponseWriter, r *http.Request, name, command string) (string, bool) {
|
||||
out, ok := a.issueAccessCommand(w, r, name, command)
|
||||
if ok && unknownCommandRe.MatchString(lpColorRe.ReplaceAllString(out, "")) {
|
||||
writeError(w, r, errLuckPermsMissing)
|
||||
return "", false
|
||||
}
|
||||
return out, ok
|
||||
}
|
||||
|
||||
// issueAccessCommand is the shared spine of every §7 access mutation: resolve the
|
||||
// named server, enforce owner-or-admin, require readiness, and run ONE
|
||||
// already-validated RCON command, returning its reply. It centralises the
|
||||
@@ -350,7 +374,7 @@ func (a *API) handleAccessPermission(w http.ResponseWriter, r *http.Request) {
|
||||
cmd += " world=" + body.World
|
||||
}
|
||||
|
||||
out, ok := a.issueAccessCommand(w, r, name, cmd)
|
||||
out, ok := a.issueLuckPermsCommand(w, r, name, cmd)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -393,7 +417,7 @@ func (a *API) handleAccessGroup(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
out, ok := a.issueAccessCommand(w, r, name, "lp user "+body.Player+" parent "+body.Action+" "+body.Group)
|
||||
out, ok := a.issueLuckPermsCommand(w, r, name, "lp user "+body.Player+" parent "+body.Action+" "+body.Group)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
@@ -435,7 +459,7 @@ func (a *API) handleAccessLuckPermsInfo(w http.ResponseWriter, r *http.Request)
|
||||
return
|
||||
}
|
||||
|
||||
out, ok := a.issueAccessCommand(w, r, name, "lp user "+player+" permission info")
|
||||
out, ok := a.issueLuckPermsCommand(w, r, name, "lp user "+player+" permission info")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -597,3 +597,51 @@ func TestParseBanlistOutput(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestLuckPermsMissingIsAConflict: on a server without LuckPerms every lp command
|
||||
// is answered by the server's unknown-command reply, and each LuckPerms door must
|
||||
// say so (409 luckperms_missing) with no audit of a change that never happened (#4).
|
||||
// The replies are what the servers send: Paper 1.21's captured live from a paper
|
||||
// server over RCON, and a Spigot one colored the way its console renders it. The
|
||||
// empty reply LuckPerms itself gives (it answers after RCON has flushed) and an
|
||||
// LP message that mentions "unknown command" mid-line both stay successes.
|
||||
func TestLuckPermsMissingIsAConflict(t *testing.T) {
|
||||
doors := []struct{ name, method, path, body string }{
|
||||
{"permission", "POST", "/api/v1/servers/survival/access/permission", `{"action":"set","player":"Steve","node":"essentials.fly"}`},
|
||||
{"group", "POST", "/api/v1/servers/survival/access/group", `{"action":"add","player":"Steve","group":"vip"}`},
|
||||
{"info", "GET", "/api/v1/servers/survival/access/luckperms/Steve", ""},
|
||||
}
|
||||
replies := []struct {
|
||||
name string
|
||||
reply string
|
||||
missing bool
|
||||
}{
|
||||
{"paper", "Unknown or incomplete command. See below for error\nlp user Steve permission info<--[HERE]", true},
|
||||
{"older vanilla", "Unknown or incomplete command, see below for error\nlp user Steve<--[HERE]", true},
|
||||
{"spigot colored", "§fUnknown command. Type \"/help\" for help.", true},
|
||||
{"luckperms silent", "", false},
|
||||
{"luckperms message", "§7[§b§lL§3§lP§7]§r §7Another command is being executed; unknown command queue", false},
|
||||
}
|
||||
for _, d := range doors {
|
||||
for _, rp := range replies {
|
||||
t.Run(d.name+"/"+rp.name, func(t *testing.T) {
|
||||
api, repo, _, console := mkAccess(t)
|
||||
api.External = staticExternal{p: accessOwner}
|
||||
console.reply = rp.reply
|
||||
w := do(api.ExternalHandler(), d.method, d.path, d.body, nil)
|
||||
if !rp.missing {
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("code = %d (%s), want 200", w.Code, w.Body.String())
|
||||
}
|
||||
return
|
||||
}
|
||||
if w.Code != http.StatusConflict || decodeErr(t, w) != "luckperms_missing" {
|
||||
t.Fatalf("code = %d (%s), want 409 luckperms_missing", w.Code, w.Body.String())
|
||||
}
|
||||
if len(repo.audits) != 0 {
|
||||
t.Fatalf("audited a change LuckPerms never made: %+v", repo.audits)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user