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().