Compare commits

...

2 Commits
v5 ... master

Author SHA1 Message Date
ErickSkrauch
cfe8fea3f7 Add signature to the custom profile property when ?unsigned=false 2024-07-09 18:37:47 +02:00
ErickSkrauch
27c7b79b32 Added onUnknownProfileRespondWithUuid param to the /profile endpoint
Introducing profiles endpoint was a mistake, but we had to deal with that mistake until I'll remove it. The Accounts service needs textures with a signature. But it is possible that a user has a fresh account and Chrly has not yet received the profile information. In this case we have no way to get textures for the player. Adding the onUnknownProfileRespondWithUuid parameter solves this problem. This is a bad solution and nobody should use it except Ely.by infrastructure. In v5 version the texture signature on Chrly will be removed.
2024-06-11 02:31:47 +02:00
5 changed files with 148 additions and 46 deletions

View File

@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed
- Adjusted Mojang usernames filter to be stickier according to their docs
- `/profile/{username}` endpoint now returns the correct signature for the custom property as well.
### Changed
- Bumped Go version to 1.21.

View File

@@ -243,11 +243,12 @@ Response example:
"properties": [
{
"name": "textures",
"signature": "signature value",
"signature": "textures signature value",
"value": "base64 encoded value"
},
{
"name": "chrly",
"signature": "custom property signature value",
"value": "how do you tame a horse in Minecraft?"
}
]

View File

