From b19e62c1b650e664d39e7cdd863d65a0f38a3278 Mon Sep 17 00:00:00 2001 From: Majd Alfhaily Date: Mon, 5 Oct 2026 23:31:42 +0200 Subject: [PATCH] fix: normalize the initial authentication url path --- pkg/appstore/appstore_bag.go | 10 ++++++++ pkg/appstore/appstore_bag_test.go | 37 +++++++++++++++++++++++------ pkg/appstore/appstore_login_test.go | 5 ++-- 3 files changed, 43 insertions(+), 9 deletions(-) diff --git a/pkg/appstore/appstore_bag.go b/pkg/appstore/appstore_bag.go index d71f285d..54a81151 100644 --- a/pkg/appstore/appstore_bag.go +++ b/pkg/appstore/appstore_bag.go @@ -54,6 +54,16 @@ func (t *appstore) bag(guid string) (BagOutput, error) { return BagOutput{}, err } + authURL, err := url.Parse(config.AuthEndpoint) + if err != nil { + return BagOutput{}, fmt.Errorf("failed to parse authentication endpoint: %w", err) + } + + // The bare path can return unusable responses, including a 301 without + // Location. Normalize only the validated bag URL, retaining its host and query. + authURL.Path = PrivateAppStoreAPIPathAuth + "/" + config.AuthEndpoint = authURL.String() + return BagOutput{AuthEndpoint: config.AuthEndpoint, SAPConfig: config}, nil } diff --git a/pkg/appstore/appstore_bag_test.go b/pkg/appstore/appstore_bag_test.go index a00adc56..59865da6 100644 --- a/pkg/appstore/appstore_bag_test.go +++ b/pkg/appstore/appstore_bag_test.go @@ -33,19 +33,42 @@ var _ = Describe("AppStore (Bag)", func() { ctrl.Finish() }) - DescribeTable("preserves supported authentication URLs from the bag", func(endpoint string) { + DescribeTable("normalizes the authentication path while preserving the bag host and query", func(endpoint, expected string) { mockMachine.EXPECT().MacAddress().Return("00:11:22:33:44:55", nil) result := validBagResult() result.URLBag.AuthEndpoint = endpoint mockBagClient.EXPECT().Send(gomock.Any()).Return(http.Result[bagResult]{StatusCode: gohttp.StatusOK, Data: result}, nil) out, err := as.Bag(BagInput{}) Expect(err).NotTo(HaveOccurred()) - Expect(out.AuthEndpoint).To(Equal(endpoint)) - Expect(out.SAPConfig.AuthEndpoint).To(Equal(endpoint)) + Expect(out.AuthEndpoint).To(Equal(expected)) + Expect(out.SAPConfig.AuthEndpoint).To(Equal(expected)) }, - Entry("bare path", testAuthEndpoint), - Entry("trailing slash", testAuthEndpoint+"/"), - Entry("pod URL with routing query", "https://p7-buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth+"/?routing=opaque"), + Entry("bare path", testAuthEndpoint, testAuthEndpoint+"/"), + Entry("trailing slash", testAuthEndpoint+"/", testAuthEndpoint+"/"), + Entry("bare path with routing query", testAuthEndpoint+"?Pod=7&routing=a%2Fb+c", testAuthEndpoint+"/?Pod=7&routing=a%2Fb+c"), + Entry("bare pod URL with routing query", "https://p7-buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth+"?Pod=7&PRH=7", "https://p7-buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth+"/?Pod=7&PRH=7"), + Entry("slashed pod URL with routing query", "https://p7-buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth+"/?routing=opaque", "https://p7-buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth+"/?routing=opaque"), + Entry("explicit HTTPS port", "https://buy.itunes.apple.com:443"+PrivateAppStoreAPIPathAuth, "https://buy.itunes.apple.com:443"+PrivateAppStoreAPIPathAuth+"/"), + ) + + DescribeTable("rejects invalid authentication URLs before normalizing the path", func(endpoint string) { + mockMachine.EXPECT().MacAddress().Return("00:11:22:33:44:55", nil) + result := validBagResult() + result.URLBag.AuthEndpoint = endpoint + mockBagClient.EXPECT().Send(gomock.Any()).Return(http.Result[bagResult]{StatusCode: gohttp.StatusOK, Data: result}, nil) + _, err := as.Bag(BagInput{}) + Expect(err).To(HaveOccurred()) + }, + Entry("double slash", testAuthEndpoint+"//"), + Entry("extra path", testAuthEndpoint+"/extra"), + Entry("encoded slash", testAuthEndpoint+"%2f"), + Entry("different path", "https://buy.itunes.apple.com/other"), + Entry("foreign host", "https://example.com"+PrivateAppStoreAPIPathAuth), + Entry("HTTP", "http://buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth), + Entry("userinfo", "https://user@buy.itunes.apple.com"+PrivateAppStoreAPIPathAuth), + Entry("unsupported port", "https://buy.itunes.apple.com:8443"+PrivateAppStoreAPIPathAuth), + Entry("fragment", testAuthEndpoint+"#fragment"), + Entry("malformed URL", testAuthEndpoint+"%zz"), ) When("fails to read machine MAC address", func() { @@ -192,7 +215,7 @@ func validBagResult() bagResult { func validSAPConfig() SAPConfig { return SAPConfig{ - AuthEndpoint: testAuthEndpoint, + AuthEndpoint: testAuthEndpoint + "/", SetupURL: testSAPSetupEndpoint, CertificateURL: testSAPSetupCertEndpoint, Version: 200, diff --git a/pkg/appstore/appstore_login_test.go b/pkg/appstore/appstore_login_test.go index ac2281fa..35c8d8d7 100644 --- a/pkg/appstore/appstore_login_test.go +++ b/pkg/appstore/appstore_login_test.go @@ -164,6 +164,7 @@ var _ = Describe("AppStore (Login)", func() { DescribeTable("normalizes 2FA codes without changing the password", func(code, suffix string) { const password = " \tpäss word\n" mockClient.EXPECT().Send(gomock.Any()).DoAndReturn(func(req http.Request) (http.Result[loginResult], error) { + Expect(req.URL).To(Equal(testAuthEndpoint + "/")) Expect(req.Payload.(*http.XMLPayload).Content).To(HaveKeyWithValue("password", password+suffix)) return http.Result[loginResult]{}, errors.New("test complete") @@ -189,7 +190,7 @@ var _ = Describe("AppStore (Login)", func() { mockClient.EXPECT(). Send(gomock.Any()). Do(func(req http.Request) { - Expect(req.URL).To(Equal(testAuthEndpoint)) + Expect(req.URL).To(Equal(testAuthEndpoint + "/")) Expect(req.ActionSigner).To(BeIdenticalTo(signer)) }). Return(http.Result[loginResult]{}, clientErr) @@ -328,7 +329,7 @@ var _ = Describe("AppStore (Login)", func() { firstCall := mockClient.EXPECT(). Send(gomock.Any()). Do(func(req http.Request) { - Expect(req.URL).To(Equal(testAuthEndpoint)) + Expect(req.URL).To(Equal(testAuthEndpoint + "/")) Expect(req.ActionSigner).To(BeIdenticalTo(signer)) Expect(req.Payload).To(BeAssignableToTypeOf(&http.XMLPayload{})) x := req.Payload.(*http.XMLPayload)