Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions pkg/appstore/appstore_bag.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
37 changes: 30 additions & 7 deletions pkg/appstore/appstore_bag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down Expand Up @@ -192,7 +215,7 @@ func validBagResult() bagResult {

func validSAPConfig() SAPConfig {
return SAPConfig{
AuthEndpoint: testAuthEndpoint,
AuthEndpoint: testAuthEndpoint + "/",
SetupURL: testSAPSetupEndpoint,
CertificateURL: testSAPSetupCertEndpoint,
Version: 200,
Expand Down
5 changes: 3 additions & 2 deletions pkg/appstore/appstore_login_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand All @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
Loading