@@ -106,19 +106,24 @@ func newSkinsystemHandler(
capesRepository CapesRepository,
mojangTexturesProvider MojangTexturesProvider,
texturesSigner TexturesSigner,
) *mux.Router {
) (*mux.Router, error) {
config.SetDefault("textures.extra_param_name", "chrly")
config.SetDefault("textures.extra_param_value", "how do you tame a horse in Minecraft?")
return (&Skinsystem{
Emitter: emitter,
SkinsRepo: skinsRepository,
CapesRepo: capesRepository,
MojangTexturesProvider: mojangTexturesProvider,
TexturesSigner: texturesSigner,
TexturesExtraParamName: config.GetString("textures.extra_param_name"),
TexturesExtraParamValue: config.GetString("textures.extra_param_value"),
}).Handler()
app, err := NewSkinsystem(
emitter,
skinsRepository,
capesRepository,
mojangTexturesProvider,
texturesSigner,
config.GetString("textures.extra_param_name"),
config.GetString("textures.extra_param_value"),
)
if err != nil {
return nil, err
}
return app.Handler(), nil
}
func newApiHandler(skinsRepository SkinsRepository) *mux.Router {

View File

@@ -6,6 +6,7 @@ import (
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"io"
"net/http"
"strings"
@@ -43,12 +44,39 @@ type TexturesSigner interface {
type Skinsystem struct {
Emitter
SkinsRepo SkinsRepository
CapesRepo CapesRepository
MojangTexturesProvider MojangTexturesProvider
TexturesSigner TexturesSigner
TexturesExtraParamName string
TexturesExtraParamValue string
SkinsRepo SkinsRepository
CapesRepo CapesRepository
MojangTexturesProvider MojangTexturesProvider
TexturesSigner TexturesSigner
TexturesExtraParamName string
TexturesExtraParamValue string
texturesExtraParamSignature string
}
func NewSkinsystem(
emitter Emitter,
skinsRepo SkinsRepository,
capesRepo CapesRepository,
mojangTexturesProvider MojangTexturesProvider,
texturesSigner TexturesSigner,
texturesExtraParamName string,
texturesExtraParamValue string,
) (*Skinsystem, error) {
texturesExtraParamSignature, err := texturesSigner.SignTextures(texturesExtraParamValue)
if err != nil {
return nil, fmt.Errorf("unable to generate signature for textures extra param: %w", err)
}
return &Skinsystem{
Emitter: emitter,
SkinsRepo: skinsRepo,
CapesRepo: capesRepo,
MojangTexturesProvider: mojangTexturesProvider,
TexturesSigner: texturesSigner,
TexturesExtraParamName: texturesExtraParamName,
TexturesExtraParamValue: texturesExtraParamValue,
texturesExtraParamSignature: texturesExtraParamSignature,
}, nil
}
type profile struct {
@@ -190,8 +218,15 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
}
if profile == nil {
response.WriteHeader(http.StatusNoContent)
return
forceResponseWithUuid := request.URL.Query().Get("onUnknownProfileRespondWithUuid")
if forceResponseWithUuid == "" {
response.WriteHeader(http.StatusNoContent)
return
}
profile = createEmptyProfile()
profile.Id = formatUuid(forceResponseWithUuid)
profile.Username = parseUsername(mux.Vars(request)["username"])
}
texturesPropContent := &mojang.TexturesProp{
@@ -208,14 +243,20 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
Name: "textures",
Value: texturesPropEncodedValue,
}
customProp := &mojang.Property{
Name: ctx.TexturesExtraParamName,
Value: ctx.TexturesExtraParamValue,
}
if request.URL.Query().Get("unsigned") == "false" {
signature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
customProp.Signature = ctx.texturesExtraParamSignature
texturesSignature, err := ctx.TexturesSigner.SignTextures(texturesProp.Value)
if err != nil {
panic(err)
}
texturesProp.Signature = signature
texturesProp.Signature = texturesSignature
}
profileResponse := &mojang.SignedTexturesResponse{
@@ -223,10 +264,7 @@ func (ctx *Skinsystem) profileHandler(response http.ResponseWriter, request *htt
Name: profile.Username,
Props: []*mojang.Property{
texturesProp,
{
Name: ctx.TexturesExtraParamName,
Value: ctx.TexturesExtraParamValue,
},
customProp,
},
}
@@ -274,12 +312,10 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
return nil, err
}
profile := &profile{
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
}
profile := createEmptyProfile()
if skin != nil {
profile.Id = strings.Replace(skin.Uuid, "-", "", -1)
profile.Id = formatUuid(skin.Uuid)
profile.Username = skin.Username
}
@@ -354,6 +390,16 @@ func (ctx *Skinsystem) getProfile(request *http.Request, proxy bool) (*profile,
return profile, nil
}
func createEmptyProfile() *profile {
return &profile{
Textures: &mojang.TexturesResponse{}, // Field must be initialized to avoid "null" after json encoding
}
}
func formatUuid(uuid string) string {
return strings.Replace(uuid, "-", "", -1)
}
func parseUsername(username string) string {
return strings.TrimSuffix(username, ".png")
}

View File

@@ -11,6 +11,7 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"strings"
"testing"
"time"
@@ -141,15 +142,17 @@ func (suite *skinsystemTestSuite) SetupTest() {
suite.TexturesSigner = &texturesSignerMock{}
suite.Emitter = &emitterMock{}
suite.App = &Skinsystem{
SkinsRepo: suite.SkinsRepository,
CapesRepo: suite.CapesRepository,
MojangTexturesProvider: suite.MojangTexturesProvider,
TexturesSigner: suite.TexturesSigner,
Emitter: suite.Emitter,
TexturesExtraParamName: "texturesParamName",
TexturesExtraParamValue: "texturesParamValue",
}
suite.TexturesSigner.On("SignTextures", "texturesParamValue").Times(1).Return("texturesParamSignature", nil)
suite.App, _ = NewSkinsystem(
suite.Emitter,
suite.SkinsRepository,
suite.CapesRepository,
suite.MojangTexturesProvider,
suite.TexturesSigner,
"texturesParamName",
"texturesParamValue",
)
}
func (suite *skinsystemTestSuite) TearDownTest() {
@@ -742,11 +745,12 @@ func (suite *skinsystemTestSuite) TestSignedTextures() {
***************************/
type profileTestCase struct {
Name string
Signed bool
BeforeTest func(suite *skinsystemTestSuite)
PanicErr string
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
Name string
Signed bool
ForceResponse string
BeforeTest func(suite *skinsystemTestSuite)
PanicErr string
AfterTest func(suite *skinsystemTestSuite, response *http.Response)
}
var profileTestsCases = []*profileTestCase{
@@ -799,6 +803,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -828,6 +833,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -857,6 +863,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -890,6 +897,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -923,6 +931,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -952,6 +961,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -981,6 +991,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -1010,6 +1021,7 @@ var profileTestsCases = []*profileTestCase{
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
@@ -1028,6 +1040,37 @@ var profileTestsCases = []*profileTestCase{
suite.Equal("", string(body))
},
},
{
Name: "Username not exists and Mojang profile unavailable, but there is a forceResponse param",
ForceResponse: "a12e41a4-e8e5-4503-987e-0adacf72ab93",
Signed: true,
BeforeTest: func(suite *skinsystemTestSuite) {
suite.SkinsRepository.On("FindSkinByUsername", "mock_username").Return(nil, nil)
suite.MojangTexturesProvider.On("GetForUsername", "mock_username").Once().Return(nil, nil)
suite.TexturesSigner.On("SignTextures", mock.Anything).Return("chrly signature", nil)
},
AfterTest: func(suite *skinsystemTestSuite, response *http.Response) {
suite.Equal(200, response.StatusCode)
suite.Equal("application/json", response.Header.Get("Content-Type"))
body, _ := ioutil.ReadAll(response.Body)
suite.JSONEq(`{
"id": "a12e41a4e8e54503987e0adacf72ab93",
"name": "mock_username",
"properties": [
{
"name": "textures",
"signature": "chrly signature",
"value": "eyJ0aW1lc3RhbXAiOjE2MTQyMTQyMjMwMDAsInByb2ZpbGVJZCI6ImExMmU0MWE0ZThlNTQ1MDM5ODdlMGFkYWNmNzJhYjkzIiwicHJvZmlsZU5hbWUiOiJtb2NrX3VzZXJuYW1lIiwidGV4dHVyZXMiOnt9fQ=="
},
{
"name": "texturesParamName",
"signature": "texturesParamSignature",
"value": "texturesParamValue"
}
]
}`, string(body))
},
},
{
Name: "Username not exists and Mojang textures proxy returned an error",
BeforeTest: func(suite *skinsystemTestSuite) {
@@ -1060,12 +1103,18 @@ func (suite *skinsystemTestSuite) TestProfile() {
suite.RunSubTest(testCase.Name, func() {
testCase.BeforeTest(suite)
url := "http://chrly/profile/mock_username"
u, _ := url.Parse("http://chrly/profile/mock_username")
q := make(url.Values)
if testCase.Signed {
url += "?unsigned=false"
q.Set("unsigned", "false")
}
req := httptest.NewRequest("GET", url, nil)
if testCase.ForceResponse != "" {
q.Set("onUnknownProfileRespondWithUuid", testCase.ForceResponse)
}
u.RawQuery = q.Encode()
req := httptest.NewRequest("GET", u.String(), nil)
w := httptest.NewRecorder()
if testCase.PanicErr != "" {