From 457eba3a230035e4cdc331b65f5c900f3d721516 Mon Sep 17 00:00:00 2001 From: tux-mind <1824153+tux-mind@users.noreply.github.com> Date: Thu, 19 Dec 2024 16:52:18 +0100 Subject: [PATCH] Customizable GUID Allow users to specify the GUID to use in HTTP requests. Ipatool takes the GUID from the MAC address of the first suitable network interface. When running inside docker containers, this results in the fixed mac address 02:42:AC:11:00:XX being used. Apple seems to have blacklisted this GUID. I've added a global flag that allows to specify a custom GUID value. --- cmd/common.go | 2 ++ cmd/root.go | 1 + pkg/appstore/appstore.go | 17 +++++++++++++++++ pkg/appstore/appstore_download.go | 6 ++---- pkg/appstore/appstore_download_test.go | 24 ++++++++++++++++++++++++ pkg/appstore/appstore_login.go | 6 ++---- pkg/appstore/appstore_login_test.go | 23 +++++++++++++++++++++++ pkg/appstore/appstore_purchase.go | 7 ++----- pkg/appstore/appstore_purchase_test.go | 23 +++++++++++++++++++++++ 9 files changed, 96 insertions(+), 13 deletions(-) diff --git a/cmd/common.go b/cmd/common.go index 03db1e74..c3f16b58 100644 --- a/cmd/common.go +++ b/cmd/common.go @@ -101,6 +101,7 @@ func initWithCommand(cmd *cobra.Command) { verbose := cmd.Flag("verbose").Value.String() == "true" interactive, _ := cmd.Context().Value("interactive").(bool) format := util.Must(OutputFormatFromString(cmd.Flag("format").Value.String())) + guid := cmd.Flag("guid").Value.String() dependencies.Logger = newLogger(format, verbose) dependencies.OS = operatingsystem.New() @@ -112,6 +113,7 @@ func initWithCommand(cmd *cobra.Command) { OperatingSystem: dependencies.OS, Keychain: dependencies.Keychain, Machine: dependencies.Machine, + Guid: guid, }) util.Must("", createConfigDirectory(dependencies.OS, dependencies.Machine)) diff --git a/cmd/root.go b/cmd/root.go index d8c69143..2981df63 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -40,6 +40,7 @@ func rootCmd() *cobra.Command { cmd.PersistentFlags().BoolVar(&verbose, "verbose", false, "enables verbose logs") cmd.PersistentFlags().BoolVarP(&nonInteractive, "non-interactive", "", false, "run in non-interactive session") cmd.PersistentFlags().StringVar(&keychainPassphrase, "keychain-passphrase", "", "passphrase for unlocking keychain") + cmd.PersistentFlags().String("guid", "", "GUID used in HTTP requests") cmd.AddCommand(authCmd()) cmd.AddCommand(downloadCmd()) diff --git a/pkg/appstore/appstore.go b/pkg/appstore/appstore.go index 1dad936c..f9de2d61 100644 --- a/pkg/appstore/appstore.go +++ b/pkg/appstore/appstore.go @@ -1,6 +1,9 @@ package appstore import ( + "fmt" + "strings" + "github.com/majd/ipatool/v2/pkg/http" "github.com/majd/ipatool/v2/pkg/keychain" "github.com/majd/ipatool/v2/pkg/util/machine" @@ -36,6 +39,7 @@ type appstore struct { httpClient http.Client[interface{}] machine machine.Machine os operatingsystem.OperatingSystem + guid string } type Args struct { @@ -43,6 +47,7 @@ type Args struct { CookieJar http.CookieJar OperatingSystem operatingsystem.OperatingSystem Machine machine.Machine + Guid string } func NewAppStore(args Args) AppStore { @@ -59,5 +64,17 @@ func NewAppStore(args Args) AppStore { httpClient: http.NewClient[interface{}](clientArgs), machine: args.Machine, os: args.OperatingSystem, + guid: args.Guid, + } +} + +func (t *appstore) getGuid() (string, error) { + if t.guid == "" { + if macAddr, err := t.machine.MacAddress(); err != nil { + return "", fmt.Errorf("failed to get mac address: %w", err) + } else { + t.guid = strings.ReplaceAll(strings.ToUpper(macAddr), ":", "") + } } + return t.guid, nil } diff --git a/pkg/appstore/appstore_download.go b/pkg/appstore/appstore_download.go index 9a3d307e..f474afd8 100644 --- a/pkg/appstore/appstore_download.go +++ b/pkg/appstore/appstore_download.go @@ -36,13 +36,11 @@ func (t *appstore) Download(input DownloadInput) (DownloadOutput, error) { return DownloadOutput{}, fmt.Errorf("failed to resolve destination path: %w", err) } - macAddr, err := t.machine.MacAddress() + guid, err := t.getGuid() if err != nil { - return DownloadOutput{}, fmt.Errorf("failed to get mac address: %w", err) + return DownloadOutput{}, fmt.Errorf("failed to get GUID: %w", err) } - guid := strings.ReplaceAll(strings.ToUpper(macAddr), ":", "") - req := t.downloadRequest(input.Account, input.App, guid) res, err := t.downloadClient.Send(req) diff --git a/pkg/appstore/appstore_download_test.go b/pkg/appstore/appstore_download_test.go index 91618264..440d6730 100644 --- a/pkg/appstore/appstore_download_test.go +++ b/pkg/appstore/appstore_download_test.go @@ -88,6 +88,30 @@ var _ = Describe("AppStore (Download)", func() { }) }) + When("user provides GUID", func() { + BeforeEach(func() { + as.(*appstore).guid = "GUID" + mockMachine.EXPECT().MacAddress().Times(0) + mockOS.EXPECT().Getwd().Return("", nil) + mockDownloadClient.EXPECT(). + Send(gomock.Any()). + Do(func(req http.Request) { + Expect(req.Payload).To(BeAssignableToTypeOf(&http.XMLPayload{})) + x := req.Payload.(*http.XMLPayload) + Expect(x.Content).To(HaveKeyWithValue("guid", "GUID")) + }). + Return(http.Result[downloadResult]{}, errors.New("")) + }) + AfterEach(func() { + as.(*appstore).guid = "" + }) + + It("sends the HTTP request with the specified GUID", func() { + _, err := as.Download(DownloadInput{}) + Expect(err).To(HaveOccurred()) + }) + }) + When("request fails", func() { BeforeEach(func() { mockOS.EXPECT(). diff --git a/pkg/appstore/appstore_login.go b/pkg/appstore/appstore_login.go index 642301fc..7afc6fbf 100644 --- a/pkg/appstore/appstore_login.go +++ b/pkg/appstore/appstore_login.go @@ -26,13 +26,11 @@ type LoginOutput struct { } func (t *appstore) Login(input LoginInput) (LoginOutput, error) { - macAddr, err := t.machine.MacAddress() + guid, err := t.getGuid() if err != nil { - return LoginOutput{}, fmt.Errorf("failed to get mac address: %w", err) + return LoginOutput{}, fmt.Errorf("failed to get GUID: %w", err) } - guid := strings.ReplaceAll(strings.ToUpper(macAddr), ":", "") - acc, err := t.login(input.Email, input.Password, input.AuthCode, guid) if err != nil { return LoginOutput{}, err diff --git a/pkg/appstore/appstore_login_test.go b/pkg/appstore/appstore_login_test.go index 1fb7ec71..37afbbae 100644 --- a/pkg/appstore/appstore_login_test.go +++ b/pkg/appstore/appstore_login_test.go @@ -61,6 +61,29 @@ var _ = Describe("AppStore (Login)", func() { }) }) + When("user provides GUID", func() { + BeforeEach(func() { + as.(*appstore).guid = "GUID" + mockMachine.EXPECT().MacAddress().Times(0) + mockClient.EXPECT(). + Send(gomock.Any()). + Do(func(req http.Request) { + Expect(req.Payload).To(BeAssignableToTypeOf(&http.XMLPayload{})) + x := req.Payload.(*http.XMLPayload) + Expect(x.Content).To(HaveKeyWithValue("guid", "GUID")) + }). + Return(http.Result[loginResult]{}, errors.New("")) + }) + AfterEach(func() { + as.(*appstore).guid = "" + }) + + It("sends the HTTP request with the specified GUID", func() { + _, err := as.Login(LoginInput{}) + Expect(err).To(HaveOccurred()) + }) + }) + When("successfully reads machine's MAC address", func() { BeforeEach(func() { mockMachine.EXPECT(). diff --git a/pkg/appstore/appstore_purchase.go b/pkg/appstore/appstore_purchase.go index be3533ce..e23e4988 100644 --- a/pkg/appstore/appstore_purchase.go +++ b/pkg/appstore/appstore_purchase.go @@ -4,7 +4,6 @@ import ( "errors" "fmt" gohttp "net/http" - "strings" "github.com/majd/ipatool/v2/pkg/http" ) @@ -21,13 +20,11 @@ type PurchaseInput struct { } func (t *appstore) Purchase(input PurchaseInput) error { - macAddr, err := t.machine.MacAddress() + guid, err := t.getGuid() if err != nil { - return fmt.Errorf("failed to get mac address: %w", err) + return fmt.Errorf("failed to get GUID: %w", err) } - guid := strings.ReplaceAll(strings.ToUpper(macAddr), ":", "") - if input.App.Price > 0 { return errors.New("purchasing paid apps is not supported") } diff --git a/pkg/appstore/appstore_purchase_test.go b/pkg/appstore/appstore_purchase_test.go index 847758f2..7cb1534a 100644 --- a/pkg/appstore/appstore_purchase_test.go +++ b/pkg/appstore/appstore_purchase_test.go @@ -52,6 +52,29 @@ var _ = Describe("AppStore (Purchase)", func() { }) }) + When("user provides GUID", func() { + BeforeEach(func() { + as.guid = "GUID" + mockMachine.EXPECT().MacAddress().Times(0) + mockPurchaseClient.EXPECT(). + Send(gomock.Any()). + Do(func(req http.Request) { + Expect(req.Payload).To(BeAssignableToTypeOf(&http.XMLPayload{})) + x := req.Payload.(*http.XMLPayload) + Expect(x.Content).To(HaveKeyWithValue("guid", "GUID")) + }). + Return(http.Result[purchaseResult]{}, errors.New("")) + }) + AfterEach(func() { + as.guid = "" + }) + + It("sends the HTTP request with the specified GUID", func() { + err := as.Purchase(PurchaseInput{}) + Expect(err).To(HaveOccurred()) + }) + }) + When("app is paid", func() { BeforeEach(func() { mockMachine.EXPECT().