Make Mojang profiles provider cancellable

This commit is contained in:
ErickSkrauch
2024-02-07 01:36:18 +01:00
parent 10c11bc060
commit bc4d714112
12 changed files with 320 additions and 204 deletions

View File

@@ -1,6 +1,7 @@
package http
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
@@ -20,7 +21,7 @@ import (
var timeNow = time.Now
type ProfilesProvider interface {
FindProfileByUsername(username string, allowProxy bool) (*db.Profile, error)
FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error)
}
type TexturesSigner interface {
@@ -55,7 +56,7 @@ func (ctx *Skinsystem) Handler() *mux.Router {
}
func (ctx *Skinsystem) skinHandler(response http.ResponseWriter, request *http.Request) {
profile, err := ctx.ProfilesProvider.FindProfileByUsername(parseUsername(mux.Vars(request)["username"]), true)
profile, err := ctx.ProfilesProvider.FindProfileByUsername(request.Context(), parseUsername(mux.Vars(request)["username"]), true)
if err != nil {
apiServerError(response, "Unable to retrieve a skin", err)
return
@@ -82,7 +83,7 @@ func (ctx *Skinsystem) skinGetHandler(response http.ResponseWriter, request *htt
}
func (ctx *Skinsystem) capeHandler(response http.ResponseWriter, request *http.Request) {
profile, err := ctx.ProfilesProvider.FindProfileByUsername(parseUsername(mux.Vars(request)["username"]), true)
profile, err := ctx.ProfilesProvider.FindProfileByUsername(request.Context(), parseUsername(mux.Vars(request)["username"]), true)
if err != nil {
apiServerError(response, "Unable to retrieve a cape", err)
return
@@ -109,7 +110,7 @@ func (ctx *Skinsystem) capeGetHandler(response http.ResponseWriter, request *htt
}
func (ctx *Skinsystem) texturesHandler(response http.ResponseWriter, request *http.Request) {
profile, err := ctx.ProfilesProvider.FindProfileByUsername(mux.Vars(request)["username"], true)
profile, err := ctx.ProfilesProvider.FindProfileByUsername(request.Context(), mux.Vars(request)["username"], true)
if err != nil {
apiServerError(response, "Unable to retrieve a profile", err)
return
@@ -134,6 +135,7 @@ func (ctx *Skinsystem) texturesHandler(response http.ResponseWriter, request *ht
func (ctx *Skinsystem) signedTexturesHandler(response http.ResponseWriter, request *http.Request) {
profile, err := ctx.ProfilesProvider.FindProfileByUsername(
request.Context(),
mux.Vars(request)["username"],
getToBool(request.URL.Query().Get("proxy")),
)
@@ -174,7 +176,7 @@ func (ctx *Skinsystem) signedTexturesHandler(response http.ResponseWriter, reque
}
func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *http.Request) {
profile, err := ctx.ProfilesProvider.FindProfileByUsername(mux.Vars(request)["username"], true)
profile, err := ctx.ProfilesProvider.FindProfileByUsername(request.Context(), mux.Vars(request)["username"], true)
if err != nil {
apiServerError(response, "Unable to retrieve a profile", err)
return

View File

@@ -1,6 +1,7 @@
package http
import (
"context"
"crypto/rsa"
"crypto/x509"
"encoding/pem"
@@ -23,8 +24,8 @@ type ProfilesProviderMock struct {
mock.Mock
}
func (m *ProfilesProviderMock) FindProfileByUsername(username string, allowProxy bool) (*db.Profile, error) {
args := m.Called(username, allowProxy)
func (m *ProfilesProviderMock) FindProfileByUsername(ctx context.Context, username string, allowProxy bool) (*db.Profile, error) {
args := m.Called(ctx, username, allowProxy)
var result *db.Profile
if casted, ok := args.Get(0).(*db.Profile); ok {
result = casted
@@ -90,12 +91,14 @@ func (t *SkinsystemTestSuite) TearDownSubTest() {
func (t *SkinsystemTestSuite) TestSkinHandler() {
for _, url := range []string{"http://chrly/skins/mock_username", "http://chrly/skins?name=mock_username"} {
t.Run("known username with a skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
}, nil)
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
// TODO: see the TODO about context above
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -104,10 +107,11 @@ func (t *SkinsystemTestSuite) TestSkinHandler() {
})
t.Run("known username without a skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{}, nil)
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -115,10 +119,11 @@ func (t *SkinsystemTestSuite) TestSkinHandler() {
})
t.Run("err from profiles provider", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, errors.New("mock error"))
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -127,12 +132,13 @@ func (t *SkinsystemTestSuite) TestSkinHandler() {
}
t.Run("username with png extension", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/skins/mock_username.png", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -154,12 +160,16 @@ func (t *SkinsystemTestSuite) TestSkinHandler() {
func (t *SkinsystemTestSuite) TestCapeHandler() {
for _, url := range []string{"http://chrly/cloaks/mock_username", "http://chrly/cloaks?name=mock_username"} {
t.Run("known username with a skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
// TODO: I can't find a way to verify that it's the context from the request that was passed in,
// as the Mux calls WithValue() on it, which creates a new Context and I haven't been able
// to find a way to verify that the passed context matches the base
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -168,10 +178,11 @@ func (t *SkinsystemTestSuite) TestCapeHandler() {
})
t.Run("known username without a skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{}, nil)
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -179,10 +190,11 @@ func (t *SkinsystemTestSuite) TestCapeHandler() {
})
t.Run("err from profiles provider", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, errors.New("mock error"))
req := httptest.NewRequest("GET", url, nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -191,12 +203,13 @@ func (t *SkinsystemTestSuite) TestCapeHandler() {
}
t.Run("username with png extension", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/cloaks/mock_username.png", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -217,12 +230,14 @@ func (t *SkinsystemTestSuite) TestCapeHandler() {
func (t *SkinsystemTestSuite) TestTexturesHandler() {
t.Run("known username with both textures", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
// TODO: see the TODO about context above
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
CapeUrl: "https://example.com/cape.png",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.App.Handler().ServeHTTP(w, req)
@@ -241,12 +256,13 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
t.Run("known username with only slim skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
SkinUrl: "https://example.com/skin.png",
SkinModel: "slim",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.App.Handler().ServeHTTP(w, req)
@@ -263,12 +279,13 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
t.Run("known username with only cape", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
CapeUrl: "https://example.com/cape.png",
}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -281,10 +298,11 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
t.Run("known username without any textures", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -294,10 +312,11 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
t.Run("unknown username", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -307,10 +326,11 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
t.Run("err from profiles provider", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, errors.New("mock error"))
req := httptest.NewRequest("GET", "http://chrly/textures/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -318,24 +338,18 @@ func (t *SkinsystemTestSuite) TestTexturesHandler() {
})
}
type signedTexturesTestCase struct {
Name string
AllowProxy bool
BeforeTest func(suite *SkinsystemTestSuite)
PanicErr string
AfterTest func(suite *SkinsystemTestSuite, response *http.Response)
}
func (t *SkinsystemTestSuite) TestSignedTextures() {
t.Run("exists profile with mojang textures", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", false).Return(&db.Profile{
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username", nil)
w := httptest.NewRecorder()
// TODO: see the TODO about context above
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", false).Return(&db.Profile{
Uuid: "mock-uuid",
Username: "mock",
MojangTextures: "mock-mojang-textures",
MojangSignature: "mock-mojang-signature",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username", nil)
w := httptest.NewRecorder()
t.App.Handler().ServeHTTP(w, req)
@@ -361,10 +375,11 @@ func (t *SkinsystemTestSuite) TestSignedTextures() {
})
t.Run("exists profile without mojang textures", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", false).Return(&db.Profile{}, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", false).Return(&db.Profile{}, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -374,10 +389,11 @@ func (t *SkinsystemTestSuite) TestSignedTextures() {
})
t.Run("not exists profile", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", false).Return(nil, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", false).Return(nil, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -387,10 +403,11 @@ func (t *SkinsystemTestSuite) TestSignedTextures() {
})
t.Run("err from profiles provider", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", false).Return(nil, errors.New("mock error"))
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", false).Return(nil, errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -398,25 +415,28 @@ func (t *SkinsystemTestSuite) TestSignedTextures() {
})
t.Run("should allow proxying when specified get param", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, nil)
req := httptest.NewRequest("GET", "http://chrly/textures/signed/mock_username?proxy=true", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, nil)
t.App.Handler().ServeHTTP(w, req)
})
}
func (t *SkinsystemTestSuite) TestProfile() {
t.Run("exists profile with skin and cape", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username", nil)
w := httptest.NewRecorder()
// TODO: see the TODO about context above
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
Uuid: "mock-uuid",
Username: "mock_username",
SkinUrl: "https://example.com/skin.png",
SkinModel: "slim",
CapeUrl: "https://example.com/cape.png",
}, nil)
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username", nil)
w := httptest.NewRecorder()
t.App.Handler().ServeHTTP(w, req)
@@ -441,15 +461,16 @@ func (t *SkinsystemTestSuite) TestProfile() {
})
t.Run("exists signed profile with skin", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username?unsigned=false", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{
Uuid: "mock-uuid",
Username: "mock_username",
SkinUrl: "https://example.com/skin.png",
SkinModel: "slim",
}, nil)
t.TexturesSigner.On("SignTextures", "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6Im1vY2stdXVpZCIsInByb2ZpbGVOYW1lIjoibW9ja191c2VybmFtZSIsInRleHR1cmVzIjp7IlNLSU4iOnsidXJsIjoiaHR0cHM6Ly9leGFtcGxlLmNvbS9za2luLnBuZyIsIm1ldGFkYXRhIjp7Im1vZGVsIjoic2xpbSJ9fX19").Return("mock signature", nil)
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username?unsigned=false", nil)
w := httptest.NewRecorder()
t.App.Handler().ServeHTTP(w, req)
@@ -475,10 +496,11 @@ func (t *SkinsystemTestSuite) TestProfile() {
})
t.Run("not exists profile", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, nil)
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, nil)
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -488,10 +510,11 @@ func (t *SkinsystemTestSuite) TestProfile() {
})
t.Run("err from profiles provider", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(nil, errors.New("mock error"))
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(nil, errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()
@@ -499,11 +522,12 @@ func (t *SkinsystemTestSuite) TestProfile() {
})
t.Run("err from textures signer", func() {
t.ProfilesProvider.On("FindProfileByUsername", "mock_username", true).Return(&db.Profile{}, nil)
t.TexturesSigner.On("SignTextures", mock.Anything).Return("", errors.New("mock error"))
req := httptest.NewRequest("GET", "http://chrly/profile/mock_username?unsigned=false", nil)
w := httptest.NewRecorder()
t.ProfilesProvider.On("FindProfileByUsername", mock.Anything, "mock_username", true).Return(&db.Profile{}, nil)
t.TexturesSigner.On("SignTextures", mock.Anything).Return("", errors.New("mock error"))
t.App.Handler().ServeHTTP(w, req)
result := w.Result()