From e2ead355a8e9f43605281d2e0ed64e44c0ace4bb Mon Sep 17 00:00:00 2001 From: Adam Borbas Date: Fri, 26 Feb 2021 09:17:49 +0100 Subject: [PATCH] Use app specific password from Apple ID connection (#73) * Update appleauth package * Update step.yml * Cover no app-specific password provided for Apple ID connection case * Update step.yml * Remove unused auth-test-apple-id-connection-without-app-specific-password --- bitrise.yml | 3 +- go.mod | 2 +- go.sum | 4 + step.yml | 7 +- .../go-steputils/input/fileprovider.go | 89 +++++++++++++++++++ .../bitrise-io/go-steputils/input/input.go | 75 ++++++++++++++++ .../bitrise-step-export-universal-apk/LICENSE | 21 +++++ .../filedownloader/filedownloader.go | 77 ++++++++++++++++ .../appleauth/auth_source.go | 15 +++- .../appleauth/key_helper.go | 45 ++-------- .../devportalservice/devportalservice.go | 64 ++++++++++--- vendor/modules.txt | 5 +- 12 files changed, 350 insertions(+), 57 deletions(-) create mode 100644 vendor/github.com/bitrise-io/go-steputils/input/fileprovider.go create mode 100644 vendor/github.com/bitrise-io/go-steputils/input/input.go create mode 100644 vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/LICENSE create mode 100644 vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader/filedownloader.go diff --git a/bitrise.yml b/bitrise.yml index eb45ea2..c6215e2 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -86,7 +86,6 @@ workflows: - work_dir: ./ - verbose_log: "yes" - connection: apple_id - - app_password: $APP_SPECIFIC_PASSWORD auth-test-apple-id-connection-globally-set-app-specific-password: envs: @@ -101,7 +100,7 @@ workflows: - verbose_log: "yes" - connection: apple_id - app_password: "" - + auth-test-api-key-connection: before_run: - _auth_prepare diff --git a/go.mod b/go.mod index ec215ad..ea64adb 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/bitrise-io/go-utils v0.0.0-20201211082830-859032e9adf0 github.com/bitrise-io/stepman v0.0.0-20190813144014-10564a4888a6 // indirect github.com/bitrise-steplib/bitrise-step-android-unit-test v0.0.0-20190902203028-ff8e682d8645 - github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209134909-4d779ddbe073 + github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210225084122-4a4d9384c633 github.com/google/go-cmp v0.5.4 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/kr/pretty v0.1.0 // indirect diff --git a/go.sum b/go.sum index 264d6d7..653d2bf 100644 --- a/go.sum +++ b/go.sum @@ -14,12 +14,16 @@ github.com/bitrise-io/stepman v0.0.0-20190813144014-10564a4888a6 h1:/GnB2kEaO/6K github.com/bitrise-io/stepman v0.0.0-20190813144014-10564a4888a6/go.mod h1:hGCjd8leP411yt5QkQi+VBNWGxIZ4H02LNIVeKBeMUk= github.com/bitrise-steplib/bitrise-step-android-unit-test v0.0.0-20190902203028-ff8e682d8645 h1:9molXzIAxnKStwV78lt7MSgUQwxIWl4+r9/oYTQA7no= github.com/bitrise-steplib/bitrise-step-android-unit-test v0.0.0-20190902203028-ff8e682d8645/go.mod h1:0yqqJw+MqwsfHKq4pL90IoSYskLF91oDCyyZYnIehWA= +github.com/bitrise-steplib/bitrise-step-export-universal-apk v0.0.0-20200729103519-a582681d23d6 h1:7UWHsApY8/iIGw1jVbDiL3FMnI70Y/t0BxG9nQupHhI= +github.com/bitrise-steplib/bitrise-step-export-universal-apk v0.0.0-20200729103519-a582681d23d6/go.mod h1:WBGpmu+FXynGkpED0re/InExsBaRG7Y2tMabxnFgSuA= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210204114640-c91fdc7c90cd h1:ieyOJ5xYu0nxVf9dpyRCg3RgS/Uhc3hfNBI6RUWms/Q= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210204114640-c91fdc7c90cd/go.mod h1:mG5kKjSyK3sZNp7e5QpFBAtxJRWeA+4PSMh3ZfwggNs= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209130532-8386805c9389 h1:NQfjDuwNE/LhjTkMV7+PmRE/c+KFPxXJTZeW6TfEptY= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209130532-8386805c9389/go.mod h1:mG5kKjSyK3sZNp7e5QpFBAtxJRWeA+4PSMh3ZfwggNs= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209134909-4d779ddbe073 h1:348wxtooA7rjIe0pXLJwVPwddrURmq+6hCWcfE6hACQ= github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209134909-4d779ddbe073/go.mod h1:mG5kKjSyK3sZNp7e5QpFBAtxJRWeA+4PSMh3ZfwggNs= +github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210225084122-4a4d9384c633 h1:6Rw2tRuYzY5baiyAWDhX+psWihsOCZRX2TqtfHzq8TU= +github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210225084122-4a4d9384c633/go.mod h1:+XuCsvAc2xcQpdVnCmQC4Ej+jMc0n60oTtg0oz/BkC4= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= diff --git a/step.yml b/step.yml index 7e97420..e0fbf24 100644 --- a/step.yml +++ b/step.yml @@ -68,7 +68,7 @@ inputs: There are two types of Apple Developer connection you can enable on Bitrise: one is based on an API key of the App Store Connect API, the other is the session-based authentication with an Apple ID. You can choose which type of Bitrise Apple Developer connection to use or you can tell the Step to only use the Step inputs for authentication: - `automatic`: Use any enabled Apple Developer connection, either based on Apple ID authentication or API key authentication. Step inputs are only used as a fallback. API key authentication has priority over Apple ID authentication in both cases. - `api_key`: Use the Apple Developer connection based on API key authentication. Authentication-related Step inputs are ignored. - - `apple_id`: Use the Apple Developer connection based on Apple ID authentication and **Application-specific password** Step input. Other authentication-related Step inputs are ignored. + - `apple_id`: Use the Apple Developer connection based on Apple ID authentication and the **Application-specific password** Step input. Other authentication-related Step inputs are ignored. - `off`: Do not use any already configured Apple Developer Connection. Only authentication-related Step inputs are considered. is_required: true value_options: @@ -108,9 +108,10 @@ inputs: - app_password: "" opts: title: "Apple ID: Application-specific password" - summary: Required if using Apple ID + summary: Optional if using two-factor enabled Apple ID. description: |- - An application-specific password for the Apple ID. + Use this input if TFA is enabled on the Apple ID but no app-specific password has been added to the used Bitrise Apple ID connection. + **NOTE:** Application-specific passwords can be created on the [AppleID Website](https://appleid.apple.com). It can be used to bypass two-factor authentication. diff --git a/vendor/github.com/bitrise-io/go-steputils/input/fileprovider.go b/vendor/github.com/bitrise-io/go-steputils/input/fileprovider.go new file mode 100644 index 0000000..b8528e6 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-steputils/input/fileprovider.go @@ -0,0 +1,89 @@ +package input + +import ( + "net/url" + "path" + "path/filepath" + "strings" + + "github.com/bitrise-io/go-utils/pathutil" +) + +const ( + fileSchema = "file://" +) + +// FileDownloader .. +type FileDownloader interface { + Get(destination, source string) error +} + +// FileProvider supports retrieving the local path to a file either provided +// as a local path using `file://` scheme +// or downloading the file to a temporary location and return the path to it. +type FileProvider struct { + filedownloader FileDownloader +} + +// NewFileProvider ... +func NewFileProvider(filedownloader FileDownloader) FileProvider { + return FileProvider{ + filedownloader: filedownloader, + } +} + +// LocalPath ... +func (fileProvider FileProvider) LocalPath(path string) (string, error) { + + var localPath string + if strings.HasPrefix(path, fileSchema) { + trimmedPath, err := fileProvider.trimmedFilePath(path) + if err != nil { + return "", err + } + localPath = trimmedPath + } else { + downloadedPath, err := fileProvider.downloadFile(path) + if err != nil { + return "", err + } + localPath = downloadedPath + } + + return localPath, nil +} + +// Removes file:// from the begining of the path +func (fileProvider FileProvider) trimmedFilePath(path string) (string, error) { + pth := strings.TrimPrefix(path, fileSchema) + return pathutil.AbsPath(pth) +} + +func (fileProvider FileProvider) downloadFile(url string) (string, error) { + tmpDir, err := pathutil.NormalizedOSTempDirPath("FileProviderprovider") + if err != nil { + return "", err + } + + fileName, err := fileProvider.fileNameFromPathURL(url) + if err != nil { + return "", err + } + localPath := path.Join(tmpDir, fileName) + if err := fileProvider.filedownloader.Get(localPath, url); err != nil { + return "", err + } + + return localPath, nil +} + +// Returns the file's name from a URL that starts with +// `http://` or `https://` +func (fileProvider FileProvider) fileNameFromPathURL(urlPath string) (string, error) { + url, err := url.Parse(urlPath) + if err != nil { + return "", err + } + + return filepath.Base(url.Path), nil +} diff --git a/vendor/github.com/bitrise-io/go-steputils/input/input.go b/vendor/github.com/bitrise-io/go-steputils/input/input.go new file mode 100644 index 0000000..9cf4a29 --- /dev/null +++ b/vendor/github.com/bitrise-io/go-steputils/input/input.go @@ -0,0 +1,75 @@ +package input + +import ( + "fmt" + "strconv" + + "github.com/bitrise-io/go-utils/pathutil" +) + +// ValidateIfNotEmpty ... +func ValidateIfNotEmpty(input string) error { + if input == "" { + return fmt.Errorf("parameter not specified") + } + return nil +} + +// ValidateWithOptions ... +func ValidateWithOptions(value string, options ...string) error { + if err := ValidateIfNotEmpty(value); err != nil { + return err + } + for _, option := range options { + if option == value { + return nil + } + } + return fmt.Errorf("invalid parameter: %s, available: %v", value, options) +} + +// ValidateIfPathExists ... +func ValidateIfPathExists(input string) error { + if err := ValidateIfNotEmpty(input); err != nil { + return err + } + if exist, err := pathutil.IsPathExists(input); err != nil { + return fmt.Errorf("failed to check if path exist at: %s, error: %s", input, err) + } else if !exist { + return fmt.Errorf("path not exist at: %s", input) + } + return nil +} + +// ValidateIfDirExists ... +func ValidateIfDirExists(input string) error { + if err := ValidateIfNotEmpty(input); err != nil { + return err + } + if exist, err := pathutil.IsDirExists(input); err != nil { + return fmt.Errorf("failed to check if dir exist at: %s, error: %s", input, err) + } else if !exist { + return fmt.Errorf("dir not exist at: %s", input) + } + return nil +} + +// ValidateInt ... +func ValidateInt(input string) (int, error) { + if input == "" { + return 0, nil + } + num, err := strconv.Atoi(input) + if err != nil { + return 0, fmt.Errorf("can't convert to int, error: %v", err) + } + return num, nil +} + +// SecureInput ... +func SecureInput(input string) string { + if input != "" { + return "***" + } + return "" +} diff --git a/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/LICENSE b/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/LICENSE new file mode 100644 index 0000000..6cecbe9 --- /dev/null +++ b/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2020 bitrise + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader/filedownloader.go b/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader/filedownloader.go new file mode 100644 index 0000000..ef8f3fd --- /dev/null +++ b/vendor/github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader/filedownloader.go @@ -0,0 +1,77 @@ +package filedownloader + +import ( + "fmt" + "io" + "net/http" + "os" + + "github.com/bitrise-io/go-utils/log" +) + +// HTTPClient ... +type HTTPClient interface { + Get(source string) (*http.Response, error) +} + +// HTTPFileDownloader ... +type HTTPFileDownloader struct { + client HTTPClient +} + +// New ... +func New(client HTTPClient) HTTPFileDownloader { + return HTTPFileDownloader{client} +} + +// GetWithFallback downloads a file from a given source. Provided destination should be a file that does not exist. +// You can specify fallback sources which will be used in order if downloading fails from either source. +func (downloader HTTPFileDownloader) GetWithFallback(destination, source string, fallbackSources ...string) error { + sources := append([]string{source}, fallbackSources...) + for _, source := range sources { + err := downloader.Get(destination, source) + if err != nil { + log.Errorf("Could not download file from: %s", err) + } else { + log.Infof("URL used to download file: %s", source) + return nil + } + } + return fmt.Errorf("None of the sources returned 200 OK status") +} + +// Get downloads a file from a given source. Provided destination should be a file that does not exist. +func (downloader HTTPFileDownloader) Get(destination, source string) error { + + resp, err := downloader.client.Get(source) + if err != nil { + return err + } + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("unable to download file from: %s. Status code: %d", source, resp.StatusCode) + } + + defer func() { + if err := resp.Body.Close(); err != nil { + log.Errorf("Failed to close body, error: %s", err) + } + }() + + f, err := os.Create(destination) + if err != nil { + return err + } + + defer func() { + if err := f.Close(); err != nil { + log.Errorf("Failed to close file, error: %s", err) + } + }() + + if _, err = io.Copy(f, resp.Body); err != nil { + return err + } + + return nil +} diff --git a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/auth_source.go b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/auth_source.go index 9280609..c2d9524 100644 --- a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/auth_source.go +++ b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/auth_source.go @@ -95,7 +95,7 @@ func (*ConnectionAppleIDSource) Fetch(conn *devportalservice.AppleDeveloperConne Username: conn.AppleIDConnection.AppleID, Password: conn.AppleIDConnection.Password, Session: "", - AppSpecificPassword: inputs.AppSpecificPassword, + AppSpecificPassword: appSpecificPasswordFavouringConnection(conn.AppleIDConnection, inputs.AppSpecificPassword), }, }, nil } @@ -149,7 +149,7 @@ func (*ConnectionAppleIDFastlaneSource) Fetch(conn *devportalservice.AppleDevelo Username: conn.AppleIDConnection.AppleID, Password: conn.AppleIDConnection.Password, Session: session, - AppSpecificPassword: inputs.AppSpecificPassword, + AppSpecificPassword: appSpecificPasswordFavouringConnection(conn.AppleIDConnection, inputs.AppSpecificPassword), }, }, nil } @@ -175,3 +175,14 @@ func (*InputAppleIDFastlaneSource) Fetch(conn *devportalservice.AppleDeveloperCo }, }, nil } + +func appSpecificPasswordFavouringConnection(conn *devportalservice.AppleIDConnection, passwordFromInput string) string { + appSpecificPassword := passwordFromInput + + // AppSpecifcPassword from the connection overwrites the one from the input + if conn != nil && conn.AppSpecificPassword != "" { + appSpecificPassword = conn.AppSpecificPassword + } + + return appSpecificPassword +} diff --git a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/key_helper.go b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/key_helper.go index 4d7e9c2..97d0c6a 100644 --- a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/key_helper.go +++ b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth/key_helper.go @@ -1,14 +1,14 @@ package appleauth import ( - "fmt" "io/ioutil" "net/http" "net/url" "path/filepath" "regexp" - "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-steputils/input" + "github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader" ) func fetchPrivateKey(privateKeyURL string) ([]byte, string, error) { @@ -17,46 +17,19 @@ func fetchPrivateKey(privateKeyURL string) ([]byte, string, error) { return nil, "", err } - key, err := copyOrDownloadFile(fileURL) + // Download or load local file + filedownloader := filedownloader.New(http.DefaultClient) + fileProvider := input.NewFileProvider(filedownloader) + localFile, err := fileProvider.LocalPath(fileURL.String()) if err != nil { return nil, "", err } - - return key, getKeyID(fileURL), nil -} - -func copyOrDownloadFile(u *url.URL) ([]byte, error) { - // if file -> copy - if u.Scheme == "file" { - b, err := ioutil.ReadFile(u.Path) - if err != nil { - return nil, err - } - - return b, err - } - - // otherwise download - resp, err := http.Get(u.String()) + key, err := ioutil.ReadFile(localFile) if err != nil { - return nil, err - } - defer func() { - if err := resp.Body.Close(); err != nil { - log.Errorf("Failed to close file: %s", err) - } - }() - - if resp.StatusCode < 200 || resp.StatusCode >= 300 { - return nil, fmt.Errorf("request failed with status %d", resp.StatusCode) - } - - contentBytes, err := ioutil.ReadAll(resp.Body) - if err != nil { - return nil, fmt.Errorf("failed to read response body: %s", err) + return nil, "", err } - return contentBytes, nil + return key, getKeyID(fileURL), nil } func getKeyID(u *url.URL) string { diff --git a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/devportalservice/devportalservice.go b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/devportalservice/devportalservice.go index 3a9603c..5d0d77f 100644 --- a/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/devportalservice/devportalservice.go +++ b/vendor/github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/devportalservice/devportalservice.go @@ -6,6 +6,7 @@ import ( "fmt" "io/ioutil" "net/http" + "regexp" "strings" "text/template" "time" @@ -93,10 +94,13 @@ func (c *BitriseClient) GetAppleDeveloperConnection() (*AppleDeveloperConnection d.APIKeyConnection.PrivateKey = privateKeyWithHeader(d.APIKeyConnection.PrivateKey) } + testDevices, duplicatedDevices := validateTestDevice(d.TestDevices) + return &AppleDeveloperConnection{ - AppleIDConnection: d.AppleIDConnection, - APIKeyConnection: d.APIKeyConnection, - TestDevices: d.TestDevices, + AppleIDConnection: d.AppleIDConnection, + APIKeyConnection: d.APIKeyConnection, + TestDevices: testDevices, + DuplicatedTestDevices: duplicatedDevices, }, nil } @@ -147,10 +151,11 @@ type cookie struct { // AppleIDConnection represents a Bitrise.io Apple ID-based Apple Developer connection. type AppleIDConnection struct { - AppleID string `json:"apple_id"` - Password string `json:"password"` - SessionExpiryDate *time.Time `json:"connection_expiry_date"` - SessionCookies map[string][]cookie `json:"session_cookies"` + AppleID string `json:"apple_id"` + Password string `json:"password"` + AppSpecificPassword string `json:"app_specific_password"` + SessionExpiryDate *time.Time `json:"connection_expiry_date"` + SessionCookies map[string][]cookie `json:"session_cookies"` } // APIKeyConnection represents a Bitrise.io API key-based Apple Developer connection. @@ -162,8 +167,9 @@ type APIKeyConnection struct { // TestDevice ... type TestDevice struct { - ID int `json:"id"` - UserID int `json:"user_id"` + ID int `json:"id"` + UserID int `json:"user_id"` + // DeviceID is the Apple device UDID DeviceID string `json:"device_identifier"` Title string `json:"title"` CreatedAt time.Time `json:"created_at"` @@ -171,12 +177,17 @@ type TestDevice struct { DeviceType string `json:"device_type"` } +// IsEqualUDID compares two UDIDs (stored in the DeviceID field of TestDevice) +func IsEqualUDID(UDID string, otherUDID string) bool { + return normalizeDeviceUDID(UDID) == normalizeDeviceUDID(otherUDID) +} + // AppleDeveloperConnection represents a Bitrise.io Apple Developer connection. // https://devcenter.bitrise.io/getting-started/configuring-bitrise-steps-that-require-apple-developer-account-data/ type AppleDeveloperConnection struct { - AppleIDConnection *AppleIDConnection - APIKeyConnection *APIKeyConnection - TestDevices []TestDevice `json:"test_devices"` + AppleIDConnection *AppleIDConnection + APIKeyConnection *APIKeyConnection + TestDevices, DuplicatedTestDevices []TestDevice } // FastlaneLoginSession returns the Apple ID login session in a ruby/object:HTTP::Cookie format. @@ -214,3 +225,32 @@ func (c *AppleIDConnection) FastlaneLoginSession() (string, error) { } return strings.Join(rubyCookies, ""), nil } + +func validDeviceUDID(udid string) string { + r := regexp.MustCompile("[^a-zA-Z0-9-]") + return r.ReplaceAllLiteralString(udid, "") +} + +func normalizeDeviceUDID(udid string) string { + return strings.ToLower(strings.ReplaceAll(validDeviceUDID(udid), "-", "")) +} + +// validateTestDevice filters out duplicated devices +// it does not change UDID casing or remove '-' separator, only to filter out whitespace or unsupported characters +func validateTestDevice(deviceList []TestDevice) (validDevices, duplicatedDevices []TestDevice) { + bitriseDevices := make(map[string]bool) + for _, device := range deviceList { + normalizedID := normalizeDeviceUDID(device.DeviceID) + if _, ok := bitriseDevices[normalizedID]; ok { + duplicatedDevices = append(duplicatedDevices, device) + + continue + } + + bitriseDevices[normalizedID] = true + device.DeviceID = validDeviceUDID(device.DeviceID) + validDevices = append(validDevices, device) + } + + return validDevices, duplicatedDevices +} diff --git a/vendor/modules.txt b/vendor/modules.txt index e5f9d27..54734f9 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -11,6 +11,7 @@ github.com/bitrise-io/bitrise-init/utility github.com/bitrise-io/envman/models # github.com/bitrise-io/go-steputils v0.0.0-20201016102104-03ae3a6ded35 github.com/bitrise-io/go-steputils/cache +github.com/bitrise-io/go-steputils/input github.com/bitrise-io/go-steputils/stepconf github.com/bitrise-io/go-steputils/tools # github.com/bitrise-io/go-utils v0.0.0-20201211082830-859032e9adf0 @@ -29,7 +30,9 @@ github.com/bitrise-io/go-utils/sliceutil github.com/bitrise-io/stepman/models # github.com/bitrise-steplib/bitrise-step-android-unit-test v0.0.0-20190902203028-ff8e682d8645 github.com/bitrise-steplib/bitrise-step-android-unit-test/cache -# github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210209134909-4d779ddbe073 +# github.com/bitrise-steplib/bitrise-step-export-universal-apk v0.0.0-20200729103519-a582681d23d6 +github.com/bitrise-steplib/bitrise-step-export-universal-apk/filedownloader +# github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver v0.0.0-20210225084122-4a4d9384c633 github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/appleauth github.com/bitrise-steplib/steps-deploy-to-itunesconnect-deliver/devportalservice # github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51