Both online and offline using the cache.
The rationale behind this change is that in the current state of
affairs, `TestPartiallyMaliciousSet()` fails in a way that cannot be
reconciled without this sort of change.
The test exercises a scenario where the beatmap being imported has an
online ID in the `.osu` file, but its hash does not match the online
hash of the beatmap. This turns out to be a more frequent scenario than
envisioned because of users doing stupid things with manual file editing
rather than reporting issues properly.
The scenario is realistic only because the behaviour of the endpoint
responsible for looking up beatmaps is such that if multiple parameters
are given (e.g. all three of beatmap MD5, online ID, and filename), it
will try the three in succession:
f6b341813b/app/Http/Controllers/BeatmapsController.php (L260-L266)
and the local metadata cache implementation reflected this
implementation.
Because online ID and filename are inherently unreliable in this
scenario due to being directly manipulable by clueless or malicious
users, neither should not be used as a fallback.
After switching `UserLookupCache` to `GET /users/lookup` from `GET
/users`, multiplayer sort of breaks, since the former endpoint does not
return `ruleset_statistics`, which are used in multiplayer to show
users' ranks. Therefore, switch multiplayer to use the appropriate
request type directly.
Regressed at some point.
I don't see much reason not to link the bindable directly with config.
It seems to work as you'd expect. Tested with logout (resets to
"Online") and connection failure (persists).
Closes https://github.com/ppy/osu/issues/29173.
You wouldn't think this would be an actual thing that can happen to us,
but it is. The most important one by far is `MaximumStatistics`; that
is the root cause behind why stuff like spinner ticks or slider tails
wasn't showing.
On a better day we should probably do cleanup to unify these models
better, but today is not that day.
This is the first half of a change that *may* fix
https://github.com/ppy/osu/issues/26338 (it definitely fixes *one case*
where the issue happens, but I'm not sure if it will cover all of them).
As described in the issue thread, using the `jti` claim from the JWT
used for authorisation seemed like a decent idea. However, upon closer
inspection the scheme falls over badly in a specific scenario where:
1. A client instance connects to spectator server using JWT A.
2. At some point, JWT A expires, and is silently rotated by the game in
exchange for JWT B.
The spectator server knows nothing of this, and continues to only
track JWT A, including the old `jti` claim in said JWT.
3. At some later point, the client's connection to one of the spectator
server hubs drops out. A reconnection is automatically attempted,
*but* it is attempted using JWT B.
The spectator server was not aware of JWT B until now, and said JWT
has a different `jti` claim than the old one, so to the spectator
server, it looks like a completely different client connecting, which
boots the user out of their account.
This PR adds a per-session GUID which is sent in a HTTP header on every
connection attempt to spectator server. This GUID will be used instead
of the `jti` claim in JWTs as a persistent identifier of a single user's
single lazer session, which bypasses the failure scenario described
above.
I don't think any stronger primitive than this is required. As far as I
can tell this is as strong a protection as the JWT was (which is to say,
not *very* strong), and doing this removes a lot of weird complexity
that would be otherwise incurred by attempting to have client ferry all
of its newly issued JWTs to the server so that it can be aware of them.
At this point its primary usage is the daily challenge event feed, but
the leaderboard will be using this too shortly.
Because the playlists results screen that exists in `master` is
hard-coupled to showing the *local user's* best result on a given
playlist by way of hard-coupling itself to the relevant API request,
allowing show of *arbitrary* score by ID requires a whole bunch of
subclassery as things stand. Oh well.
Class naming is... best effort, due to the above.