test(nano): cover the bar-list error, bad identity id and ip relay
Three paths in handleHasJoined had no test that fails when they break: - A bar-list lookup error answers 500. Logging it and carrying on would admit a reclaimed squatter during a database outage. - An identity (Mojang) id that does not parse answers 204. Ignoring the parse error would emit the nil UUID for every such login, so they all share one player's data. - The ip parameter is relayed to each source. Dropping it turns off the sources' check that the session is used from the player's own address. One subtest each. Mutants that ignore the bar-list error, ignore the id parse error, or stop appending ip each fail their subtest.
This commit is contained in:
1 file changed
+47
@@ -5,6 +5,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net"
|
"net"
|
||||||
@@ -75,6 +76,13 @@ func fakeYgg(t *testing.T, id, name string) *httptest.Server {
|
|||||||
return srv
|
return srv
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// blacklistDownRepo is a store whose bar list cannot be read.
|
||||||
|
type blacklistDownRepo struct{ *fakeRepo }
|
||||||
|
|
||||||
|
func (blacklistDownRepo) IsUsernameBlacklisted(context.Context, string) (bool, error) {
|
||||||
|
return false, errors.New("bar list unreachable")
|
||||||
|
}
|
||||||
|
|
||||||
func getHasJoined(h http.Handler, username, serverID string) *httptest.ResponseRecorder {
|
func getHasJoined(h http.Handler, username, serverID string) *httptest.ResponseRecorder {
|
||||||
return do(h, "GET", "/session/minecraft/hasJoined?username="+username+"&serverId="+serverID, "", nil)
|
return do(h, "GET", "/session/minecraft/hasJoined?username="+username+"&serverId="+serverID, "", nil)
|
||||||
}
|
}
|
||||||
@@ -323,6 +331,45 @@ func TestHasJoined(t *testing.T) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
// With the bar list unreadable, nobody can say the player is not barred; the login must
|
||||||
|
// not go through. (Velocity reports the 500 as the auth servers being down.)
|
||||||
|
t.Run("bar list lookup error -> not admitted", func(t *testing.T) {
|
||||||
|
mojang := fakeYgg(t, notchMojangID, "Notch")
|
||||||
|
api := newTestAPI(blacklistDownRepo{newFakeRepo()}, newFakeCluster())
|
||||||
|
api.AuthSources = []AuthSource{{Tag: "mojang", URL: mojang.URL, Identity: true}}
|
||||||
|
if w := getHasJoined(api.InternalHandler(), "Notch", "abc"); w.Code == http.StatusOK {
|
||||||
|
t.Fatalf("admitted with the bar list unreadable (%q)", w.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Mojang is trusted for its UUIDs, which is exactly why one that does not parse must not
|
||||||
|
// be emitted as some default: every such login would share the nil UUID.
|
||||||
|
t.Run("identity source with an unparseable id -> 204", func(t *testing.T) {
|
||||||
|
mojang := fakeYgg(t, "not-a-uuid", "Notch")
|
||||||
|
api := newTestAPI(newFakeRepo(), newFakeCluster())
|
||||||
|
api.AuthSources = []AuthSource{{Tag: "mojang", URL: mojang.URL, Identity: true}}
|
||||||
|
if w := getHasJoined(api.InternalHandler(), "Notch", "abc"); w.Code != http.StatusNoContent {
|
||||||
|
t.Fatalf("code = %d, want 204 (%q)", w.Code, w.Body.String())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// The player's address is what lets a source refuse a session relayed from another IP
|
||||||
|
// (prevent-proxy-connections); it has to reach the source unchanged.
|
||||||
|
t.Run("ip is forwarded to the source", func(t *testing.T) {
|
||||||
|
got := make(chan string, 1)
|
||||||
|
src := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
got <- r.URL.Query().Get("ip")
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
}))
|
||||||
|
t.Cleanup(src.Close)
|
||||||
|
api := newTestAPI(newFakeRepo(), newFakeCluster())
|
||||||
|
api.AuthSources = []AuthSource{{Tag: "mojang", URL: src.URL, Identity: true}}
|
||||||
|
do(api.InternalHandler(), "GET", "/session/minecraft/hasJoined?username=Notch&serverId=abc&ip=203.0.113.9", "", nil)
|
||||||
|
if ip := <-got; ip != "203.0.113.9" {
|
||||||
|
t.Fatalf("source saw ip %q, want 203.0.113.9", ip)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// A GET that declares a body it never sends must still be answered and lose its
|
// A GET that declares a body it never sends must still be answered and lose its
|
||||||
// connection; otherwise each such socket stays open for as long as the client likes.
|
// connection; otherwise each such socket stays open for as long as the client likes.
|
||||||
t.Run("request declaring a body is refused and closed", func(t *testing.T) {
|
t.Run("request declaring a body is refused and closed", func(t *testing.T) {
|
||||||
|
|||||||
Reference in new issue
Block a user