Unverified Commit 201cfc86 authored by Lemon-miaow's avatar Lemon-miaow
Browse files

feat(access): 服务器没装 LuckPerms 时权限与用户组操作返回 409 luckperms_missing,面板说明原因

parent aa6e487f
Loading
Loading
Loading
Loading
+12 −3
Changes for docs/openapi.yaml: 12 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -2249,7 +2249,10 @@ paths:
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Server not running.
          description: >-
            not_running (the server is not running) or luckperms_missing (the
            server answered the lp command as unknown: LuckPerms is not installed,
            and nothing changed).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
@@ -2293,7 +2296,10 @@ paths:
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Server not running.
          description: >-
            not_running (the server is not running) or luckperms_missing (the
            server answered the lp command as unknown: LuckPerms is not installed,
            and nothing changed).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
@@ -2349,7 +2355,10 @@ paths:
        '404':
          $ref: '#/components/responses/NotFound'
        '409':
          description: Server not running.
          description: >-
            not_running (the server is not running) or luckperms_missing (the
            server answered the lp command as unknown: LuckPerms is not installed,
            and nothing changed).
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }
+27 −3
Changes for internal/api/handlers_access.go: 27 added lines, 3 removed lines.
Original line number Diff line number Diff line
@@ -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
	}
+48 −0
Changes for internal/api/handlers_access_test.go: 48 added lines, 0 removed lines.
Original line number Diff line number Diff line
@@ -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)
				}
			})
		}
	}
}
+1 −0
Changes for panel/src/i18n/resources/en-US/errors.json: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
  "cooldown": "Wake is cooling down — try again shortly.",
  "not_running": "The server isn't running — wake it before managing access.",
  "console_unavailable": "Can't reach the server console right now — try again shortly.",
  "luckperms_missing": "LuckPerms isn't installed on this server, so permission and group changes have no effect. For the lobby, ask the operator to re-run the installer; any other server needs the LuckPerms plugin added first.",
  "no_backup": "There's no restorable backup for this server yet.",
  "backup_corrupt": "This backup failed its read-back check and can't be restored intact — pick another backup.",
  "not_stopped": "Stop the server completely before restoring — a restore overwrites the live world volume.",
+1 −0
Changes for panel/src/i18n/resources/zh-CN/errors.json: 1 added line, 0 removed lines.
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@
  "cooldown": "启动冷却中——请稍后再试。",
  "not_running": "服务器未在运行——请先启动它再管理访问权限。",
  "console_unavailable": "暂时无法连接服务器控制台,请稍后重试。",
  "luckperms_missing": "这台服务器没有装 LuckPerms,权限和用户组的改动不会生效。大厅请让运维重跑安装脚本来补上;其他服务器需要先装 LuckPerms 插件。",
  "no_backup": "这台服务器暂时没有可回档的备份。",
  "backup_corrupt": "这份备份回读校验未通过,已无法完整恢复——请选择另一份备份。",
  "not_stopped": "回档会覆盖世界的实时存储卷,请先把服务器完全停止再回档。",
Loading