Class CloudAuthManager
/ulticloud login|logout|status.
As of plan 16-09 (D-17) this class owns no state of its own and exposes no generation-shaped or
TokenEntity-shaped public surface -- everything that used to be a fine-grained public
static (the generation triple, saveToken/clearToken/refreshToken/
loadSavedToken, the start/stop pairs for polling and refresh) is gone outright, not
deprecated: CloudSession instance methods now carry that behaviour, reached only through
login(Runnable, Consumer, Runnable, Consumer, Consumer), logout() and
status() below. Keeping a delegating shim for any of them would leave the generation
convention callable, which is exactly what issue #298's second acceptance criterion requires to
stop being true (see plan 16-08's CloudSession javadoc for the mechanism this replaced).
CredentialStaticSurfaceInvariantTest enforces this structurally: no public static method
anywhere in this package may accept or return a TokenEntity or a generation (D-18), so a
static bypass of the session's credential commit cannot be reintroduced unnoticed. As of 6.3.1 the
6.3.0 user token is neither stored nor sent (plan 22-24 removed its store); the server credential is
committed only through CloudSession.commitCredential(ServerCredential).
Marked ApiStatus.Internal -- this class exists to serve CloudLoginCommand's three
@CmdMapping methods and is not part of the framework's public API surface for module
authors.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final classAn immutable view ofstatus()'s result.static interfaceThe outcomes oflogin(DeviceLoginListener).static enumCompleted unlink outcomes; none contains a credential.static final classImmutable result of a manual reconnect request; an accepted attempt runs asynchronously. -
Field Summary
FieldsModifier and TypeFieldDescription(package private) static final StringThe message a login reports when an overlapping login already replaced its session.(package private) static final intThe longestsuggestedNameUltiCloud accepts, in code points (contract answer 13).private static final long -
Constructor Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprivate static boolean(package private) static ServerCredentiallinkedCredential(CloudSession session) The current credential for status, login and logout.static voidlogin(CloudAuthManager.DeviceLoginListener listener) Links this server to UltiCloud with the RFC 8628 device authorization grant (protocol v2).static voidlogin(Runnable onAlreadyLoggedIn, Consumer<Long> onRateLimited, Runnable onRequesting, Consumer<String> onSuccess, Consumer<String> onError) Deprecated, for removal: This API element is subject to removal in a future version.static booleanlogout()Tears down cloud features unconditionally, then asynchronously revokes and deletes the protocol v2 credential.static voidStops the captured session synchronously, then revokes and deletes its credential on a Bukkit asynchronous task.(package private) static StringnormaliseSuggestedName(String motd) Requests an immediate connection attempt without waiting for the network.(package private) static Stringstatic CloudAuthManager.CloudStatusstatus()(package private) static String(package private) static String
-
Field Details
-
MAX_SUGGESTED_NAME_CODE_POINTS
static final int MAX_SUGGESTED_NAME_CODE_POINTSThe longestsuggestedNameUltiCloud accepts, in code points (contract answer 13).- See Also:
-
LOGIN_IN_PROGRESS
The message a login reports when an overlapping login already replaced its session.- See Also:
-
RECONNECT_COOLDOWN_MS
private static final long RECONNECT_COOLDOWN_MS- See Also:
-
-
Constructor Details
-
CloudAuthManager
private CloudAuthManager()
-
-
Method Details
-
login
Links this server to UltiCloud with the RFC 8628 device authorization grant (protocol v2).Runs the already-linked check (a credential held by the current session or stored in
credentials.json), the rate-limit check, then replaces the current session and returns. The device-code request and the polling run on the new session's own poll executor, so a caller on the server's primary thread is never blocked on the network; their outcome reacheslisteneron that executor's thread.The lock structure is the one the 6.3.0 magic-link login used, unchanged:
CloudSession.class, then the captured session's monitor, the re-check, thesessionAtCheck != CloudSession.current()guard,disableCloud(sessionAtCheck),startNew()-- the documented lock order ofCloudSession's class javadoc.- Parameters:
listener- receives every outcome; must not benull- Since:
- 6.3.1
-
linkedCredential
The current credential for status, login and logout. The session is its one owner (plan 22-21 fix): every change to the stored entry -- device login, rotation, logout, rejection -- updates the session's copy in the same step, so the two never diverge. The file is read only when the session holds no credential (it has not adopted one yet, for example while cloud features are off at startup), so logout still revokes and deletes a stored credential the running session never loaded.- Returns:
- the credential
sessionholds, else the one stored incredentials.json, elsenull
-
runningFrameworkVersion
- Returns:
- the running framework version, sent verbatim (a
-SNAPSHOTsuffix included): the plugin description's version, elseenv.yml's, elseunknown
-
suggestedServerName
- Returns:
- the server's MOTD as plain text, at most 64 code
points, or
nullwhen there is no server or the MOTD is blank (the field is then omitted; the owner names the server on the confirmation page either way)
-
normaliseSuggestedName
- Parameters:
motd- a legacy-formatted MOTD, ornull- Returns:
- the MOTD without colour codes, with every run of whitespace or control characters
collapsed to one space, trimmed and cut to 64
code points without splitting a surrogate pair;
nullwhen nothing remains
-
storedLegacyServerUuid
- Returns:
- this server's stored install id (
uuidincredentials.json) normalised byLegacyServerId.canonical(String), ornull; reads only, never writes
-
login
@Deprecated(since="6.3.1", forRemoval=true) public static void login(Runnable onAlreadyLoggedIn, Consumer<Long> onRateLimited, Runnable onRequesting, Consumer<String> onSuccess, Consumer<String> onError) Deprecated, for removal: This API element is subject to removal in a future version.Uselogin(DeviceLoginListener)instead; removed in 6.4.0.The 6.3.0 callback form oflogin(DeviceLoginListener), kept with its signature for compatibility. It now runs the device authorization:onSuccessreceives the verification URI with the code filled in (verificationUriComplete); the later outcome (linked, denied, expired) is reported only in the server log.- Parameters:
onAlreadyLoggedIn- invoked, with nothing else attempted, if this server is already linkedonRateLimited- invoked with the remaining cooldown in seconds if the login attempt is currently rate-limitedonRequesting- invoked right before the device-code request is madeonSuccess- invoked with the complete verification URI once a code was issuedonError- invoked with an error message if the request fails- Scheduled removal version:
- 6.4.0
-
logout
Tears down cloud features unconditionally, then asynchronously revokes and deletes the protocol v2 credential. The revoke and delete finish after this method returns; uselogoutReporting(Consumer)to observe their outcome. In both forms teardown must run even when there is nothing to clear (an expired-but-still-connected session is the case that most needs logout to take effect), and the credential is read only after teardown so an in-flight login that commits mid-teardown is still caught and cleared -- seeCloudLoginCommand.logout(CommandSender)'s own comments for why that order matters.CR-01 (16-REVIEW-cloud.md): this method captures
CloudSession.current()exactly once -- in the statement below, immediately before teardown begins -- and passes that captured reference toPluginInitiationUtils.disableCloud(CloudSession), which acts on and returns that exact instance. The revoke-and-delete decision then acts on the returned reference, never on a second, independent call toCloudSession.current()taken after teardown returns. Readingcurrent()a second time, afterward, used to be exactly the bug: a concurrentlogin()(unsynchronized, and reachable from a different thread via@RunAsync) can install a brand-new session withCloudSession.startNew()in the gap while teardown is running, and that second read would then see the NEW session's (alwaysnull) credential instead of the one actually being logged out of -- concluding "nothing to clear" and leaving the real credential on disk. Acting on the one reference this method already holds makes that race structurally impossible: there is no second read left to disagree with the first. This method deliberately does not callCloudSession.startNew()itself any more either; clearing the torn-down session in place is sufficient, and the next real login installs a fresh session via its ownCloudSession.startNew()call, exactly as it always has.Round-1 external review finding: the paragraph above establishes WHICH session this method acts on, but not what its delete is safe to remove. A concurrent
login()can write a fresh credential to the same shared document while this session's teardown is in flight, so the delete is a compare-and-delete keyed on the retiring credential's id (ServerCredentialStore.deleteIfMatches(String)): it never removes a credential the torn-down session did not hold. Alogin()that installs its new session after this method's single read ofCloudSession.current()has lost the session race regardless --CloudSession.startNew()invalidates whatever session it replaces.- Returns:
trueif a credential existed and cleanup was started, or a pending authentication attempt was cancelled (real-machine UAT fix, plan 16-08 -- seeCloudSession.hasAnythingToClear());falseif there was nothing to clear and nothing pending (teardown still ran regardless)- Throws:
IOException- never as of 6.3.1 (a failed local delete is reported by the asynchronous task); kept in the 6.3.0 signature
-
logoutReporting
Stops the captured session synchronously, then revokes and deletes its credential on a Bukkit asynchronous task. The callback runs on that task, or inline when no credential is held. A local storage failure is propagated or reported by the scheduler, never reported as deletion.- Parameters:
onDone- receives the completed outcome- Since:
- 6.3.1
-
beginLogout
-
reconnect
Requests an immediate connection attempt without waiting for the network. The ten-second cooldown applies to every invocation, including one refused by the current connection state.- Returns:
- whether an attempt was queued, or why the request was refused
- Since:
- 6.3.1
-
status
- Returns:
- an immutable snapshot of the current session's connection state, for
/ulticloud statusto render -- never the token itself (D-18)
-
login(DeviceLoginListener)instead; removed in 6.4.0.