From 4b7e407c758fe44109bf9dffd5647bccf5cffc48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Thu, 29 Jan 2026 11:04:31 +0100 Subject: [PATCH 1/8] profileutil v2 init --- _integration_tests/zip/ipa_reader_test.go | 2 +- artifacts/ipa_reader.go | 2 +- autocodesign/autocodesign.go | 2 +- .../localcodesignasset_test.go | 2 +- .../mocks/ProvisioningProfileConverter.go | 27 +- .../mocks/ProvisioningProfileProvider.go | 2 +- autocodesign/localcodesignasset/profile.go | 2 +- .../localcodesignasset/profileconverter.go | 2 +- .../localcodesignasset/profilelookup.go | 2 +- .../localcodesignasset/profilelookup_test.go | 2 +- .../localcodesignasset/profileprovider.go | 2 +- .../profiledownloader/profiledownloader.go | 2 +- autocodesign/profiles.go | 2 +- codesign/codesign.go | 6 +- codesign/codesign_test.go | 2 +- .../codesign_group_provider.go | 7 +- .../exportoptionsgenerator.go | 2 +- .../exportoptionsgenerator_test.go | 2 +- exportoptionsgenerator/profiles.go | 2 +- profileutil/capabilities.go | 66 +++ profileutil/info_model.go | 254 ++++++++++++ profileutil/info_model_test.go | 203 ++++++++++ profileutil/plist_data.go | 171 ++++++++ profileutil/plist_data_test.go | 375 ++++++++++++++++++ profileutil/util.go | 110 +++++ profileutil/v1_adapter.go | 69 ++++ xcarchive/ios.go | 2 +- xcarchive/ios_test.go | 2 +- xcarchive/macos.go | 2 +- 29 files changed, 1298 insertions(+), 28 deletions(-) create mode 100644 profileutil/capabilities.go create mode 100644 profileutil/info_model.go create mode 100644 profileutil/info_model_test.go create mode 100644 profileutil/plist_data.go create mode 100644 profileutil/plist_data_test.go create mode 100644 profileutil/util.go create mode 100644 profileutil/v1_adapter.go diff --git a/_integration_tests/zip/ipa_reader_test.go b/_integration_tests/zip/ipa_reader_test.go index fa25c8cd..b584d165 100644 --- a/_integration_tests/zip/ipa_reader_test.go +++ b/_integration_tests/zip/ipa_reader_test.go @@ -6,11 +6,11 @@ import ( "testing" "github.com/bitrise-io/go-utils/v2/log" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/_integration_tests" "github.com/bitrise-io/go-xcode/v2/artifacts" internalzip "github.com/bitrise-io/go-xcode/v2/internal/zip" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/v2/zip" "github.com/stretchr/testify/require" ) diff --git a/artifacts/ipa_reader.go b/artifacts/ipa_reader.go index df25abbe..0185327f 100644 --- a/artifacts/ipa_reader.go +++ b/artifacts/ipa_reader.go @@ -3,8 +3,8 @@ package artifacts import ( "fmt" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // IPAReader ... diff --git a/autocodesign/autocodesign.go b/autocodesign/autocodesign.go index 12546aa4..62ae2339 100644 --- a/autocodesign/autocodesign.go +++ b/autocodesign/autocodesign.go @@ -11,9 +11,9 @@ import ( "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-xcode/certificateutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect" "github.com/bitrise-io/go-xcode/v2/devportalservice" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/xcodeproject/serialized" ) diff --git a/autocodesign/localcodesignasset/localcodesignasset_test.go b/autocodesign/localcodesignasset/localcodesignasset_test.go index c017955e..6775587c 100644 --- a/autocodesign/localcodesignasset/localcodesignasset_test.go +++ b/autocodesign/localcodesignasset/localcodesignasset_test.go @@ -8,10 +8,10 @@ import ( "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect" "github.com/bitrise-io/go-xcode/v2/autocodesign/localcodesignasset/mocks" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" ) diff --git a/autocodesign/localcodesignasset/mocks/ProvisioningProfileConverter.go b/autocodesign/localcodesignasset/mocks/ProvisioningProfileConverter.go index c92f914d..0f0432b4 100644 --- a/autocodesign/localcodesignasset/mocks/ProvisioningProfileConverter.go +++ b/autocodesign/localcodesignasset/mocks/ProvisioningProfileConverter.go @@ -1,4 +1,4 @@ -// Code generated by mockery 2.9.4. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package mocks @@ -7,7 +7,7 @@ import ( mock "github.com/stretchr/testify/mock" - profileutil "github.com/bitrise-io/go-xcode/profileutil" + profileutil "github.com/bitrise-io/go-xcode/v2/profileutil" ) // ProvisioningProfileConverter is an autogenerated mock type for the ProvisioningProfileConverter type @@ -19,7 +19,15 @@ type ProvisioningProfileConverter struct { func (_m *ProvisioningProfileConverter) ProfileInfoToProfile(info profileutil.ProvisioningProfileInfoModel) (autocodesign.Profile, error) { ret := _m.Called(info) + if len(ret) == 0 { + panic("no return value specified for ProfileInfoToProfile") + } + var r0 autocodesign.Profile + var r1 error + if rf, ok := ret.Get(0).(func(profileutil.ProvisioningProfileInfoModel) (autocodesign.Profile, error)); ok { + return rf(info) + } if rf, ok := ret.Get(0).(func(profileutil.ProvisioningProfileInfoModel) autocodesign.Profile); ok { r0 = rf(info) } else { @@ -28,7 +36,6 @@ func (_m *ProvisioningProfileConverter) ProfileInfoToProfile(info profileutil.Pr } } - var r1 error if rf, ok := ret.Get(1).(func(profileutil.ProvisioningProfileInfoModel) error); ok { r1 = rf(info) } else { @@ -37,3 +44,17 @@ func (_m *ProvisioningProfileConverter) ProfileInfoToProfile(info profileutil.Pr return r0, r1 } + +// NewProvisioningProfileConverter creates a new instance of ProvisioningProfileConverter. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewProvisioningProfileConverter(t interface { + mock.TestingT + Cleanup(func()) +}) *ProvisioningProfileConverter { + mock := &ProvisioningProfileConverter{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/autocodesign/localcodesignasset/mocks/ProvisioningProfileProvider.go b/autocodesign/localcodesignasset/mocks/ProvisioningProfileProvider.go index 48cafa7a..28cf2ee1 100644 --- a/autocodesign/localcodesignasset/mocks/ProvisioningProfileProvider.go +++ b/autocodesign/localcodesignasset/mocks/ProvisioningProfileProvider.go @@ -3,7 +3,7 @@ package mocks import ( - profileutil "github.com/bitrise-io/go-xcode/profileutil" + profileutil "github.com/bitrise-io/go-xcode/v2/profileutil" mock "github.com/stretchr/testify/mock" ) diff --git a/autocodesign/localcodesignasset/profile.go b/autocodesign/localcodesignasset/profile.go index c3a0789c..001fb010 100644 --- a/autocodesign/localcodesignasset/profile.go +++ b/autocodesign/localcodesignasset/profile.go @@ -1,10 +1,10 @@ package localcodesignasset import ( - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/time" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // Profile ... diff --git a/autocodesign/localcodesignasset/profileconverter.go b/autocodesign/localcodesignasset/profileconverter.go index 30b10d9e..d7f4bc26 100644 --- a/autocodesign/localcodesignasset/profileconverter.go +++ b/autocodesign/localcodesignasset/profileconverter.go @@ -3,8 +3,8 @@ package localcodesignasset import ( "os" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // ProvisioningProfileConverter ... diff --git a/autocodesign/localcodesignasset/profilelookup.go b/autocodesign/localcodesignasset/profilelookup.go index 511b3e24..5caf88b6 100644 --- a/autocodesign/localcodesignasset/profilelookup.go +++ b/autocodesign/localcodesignasset/profilelookup.go @@ -6,8 +6,8 @@ import ( "time" "github.com/bitrise-io/go-utils/sliceutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) func findProfile(localProfiles []profileutil.ProvisioningProfileInfoModel, platform autocodesign.Platform, distributionType autocodesign.DistributionType, bundleID string, entitlements autocodesign.Entitlements, minProfileDaysValid int, certSerials []string, deviceUDIDs []string) *profileutil.ProvisioningProfileInfoModel { diff --git a/autocodesign/localcodesignasset/profilelookup_test.go b/autocodesign/localcodesignasset/profilelookup_test.go index 84765fb5..d76571ce 100644 --- a/autocodesign/localcodesignasset/profilelookup_test.go +++ b/autocodesign/localcodesignasset/profilelookup_test.go @@ -5,8 +5,8 @@ import ( "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/stretchr/testify/assert" ) diff --git a/autocodesign/localcodesignasset/profileprovider.go b/autocodesign/localcodesignasset/profileprovider.go index fcdc5f9c..971ecb25 100644 --- a/autocodesign/localcodesignasset/profileprovider.go +++ b/autocodesign/localcodesignasset/profileprovider.go @@ -1,6 +1,6 @@ package localcodesignasset -import "github.com/bitrise-io/go-xcode/profileutil" +import "github.com/bitrise-io/go-xcode/v2/profileutil" // ProvisioningProfileProvider can list profile infos. type ProvisioningProfileProvider interface { diff --git a/autocodesign/profiledownloader/profiledownloader.go b/autocodesign/profiledownloader/profiledownloader.go index 3bb4210f..08c06590 100644 --- a/autocodesign/profiledownloader/profiledownloader.go +++ b/autocodesign/profiledownloader/profiledownloader.go @@ -8,9 +8,9 @@ import ( "github.com/bitrise-io/go-steputils/input" "github.com/bitrise-io/go-utils/filedownloader" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/autocodesign/localcodesignasset" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) type downloader struct { diff --git a/autocodesign/profiles.go b/autocodesign/profiles.go index 181b09fc..708abbf9 100644 --- a/autocodesign/profiles.go +++ b/autocodesign/profiles.go @@ -10,8 +10,8 @@ import ( "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-utils/retry" "github.com/bitrise-io/go-utils/sliceutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/xcodeproject/serialized" ) diff --git a/codesign/codesign.go b/codesign/codesign.go index d6eda9e8..2174b63f 100644 --- a/codesign/codesign.go +++ b/codesign/codesign.go @@ -8,7 +8,6 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient" "github.com/bitrise-io/go-xcode/v2/autocodesign/devportalclient/appstoreconnect" @@ -17,6 +16,7 @@ import ( "github.com/bitrise-io/go-xcode/v2/devportalservice" "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/v2/xcarchive" ) @@ -505,7 +505,7 @@ func (m *Manager) createCodeSignAssetMap(appLayout autocodesign.AppLayout, certi bundleIDProfileInfoMap := signingAssets.BundleIDProfileMap() bundleIDProfileMap := map[string]autocodesign.Profile{} for bundleID, profileInfo := range bundleIDProfileInfoMap { - signingProfile, err := m.profileConverter.ProfileInfoToProfile(profileInfo) + signingProfile, err := m.profileConverter.ProfileInfoToProfile(profileutil.V2Profile(profileInfo)) if err != nil { return nil, fmt.Errorf("failed to convert profile info: %w", err) } @@ -539,7 +539,7 @@ func (m *Manager) createCodeSignAssetMap(appLayout autocodesign.AppLayout, certi bundleIDProfileInfoMap := uiTestSigningAssets.BundleIDProfileMap() bundleIDProfileMap := map[string]autocodesign.Profile{} for bundleID, profileInfo := range bundleIDProfileInfoMap { - signingProfile, err := m.profileConverter.ProfileInfoToProfile(profileInfo) + signingProfile, err := m.profileConverter.ProfileInfoToProfile(profileutil.V2Profile(profileInfo)) if err != nil { return nil, fmt.Errorf("failed to convert profile info: %w", err) } diff --git a/codesign/codesign_test.go b/codesign/codesign_test.go index 2aa11a1e..8ea557cf 100644 --- a/codesign/codesign_test.go +++ b/codesign/codesign_test.go @@ -12,12 +12,12 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/autocodesign/localcodesignasset" localcodesignassetMocks "github.com/bitrise-io/go-xcode/v2/autocodesign/localcodesignasset/mocks" "github.com/bitrise-io/go-xcode/v2/codesign/mocks" "github.com/bitrise-io/go-xcode/v2/devportalservice" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) diff --git a/exportoptionsgenerator/codesign_group_provider.go b/exportoptionsgenerator/codesign_group_provider.go index 60f95086..6c186a41 100644 --- a/exportoptionsgenerator/codesign_group_provider.go +++ b/exportoptionsgenerator/codesign_group_provider.go @@ -5,8 +5,9 @@ import ( "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/export" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" + profileutilv1 "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // CodeSignGroupProvider ... @@ -52,7 +53,7 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate } g.logger.Printf("Resolving CodeSignGroups...") - codeSignGroups := export.CreateSelectableCodeSignGroups(certificates, profiles, bundleIDs) + codeSignGroups := export.CreateSelectableCodeSignGroups(certificates, profileutil.V1Profiles(profiles), bundleIDs) if len(codeSignGroups) == 0 { g.logger.Errorf("Failed to find code signing groups for specified export method (%s)", exportMethod) } @@ -123,7 +124,7 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate var iosCodeSignGroups []export.IosCodeSignGroup for _, selectable := range codeSignGroups { - bundleIDProfileMap := map[string]profileutil.ProvisioningProfileInfoModel{} + bundleIDProfileMap := map[string]profileutilv1.ProvisioningProfileInfoModel{} for bundleID, profiles := range selectable.BundleIDProfilesMap { if len(profiles) > 0 { bundleIDProfileMap[bundleID] = profiles[0] diff --git a/exportoptionsgenerator/exportoptionsgenerator.go b/exportoptionsgenerator/exportoptionsgenerator.go index 61c8a533..66771866 100644 --- a/exportoptionsgenerator/exportoptionsgenerator.go +++ b/exportoptionsgenerator/exportoptionsgenerator.go @@ -7,8 +7,8 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/export" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/v2/xcodeversion" ) diff --git a/exportoptionsgenerator/exportoptionsgenerator_test.go b/exportoptionsgenerator/exportoptionsgenerator_test.go index 9c2fd51a..bbf2ca22 100644 --- a/exportoptionsgenerator/exportoptionsgenerator_test.go +++ b/exportoptionsgenerator/exportoptionsgenerator_test.go @@ -7,9 +7,9 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/mocks" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/v2/xcodeversion" "github.com/stretchr/testify/require" ) diff --git a/exportoptionsgenerator/profiles.go b/exportoptionsgenerator/profiles.go index e9b3928e..30dab9a4 100644 --- a/exportoptionsgenerator/profiles.go +++ b/exportoptionsgenerator/profiles.go @@ -8,7 +8,7 @@ import ( "github.com/bitrise-io/go-utils/pathutil" "github.com/bitrise-io/go-utils/v2/log" - "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // ProvisioningProfileProvider can list profile infos. diff --git a/profileutil/capabilities.go b/profileutil/capabilities.go new file mode 100644 index 00000000..3e5b850b --- /dev/null +++ b/profileutil/capabilities.go @@ -0,0 +1,66 @@ +package profileutil + +import ( + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-xcode/plistutil" +) + +// MatchTargetAndProfileEntitlements ... +func MatchTargetAndProfileEntitlements(targetEntitlements plistutil.PlistData, profileEntitlements plistutil.PlistData, profileType ProfileType) []string { + missingEntitlements := []string{} + + for key := range targetEntitlements { + _, known := KnownProfileCapabilitiesMap[profileType][key] + if !known { + continue + } + _, found := profileEntitlements[key] + if !found { + missingEntitlements = append(missingEntitlements, key) + } + } + + log.Debugf("Found %v entitlements from %v target", len(missingEntitlements), len(targetEntitlements)) + + return missingEntitlements +} + +// KnownProfileCapabilitiesMap ... +var KnownProfileCapabilitiesMap = map[ProfileType]map[string]bool{ + ProfileTypeMacOs: map[string]bool{ + "com.apple.developer.networking.networkextension": true, + "com.apple.developer.icloud-container-environment": true, + "com.apple.developer.icloud-container-development-container-identifiers": true, + "com.apple.developer.aps-environment": true, + "keychain-access-groups": true, + "com.apple.developer.icloud-services": true, + "com.apple.developer.icloud-container-identifiers": true, + "com.apple.developer.networking.vpn.api": true, + "com.apple.developer.ubiquity-kvstore-identifier": true, + "com.apple.developer.ubiquity-container-identifiers": true, + "com.apple.developer.game-center": true, + "com.apple.application-identifier": true, + "com.apple.developer.team-identifier": true, + "com.apple.developer.maps": true, + }, + ProfileTypeIos: map[string]bool{ + "com.apple.developer.in-app-payments": true, + "com.apple.security.application-groups": true, + "com.apple.developer.default-data-protection": true, + "com.apple.developer.healthkit": true, + "com.apple.developer.homekit": true, + "com.apple.developer.networking.HotspotConfiguration": true, + "inter-app-audio": true, + "keychain-access-groups": true, + "com.apple.developer.networking.multipath": true, + "com.apple.developer.nfc.readersession.formats": true, + "com.apple.developer.networking.networkextension": true, + "aps-environment": true, + "com.apple.developer.associated-domains": true, + "com.apple.developer.siri": true, + "com.apple.developer.networking.vpn.api": true, + "com.apple.external-accessory.wireless-configuration": true, + "com.apple.developer.pass-type-identifiers": true, + "com.apple.developer.icloud-container-identifiers": true, + }, +} diff --git a/profileutil/info_model.go b/profileutil/info_model.go new file mode 100644 index 00000000..43e2639f --- /dev/null +++ b/profileutil/info_model.go @@ -0,0 +1,254 @@ +package profileutil + +import ( + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "strings" + "time" + + "github.com/bitrise-io/go-plist" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/fullsailor/pkcs7" +) + +// ProvisioningProfileInfoModel ... +type ProvisioningProfileInfoModel struct { + UUID string + Name string + TeamName string + TeamID string + BundleID string + ExportType exportoptions.Method + ProvisionedDevices []string + DeveloperCertificates []certificateutil.CertificateInfoModel + CreationDate time.Time + ExpirationDate time.Time + Entitlements plistutil.PlistData + ProvisionsAllDevices bool + Type ProfileType +} + +func collectCapabilitesPrintableInfo(entitlements plistutil.PlistData) map[string]interface{} { + capabilities := map[string]interface{}{} + + for key, value := range entitlements { + if KnownProfileCapabilitiesMap[ProfileTypeIos][key] || + KnownProfileCapabilitiesMap[ProfileTypeMacOs][key] { + capabilities[key] = value + } + } + + return capabilities +} + +// PrintableProvisioningProfileInfo ... +func (info ProvisioningProfileInfoModel) String(installedCertificates ...certificateutil.CertificateInfoModel) string { + printable := map[string]interface{}{} + printable["name"] = fmt.Sprintf("%s (%s)", info.Name, info.UUID) + printable["export_type"] = string(info.ExportType) + printable["team"] = fmt.Sprintf("%s (%s)", info.TeamName, info.TeamID) + printable["bundle_id"] = info.BundleID + printable["expiry"] = info.ExpirationDate.String() + printable["is_xcode_managed"] = info.IsXcodeManaged() + + printable["capabilities"] = collectCapabilitesPrintableInfo(info.Entitlements) + + if info.ProvisionedDevices != nil { + printable["devices"] = info.ProvisionedDevices + } + + certificates := []map[string]interface{}{} + for _, certificateInfo := range info.DeveloperCertificates { + certificate := map[string]interface{}{} + certificate["name"] = certificateInfo.CommonName + certificate["serial"] = certificateInfo.Serial + certificate["team_id"] = certificateInfo.TeamID + certificates = append(certificates, certificate) + } + printable["certificates"] = certificates + + errors := []string{} + if installedCertificates != nil && !info.HasInstalledCertificate(installedCertificates) { + errors = append(errors, "none of the profile's certificates are installed") + } + + if err := info.CheckValidity(); err != nil { + errors = append(errors, err.Error()) + } + if len(errors) > 0 { + printable["errors"] = errors + } + + data, err := json.MarshalIndent(printable, "", "\t") + if err != nil { + log.Errorf("Failed to marshal: %v, error: %s", printable, err) + return "" + } + + return string(data) +} + +// IsXcodeManaged ... +func IsXcodeManaged(profileName string) bool { + if strings.HasPrefix(profileName, "XC") { + return true + } + if strings.Contains(profileName, "Provisioning Profile") { + if strings.HasPrefix(profileName, "iOS Team") || + strings.HasPrefix(profileName, "Mac Catalyst Team") || + strings.HasPrefix(profileName, "tvOS Team") || + strings.HasPrefix(profileName, "Mac Team") { + return true + } + } + return false +} + +// IsXcodeManaged ... +func (info ProvisioningProfileInfoModel) IsXcodeManaged() bool { + return IsXcodeManaged(info.Name) +} + +// CheckValidity ... +func (info ProvisioningProfileInfoModel) CheckValidity() error { + timeNow := time.Now() + if !timeNow.Before(info.ExpirationDate) { + return fmt.Errorf("Provisioning Profile is not valid anymore - validity ended at: %s", info.ExpirationDate) + } + return nil +} + +// HasInstalledCertificate ... +func (info ProvisioningProfileInfoModel) HasInstalledCertificate(installedCertificates []certificateutil.CertificateInfoModel) bool { + has := false + for _, certificate := range info.DeveloperCertificates { + for _, installedCertificate := range installedCertificates { + if certificate.Serial == installedCertificate.Serial { + has = true + break + } + } + } + return has +} + +// NewProvisioningProfileInfo ... +func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { + var data plistutil.PlistData + if _, err := plist.Unmarshal(provisioningProfile.Content, &data); err != nil { + return ProvisioningProfileInfoModel{}, err + } + + platforms, _ := data.GetStringArray("Platform") + if len(platforms) == 0 { + return ProvisioningProfileInfoModel{}, fmt.Errorf("missing Platform array in profile") + } + + platform := strings.ToLower(platforms[0]) + var profileType ProfileType + + switch platform { + case string(ProfileTypeIos): + profileType = ProfileTypeIos + case string(ProfileTypeMacOs): + profileType = ProfileTypeMacOs + case string(ProfileTypeTvOs): + profileType = ProfileTypeTvOs + default: + return ProvisioningProfileInfoModel{}, fmt.Errorf("unknown platform type: %s", platform) + } + + profile := PlistData(data) + info := ProvisioningProfileInfoModel{ + UUID: profile.GetUUID(), + Name: profile.GetName(), + TeamName: profile.GetTeamName(), + TeamID: profile.GetTeamID(), + BundleID: profile.GetBundleIdentifier(), + CreationDate: profile.GetCreationDate(), + ExpirationDate: profile.GetExpirationDate(), + ProvisionsAllDevices: profile.GetProvisionsAllDevices(), + Type: profileType, + } + + info.ExportType = profile.GetExportMethod() + + if devicesList := profile.GetProvisionedDevices(); devicesList != nil { + info.ProvisionedDevices = devicesList + } + + developerCertificates, found := data.GetByteArrayArray("DeveloperCertificates") + if found { + certificates := []*x509.Certificate{} + for _, certificateBytes := range developerCertificates { + certificate, err := certificateutil.CertificateFromDERContent(certificateBytes) + if err == nil && certificate != nil { + certificates = append(certificates, certificate) + } + } + + for _, certificate := range certificates { + if certificate != nil { + info.DeveloperCertificates = append(info.DeveloperCertificates, certificateutil.NewCertificateInfo(*certificate, nil)) + } + } + } + + info.Entitlements = profile.GetEntitlements() + + return info, nil +} + +// NewProvisioningProfileInfoFromFile ... +func NewProvisioningProfileInfoFromFile(pth string) (ProvisioningProfileInfoModel, error) { + provisioningProfile, err := ProvisioningProfileFromFile(pth) + if err != nil { + return ProvisioningProfileInfoModel{}, err + } + if provisioningProfile != nil { + return NewProvisioningProfileInfo(*provisioningProfile) + } + return ProvisioningProfileInfoModel{}, errors.New("failed to parse provisioning profile infos") +} + +// InstalledProvisioningProfileInfos ... +func InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningProfileInfoModel, error) { + provisioningProfiles, err := InstalledProvisioningProfiles(profileType) + if err != nil { + return nil, err + } + + infos := []ProvisioningProfileInfoModel{} + for _, provisioningProfile := range provisioningProfiles { + if provisioningProfile != nil { + info, err := NewProvisioningProfileInfo(*provisioningProfile) + if err != nil { + return nil, err + } + infos = append(infos, info) + } + } + return infos, nil +} + +// FindProvisioningProfileInfo ... +func FindProvisioningProfileInfo(uuid string) (ProvisioningProfileInfoModel, string, error) { + profile, pth, err := FindProvisioningProfile(uuid) + if err != nil { + return ProvisioningProfileInfoModel{}, "", err + } + if pth == "" || profile == nil { + return ProvisioningProfileInfoModel{}, "", nil + } + + info, err := NewProvisioningProfileInfo(*profile) + if err != nil { + return ProvisioningProfileInfoModel{}, "", err + } + return info, pth, nil +} diff --git a/profileutil/info_model_test.go b/profileutil/info_model_test.go new file mode 100644 index 00000000..4ca3a4c5 --- /dev/null +++ b/profileutil/info_model_test.go @@ -0,0 +1,203 @@ +package profileutil + +import ( + "testing" + + "github.com/fullsailor/pkcs7" + + "github.com/stretchr/testify/require" +) + +func TestIsXcodeManaged(t *testing.T) { + xcodeManagedNames := []string{ + "XC iOS: custom.bundle.id", + "XC tvOS: custom.bundle.id", + "iOS Team Provisioning Profile: another.custom.bundle.id", + "tvOS Team Provisioning Profile: another.custom.bundle.id", + "iOS Team Store Provisioning Profile: my.bundle.id", + "tvOS Team Store Provisioning Profile: my.bundle.id", + "Mac Team Provisioning Profile: my.bundle.id", + "Mac Team Store Provisioning Profile: my.bundle.id", + "Mac Catalyst Team Provisioning Profile: my.bundle.id", + } + nonXcodeManagedNames := []string{ + "Test Profile Name", + "iOS Distribution Profile: test.bundle.id", + "iOS Dev", + "tvOS Distribution Profile: test.bundle.id", + "tvOS Dev", + "Mac Distribution Profile: test.bundle.id", + "Mac Dev", + } + + for _, profileName := range xcodeManagedNames { + require.Equal(t, true, IsXcodeManaged(profileName)) + } + + for _, profileName := range nonXcodeManagedNames { + require.Equal(t, false, IsXcodeManaged(profileName)) + } +} + +func TestProvisioningProfilePlatform(t *testing.T) { + tests := []struct { + name string + profileContent string + want ProfileType + }{ + { + name: "iOS", + profileContent: iosProfileContent, + want: ProfileTypeIos, + }, + { + name: "macOS", + profileContent: macosProfileContent, + want: ProfileTypeMacOs, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + profilePkcs7 := pkcs7.PKCS7{Content: []byte(tt.profileContent)} + got, err := NewProvisioningProfileInfo(profilePkcs7) + + require.NoError(t, err) + require.Equal(t, tt.want, got.Type) + }) + } +} + +const iosProfileContent = ` + + + + AppIDName + Bitrise Test + ApplicationIdentifierPrefix + + 9NS44DLTN7 + + CreationDate + 2016-09-22T11:28:46Z + Platform + + iOS + + DeveloperCertificates + + + + Entitlements + + keychain-access-groups + + 9NS44DLTN7.* + + get-task-allow + + application-identifier + 9NS44DLTN7.* + com.apple.developer.team-identifier + 9NS44DLTN7 + + ExpirationDate + 2017-09-22T11:28:46Z + Name + Bitrise Test Development + ProvisionedDevices + + b13813075ad9b298cb9a9f28555c49573d8bc322 + + TeamIdentifier + + 9NS44DLTN7 + + TeamName + Some Dude + TimeToLive + 365 + UUID + 4b617a5f-e31e-4edc-9460-718a5abacd05 + Version + 1 +` + +const macosProfileContent = ` + + + + AppIDName + XC io bitrise mobile ios QuickActionsTodayExtension + ApplicationIdentifierPrefix + + 72SA8V3WYL + + CreationDate + 2022-02-28T10:35:39Z + Platform + + OSX + + IsXcodeManaged + + DeveloperCertificates + + + + + DER-Encoded-Profile + + + Entitlements + + + com.apple.developer.game-center + + + com.apple.security.application-groups + + group.io.bitrise.statistics + + + application-identifier + 72SA8V3WYL.io.bitrise.mobile.ios.QuickActionsTodayExtension + + com.apple.application-identifier + 72SA8V3WYL.io.bitrise.mobile.ios.QuickActionsTodayExtension + + keychain-access-groups + + 72SA8V3WYL.* + com.apple.token + + + get-task-allow + + + com.apple.developer.team-identifier + 72SA8V3WYL + + + ExpirationDate + 2023-02-28T10:35:39Z + Name + _profile_bug_type_catalyst + ProvisionedDevices + + BA0EC799-F254-5574-B335-E70B8A2FA5E7 + + TeamIdentifier + + 72SA8V3WYL + + TeamName + BITFALL FEJLESZTO KORLATOLT FELELOSSEGU TARSASAG + TimeToLive + 365 + UUID + dea6a48c-d7d3-4624-9f6b-e0c3b3ce517d + Version + 1 + +` diff --git a/profileutil/plist_data.go b/profileutil/plist_data.go new file mode 100644 index 00000000..56467962 --- /dev/null +++ b/profileutil/plist_data.go @@ -0,0 +1,171 @@ +package profileutil + +import ( + "strings" + "time" + + "github.com/bitrise-io/go-plist" + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/plistutil" +) + +// PlistData ... +type PlistData plistutil.PlistData + +// NewPlistDataFromFile ... +func NewPlistDataFromFile(provisioningProfilePth string) (PlistData, error) { + provisioningProfilePKCS7, err := ProvisioningProfileFromFile(provisioningProfilePth) + if err != nil { + return PlistData{}, err + } + + var plistData plistutil.PlistData + if _, err := plist.Unmarshal(provisioningProfilePKCS7.Content, &plistData); err != nil { + return PlistData{}, err + } + + return PlistData(plistData), nil +} + +// GetUUID ... +func (profile PlistData) GetUUID() string { + data := plistutil.PlistData(profile) + uuid, _ := data.GetString("UUID") + return uuid +} + +// GetName ... +func (profile PlistData) GetName() string { + data := plistutil.PlistData(profile) + uuid, _ := data.GetString("Name") + return uuid +} + +// GetApplicationIdentifier ... +func (profile PlistData) GetApplicationIdentifier() string { + data := plistutil.PlistData(profile) + entitlements, ok := data.GetMapStringInterface("Entitlements") + if !ok { + return "" + } + + applicationID, ok := entitlements.GetString("application-identifier") + if !ok { + applicationID, ok = entitlements.GetString("com.apple.application-identifier") + if !ok { + return "" + } + } + return applicationID +} + +// GetBundleIdentifier ... +func (profile PlistData) GetBundleIdentifier() string { + applicationID := profile.GetApplicationIdentifier() + + plistData := plistutil.PlistData(profile) + prefixes, found := plistData.GetStringArray("ApplicationIdentifierPrefix") + if found { + for _, prefix := range prefixes { + applicationID = strings.TrimPrefix(applicationID, prefix+".") + } + } + + teamID := profile.GetTeamID() + return strings.TrimPrefix(applicationID, teamID+".") +} + +// GetExportMethod ... +func (profile PlistData) GetExportMethod() exportoptions.Method { + data := plistutil.PlistData(profile) + entitlements, _ := data.GetMapStringInterface("Entitlements") + platform, _ := data.GetStringArray("Platform") + + if len(platform) != 0 { + switch strings.ToLower(platform[0]) { + case "osx": + _, ok := data.GetStringArray("ProvisionedDevices") + if !ok { + if allDevices, ok := data.GetBool("ProvisionsAllDevices"); ok && allDevices { + return exportoptions.MethodDeveloperID + } + return exportoptions.MethodAppStore + } + return exportoptions.MethodDevelopment + case "ios", "tvos": + _, ok := data.GetStringArray("ProvisionedDevices") + if !ok { + if allDevices, ok := data.GetBool("ProvisionsAllDevices"); ok && allDevices { + return exportoptions.MethodEnterprise + } + return exportoptions.MethodAppStore + } + if allow, ok := entitlements.GetBool("get-task-allow"); ok && allow { + return exportoptions.MethodDevelopment + } + return exportoptions.MethodAdHoc + } + } + + return exportoptions.MethodDefault +} + +// GetEntitlements ... +func (profile PlistData) GetEntitlements() plistutil.PlistData { + data := plistutil.PlistData(profile) + entitlements, _ := data.GetMapStringInterface("Entitlements") + return entitlements +} + +// GetTeamID ... +func (profile PlistData) GetTeamID() string { + data := plistutil.PlistData(profile) + entitlements, ok := data.GetMapStringInterface("Entitlements") + if ok { + teamID, _ := entitlements.GetString("com.apple.developer.team-identifier") + return teamID + } + return "" +} + +// GetExpirationDate ... +func (profile PlistData) GetExpirationDate() time.Time { + data := plistutil.PlistData(profile) + expiry, _ := data.GetTime("ExpirationDate") + return expiry +} + +// GetProvisionedDevices ... +func (profile PlistData) GetProvisionedDevices() []string { + data := plistutil.PlistData(profile) + devices, _ := data.GetStringArray("ProvisionedDevices") + return devices +} + +// GetDeveloperCertificates ... +func (profile PlistData) GetDeveloperCertificates() [][]byte { + data := plistutil.PlistData(profile) + developerCertificates, _ := data.GetByteArrayArray("DeveloperCertificates") + return developerCertificates +} + +// GetTeamName ... +func (profile PlistData) GetTeamName() string { + data := plistutil.PlistData(profile) + teamName, _ := data.GetString("TeamName") + return teamName +} + +// GetCreationDate ... +func (profile PlistData) GetCreationDate() time.Time { + data := plistutil.PlistData(profile) + creationDate, _ := data.GetTime("CreationDate") + return creationDate +} + +// GetProvisionsAllDevices ... +func (profile PlistData) GetProvisionsAllDevices() bool { + data := plistutil.PlistData(profile) + provisionsAlldevices, _ := data.GetBool("ProvisionsAllDevices") + return provisionsAlldevices +} diff --git a/profileutil/plist_data_test.go b/profileutil/plist_data_test.go new file mode 100644 index 00000000..7a0d08d9 --- /dev/null +++ b/profileutil/plist_data_test.go @@ -0,0 +1,375 @@ +package profileutil + +import ( + "testing" + + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/plistutil" + "github.com/stretchr/testify/require" +) + +func TestPlistData(t *testing.T) { + t.Log("development profile specifies development export method") + { + profile, err := plistutil.NewPlistDataFromContent(developmentProfileContent) + require.NoError(t, err) + require.Equal(t, "4b617a5f-e31e-4edc-9460-718a5abacd05", PlistData(profile).GetUUID()) + require.Equal(t, "Bitrise Test Development", PlistData(profile).GetName()) + require.Equal(t, "9NS44DLTN7.*", PlistData(profile).GetApplicationIdentifier()) + require.Equal(t, "*", PlistData(profile).GetBundleIdentifier()) + require.Equal(t, exportoptions.MethodDevelopment, PlistData(profile).GetExportMethod()) + require.Equal(t, "9NS44DLTN7", PlistData(profile).GetTeamID()) + require.Equal(t, "Some Dude", PlistData(profile).GetTeamName()) + require.Equal(t, "2016-09-22T11:28:46Z", PlistData(profile).GetCreationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, "2017-09-22T11:28:46Z", PlistData(profile).GetExpirationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, []string{"b13813075ad9b298cb9a9f28555c49573d8bc322"}, PlistData(profile).GetProvisionedDevices()) + require.Equal(t, [][]uint8{[]uint8{}}, PlistData(profile).GetDeveloperCertificates()) + require.Equal(t, false, PlistData(profile).GetProvisionsAllDevices()) + } + + t.Log("app store profile specifies app-store export method") + { + profile, err := plistutil.NewPlistDataFromContent(appStoreProfileContent) + require.NoError(t, err) + require.Equal(t, "a60668dd-191a-4770-8b1e-b453b87aa60b", PlistData(profile).GetUUID()) + require.Equal(t, "Bitrise Test App Store", PlistData(profile).GetName()) + require.Equal(t, "9NS44DLTN7.*", PlistData(profile).GetApplicationIdentifier()) + require.Equal(t, "*", PlistData(profile).GetBundleIdentifier()) + require.Equal(t, exportoptions.MethodAppStore, PlistData(profile).GetExportMethod()) + require.Equal(t, "9NS44DLTN7", PlistData(profile).GetTeamID()) + require.Equal(t, "Some Dude", PlistData(profile).GetTeamName()) + require.Equal(t, "2016-09-22T11:29:12Z", PlistData(profile).GetCreationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, "2017-09-21T13:20:06Z", PlistData(profile).GetExpirationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, []string(nil), PlistData(profile).GetProvisionedDevices()) + require.Equal(t, [][]uint8{[]uint8{}}, PlistData(profile).GetDeveloperCertificates()) + require.Equal(t, false, PlistData(profile).GetProvisionsAllDevices()) + } + + t.Log("ad hoc profile specifies ad-hoc export method") + { + profile, err := plistutil.NewPlistDataFromContent(adHocProfileContent) + require.NoError(t, err) + require.Equal(t, "26668300-5743-46a1-8e00-7023e2e35c7d", PlistData(profile).GetUUID()) + require.Equal(t, "Bitrise Test Ad Hoc", PlistData(profile).GetName()) + require.Equal(t, "9NS44DLTN7.*", PlistData(profile).GetApplicationIdentifier()) + require.Equal(t, "*", PlistData(profile).GetBundleIdentifier()) + require.Equal(t, exportoptions.MethodAdHoc, PlistData(profile).GetExportMethod()) + require.Equal(t, "9NS44DLTN7", PlistData(profile).GetTeamID()) + require.Equal(t, "Some Dude", PlistData(profile).GetTeamName()) + require.Equal(t, "2016-09-22T11:29:38Z", PlistData(profile).GetCreationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, "2017-09-21T13:20:06Z", PlistData(profile).GetExpirationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, []string{"b13813075ad9b298cb9a9f28555c49573d8bc322"}, PlistData(profile).GetProvisionedDevices()) + require.Equal(t, [][]uint8{[]uint8{}}, PlistData(profile).GetDeveloperCertificates()) + require.Equal(t, false, PlistData(profile).GetProvisionsAllDevices()) + } + + t.Log("it creates model from enterprise profile content") + { + profile, err := plistutil.NewPlistDataFromContent(enterpriseProfileContent) + require.NoError(t, err) + require.Equal(t, "8d6caa15-ac49-48f9-9bd3-ce9244add6a0", PlistData(profile).GetUUID()) + require.Equal(t, "Bitrise Test Enterprise", PlistData(profile).GetName()) + require.Equal(t, "9NS44DLTN7.com.Bitrise.Test", PlistData(profile).GetApplicationIdentifier()) + require.Equal(t, "com.Bitrise.Test", PlistData(profile).GetBundleIdentifier()) + require.Equal(t, exportoptions.MethodEnterprise, PlistData(profile).GetExportMethod()) + require.Equal(t, "9NS44DLTN7", PlistData(profile).GetTeamID()) + require.Equal(t, "Bitrise", PlistData(profile).GetTeamName()) + require.Equal(t, "2015-10-05T13:32:46Z", PlistData(profile).GetCreationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, "2016-10-04T13:32:46Z", PlistData(profile).GetExpirationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, []string(nil), PlistData(profile).GetProvisionedDevices()) + require.Equal(t, [][]uint8{[]uint8{}}, PlistData(profile).GetDeveloperCertificates()) + require.Equal(t, true, PlistData(profile).GetProvisionsAllDevices()) + } +} + +func TestTVOSPlistData(t *testing.T) { + t.Log("it creates model from tvOS appstore profile content") + { + profile, err := plistutil.NewPlistDataFromContent(tvOSAppStoreProfileContent) + require.NoError(t, err) + require.Equal(t, "dec523d5-624b-44bd-8d16-6d1d69c63276", PlistData(profile).GetUUID()) + require.Equal(t, "Bitrise app-store - (bdh.NPO-Live.bitrise.sample)", PlistData(profile).GetName()) + require.Equal(t, "72SA8V3WYL.bdh.NPO-Live.bitrise.sample", PlistData(profile).GetApplicationIdentifier()) + require.Equal(t, "bdh.NPO-Live.bitrise.sample", PlistData(profile).GetBundleIdentifier()) + require.Equal(t, exportoptions.MethodAppStore, PlistData(profile).GetExportMethod()) + require.Equal(t, "72SA8V3WYL", PlistData(profile).GetTeamID()) + require.Equal(t, "Bitrise", PlistData(profile).GetTeamName()) + require.Equal(t, "2018-10-24T11:22:30Z", PlistData(profile).GetCreationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, "2019-04-16T08:42:18Z", PlistData(profile).GetExpirationDate().Format("2006-01-02T15:04:05Z")) + require.Equal(t, []string(nil), PlistData(profile).GetProvisionedDevices()) + require.Equal(t, [][]uint8{[]uint8{}}, PlistData(profile).GetDeveloperCertificates()) + require.Equal(t, false, PlistData(profile).GetProvisionsAllDevices()) + } +} + +const developmentProfileContent = ` + + + + AppIDName + Bitrise Test + ApplicationIdentifierPrefix + + 9NS44DLTN7 + + CreationDate + 2016-09-22T11:28:46Z + Platform + + iOS + + DeveloperCertificates + + + + Entitlements + + keychain-access-groups + + 9NS44DLTN7.* + + get-task-allow + + application-identifier + 9NS44DLTN7.* + com.apple.developer.team-identifier + 9NS44DLTN7 + + ExpirationDate + 2017-09-22T11:28:46Z + Name + Bitrise Test Development + ProvisionedDevices + + b13813075ad9b298cb9a9f28555c49573d8bc322 + + TeamIdentifier + + 9NS44DLTN7 + + TeamName + Some Dude + TimeToLive + 365 + UUID + 4b617a5f-e31e-4edc-9460-718a5abacd05 + Version + 1 +` + +const appStoreProfileContent = ` + + + + AppIDName + Bitrise Test + ApplicationIdentifierPrefix + + 9NS44DLTN7 + + CreationDate + 2016-09-22T11:29:12Z + Platform + + iOS + + DeveloperCertificates + + + + Entitlements + + keychain-access-groups + + 9NS44DLTN7.* + + get-task-allow + + application-identifier + 9NS44DLTN7.* + com.apple.developer.team-identifier + 9NS44DLTN7 + beta-reports-active + + + ExpirationDate + 2017-09-21T13:20:06Z + Name + Bitrise Test App Store + TeamIdentifier + + 9NS44DLTN7 + + TeamName + Some Dude + TimeToLive + 364 + UUID + a60668dd-191a-4770-8b1e-b453b87aa60b + Version + 1 +` + +const adHocProfileContent = ` + + + + AppIDName + Bitrise Test + ApplicationIdentifierPrefix + + 9NS44DLTN7 + + CreationDate + 2016-09-22T11:29:38Z + Platform + + iOS + + DeveloperCertificates + + + + Entitlements + + keychain-access-groups + + 9NS44DLTN7.* + + get-task-allow + + application-identifier + 9NS44DLTN7.* + com.apple.developer.team-identifier + 9NS44DLTN7 + + ExpirationDate + 2017-09-21T13:20:06Z + Name + Bitrise Test Ad Hoc + ProvisionedDevices + + b13813075ad9b298cb9a9f28555c49573d8bc322 + + TeamIdentifier + + 9NS44DLTN7 + + TeamName + Some Dude + TimeToLive + 364 + UUID + 26668300-5743-46a1-8e00-7023e2e35c7d + Version + 1 +` + +const enterpriseProfileContent = ` + + + + AppIDName + Test + ApplicationIdentifierPrefix + + 9NS44DLTN7 + + CreationDate + 2015-10-05T13:32:46Z + Platform + + iOS + + DeveloperCertificates + + + + Entitlements + + keychain-access-groups + + 9NS44DLTN7.* + + get-task-allow + + application-identifier + 9NS44DLTN7.com.Bitrise.Test + com.apple.developer.team-identifier + 9NS44DLTN7 + + + ExpirationDate + 2016-10-04T13:32:46Z + Name + Bitrise Test Enterprise + ProvisionsAllDevices + + TeamIdentifier + + 9NS44DLTN7 + + TeamName + Bitrise + TimeToLive + 365 + UUID + 8d6caa15-ac49-48f9-9bd3-ce9244add6a0 + Version + 1 +` + +const tvOSAppStoreProfileContent = ` + + + + AppIDName + Bitrise bdh NPOLive bitrise sample be2b4e3cfb0f2a967b404820aa18e09c + ApplicationIdentifierPrefix + + 72SA8V3WYL + + CreationDate + 2018-10-24T11:22:30Z + Platform + + tvOS + + DeveloperCertificates + + + + IsXcodeManaged + + Entitlements + + keychain-access-groups + + 72SA8V3WYL.* + + get-task-allow + + application-identifier + 72SA8V3WYL.bdh.NPO-Live.bitrise.sample + com.apple.developer.team-identifier + 72SA8V3WYL + beta-reports-active + + + ExpirationDate + 2019-04-16T08:42:18Z + Name + Bitrise app-store - (bdh.NPO-Live.bitrise.sample) + TeamIdentifier + + 72SA8V3WYL + + TeamName + Bitrise + TimeToLive + 173 + UUID + dec523d5-624b-44bd-8d16-6d1d69c63276 + Version + 1 +` diff --git a/profileutil/util.go b/profileutil/util.go new file mode 100644 index 00000000..015d4780 --- /dev/null +++ b/profileutil/util.go @@ -0,0 +1,110 @@ +package profileutil + +import ( + "path/filepath" + + "github.com/bitrise-io/go-utils/fileutil" + "github.com/bitrise-io/go-utils/pathutil" + "github.com/fullsailor/pkcs7" +) + +// ProfileType ... +type ProfileType string + +// ProfileTypeIos ... +const ProfileTypeIos ProfileType = "ios" + +// ProfileTypeMacOs ... +const ProfileTypeMacOs ProfileType = "osx" + +// ProfileTypeTvOs ... +const ProfileTypeTvOs ProfileType = "tvos" + +// ProvProfileSystemDirPath ... +const ProvProfileSystemDirPath = "~/Library/MobileDevice/Provisioning Profiles" + +// ProvisioningProfileFromContent ... +func ProvisioningProfileFromContent(content []byte) (*pkcs7.PKCS7, error) { + return pkcs7.Parse(content) +} + +// ProvisioningProfileFromFile ... +func ProvisioningProfileFromFile(pth string) (*pkcs7.PKCS7, error) { + content, err := fileutil.ReadBytesFromFile(pth) + if err != nil { + return nil, err + } + return ProvisioningProfileFromContent(content) +} + +// InstalledProvisioningProfiles ... +func InstalledProvisioningProfiles(profileType ProfileType) ([]*pkcs7.PKCS7, error) { + ext := ".mobileprovision" + if profileType == ProfileTypeMacOs { + ext = ".provisionprofile" + } + + absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) + if err != nil { + return nil, err + } + + pattern := filepath.Join(pathutil.EscapeGlobPath(absProvProfileDirPath), "*"+ext) + pths, err := filepath.Glob(pattern) + if err != nil { + return nil, err + } + + profiles := []*pkcs7.PKCS7{} + for _, pth := range pths { + profile, err := ProvisioningProfileFromFile(pth) + if err != nil { + return nil, err + } + profiles = append(profiles, profile) + } + return profiles, nil +} + +// FindProvisioningProfile ... +func FindProvisioningProfile(uuid string) (*pkcs7.PKCS7, string, error) { + { + iosProvisioningProfileExt := ".mobileprovision" + absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) + if err != nil { + return nil, "", err + } + + pth := filepath.Join(absProvProfileDirPath, uuid+iosProvisioningProfileExt) + if exist, err := pathutil.IsPathExists(pth); err != nil { + return nil, "", err + } else if exist { + profile, err := ProvisioningProfileFromFile(pth) + if err != nil { + return nil, "", err + } + return profile, pth, nil + } + } + + { + macOsProvisioningProfileExt := ".provisionprofile" + absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) + if err != nil { + return nil, "", err + } + + pth := filepath.Join(absProvProfileDirPath, uuid+macOsProvisioningProfileExt) + if exist, err := pathutil.IsPathExists(pth); err != nil { + return nil, "", err + } else if exist { + profile, err := ProvisioningProfileFromFile(pth) + if err != nil { + return nil, "", err + } + return profile, pth, nil + } + } + + return nil, "", nil +} diff --git a/profileutil/v1_adapter.go b/profileutil/v1_adapter.go new file mode 100644 index 00000000..afeb53e1 --- /dev/null +++ b/profileutil/v1_adapter.go @@ -0,0 +1,69 @@ +package profileutil + +import ( + profileutilv1 "github.com/bitrise-io/go-xcode/profileutil" +) + +func V2Profile(model profileutilv1.ProvisioningProfileInfoModel) ProvisioningProfileInfoModel { + return ProvisioningProfileInfoModel{ + UUID: model.UUID, + Name: model.Name, + TeamName: model.TeamName, + TeamID: model.TeamID, + BundleID: model.BundleID, + ExportType: model.ExportType, + ProvisionedDevices: copySlice(model.ProvisionedDevices), + DeveloperCertificates: copySlice(model.DeveloperCertificates), + CreationDate: model.CreationDate, + ExpirationDate: model.ExpirationDate, + Entitlements: copyMap(model.Entitlements), + ProvisionsAllDevices: model.ProvisionsAllDevices, + Type: ProfileType(model.Type), + } +} + +func V1Profiles(models []ProvisioningProfileInfoModel) []profileutilv1.ProvisioningProfileInfoModel { + profiles := make([]profileutilv1.ProvisioningProfileInfoModel, len(models)) + for i, model := range models { + profiles[i] = V1Profile(model) + } + return profiles +} + +func V1Profile(model ProvisioningProfileInfoModel) profileutilv1.ProvisioningProfileInfoModel { + return profileutilv1.ProvisioningProfileInfoModel{ + UUID: model.UUID, + Name: model.Name, + TeamName: model.TeamName, + TeamID: model.TeamID, + BundleID: model.BundleID, + ExportType: model.ExportType, + ProvisionedDevices: copySlice(model.ProvisionedDevices), + DeveloperCertificates: copySlice(model.DeveloperCertificates), + CreationDate: model.CreationDate, + ExpirationDate: model.ExpirationDate, + Entitlements: copyMap(model.Entitlements), + ProvisionsAllDevices: model.ProvisionsAllDevices, + Type: profileutilv1.ProfileType(model.Type), + } +} + +func copySlice[T any](src []T) []T { + if src == nil { + return nil + } + dst := make([]T, len(src)) + copy(dst, src) + return dst +} + +func copyMap[K comparable, V any](src map[K]V) map[K]V { + if src == nil { + return nil + } + dst := make(map[K]V, len(src)) + for k, v := range src { + dst[k] = v + } + return dst +} diff --git a/xcarchive/ios.go b/xcarchive/ios.go index c945d99c..07f3fa67 100644 --- a/xcarchive/ios.go +++ b/xcarchive/ios.go @@ -8,9 +8,9 @@ import ( "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" "github.com/bitrise-io/go-utils/v2/pathutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // IosBaseApplication ... diff --git a/xcarchive/ios_test.go b/xcarchive/ios_test.go index 4ded9cb8..60039a23 100644 --- a/xcarchive/ios_test.go +++ b/xcarchive/ios_test.go @@ -10,9 +10,9 @@ import ( "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" "github.com/bitrise-io/go-utils/v2/pathutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/stretchr/testify/require" ) diff --git a/xcarchive/macos.go b/xcarchive/macos.go index 1d3b3c13..c443c4b7 100644 --- a/xcarchive/macos.go +++ b/xcarchive/macos.go @@ -7,8 +7,8 @@ import ( "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" "github.com/bitrise-io/go-utils/v2/pathutil" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) type macosBaseApplication struct { From 846a470c78a527e6c37dec7ce21dbec28441cf99 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Thu, 29 Jan 2026 11:34:11 +0100 Subject: [PATCH 2/8] Fix lint issue --- profileutil/v1_adapter.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/profileutil/v1_adapter.go b/profileutil/v1_adapter.go index afeb53e1..5a4d3f79 100644 --- a/profileutil/v1_adapter.go +++ b/profileutil/v1_adapter.go @@ -4,6 +4,7 @@ import ( profileutilv1 "github.com/bitrise-io/go-xcode/profileutil" ) +// V2Profile ... func V2Profile(model profileutilv1.ProvisioningProfileInfoModel) ProvisioningProfileInfoModel { return ProvisioningProfileInfoModel{ UUID: model.UUID, @@ -22,6 +23,7 @@ func V2Profile(model profileutilv1.ProvisioningProfileInfoModel) ProvisioningPro } } +// V1Profiles ... func V1Profiles(models []ProvisioningProfileInfoModel) []profileutilv1.ProvisioningProfileInfoModel { profiles := make([]profileutilv1.ProvisioningProfileInfoModel, len(models)) for i, model := range models { @@ -30,6 +32,7 @@ func V1Profiles(models []ProvisioningProfileInfoModel) []profileutilv1.Provision return profiles } +// V1Profile ... func V1Profile(model ProvisioningProfileInfoModel) profileutilv1.ProvisioningProfileInfoModel { return profileutilv1.ProvisioningProfileInfoModel{ UUID: model.UUID, From cf13b9928474283fb4399d0e1030962d9939f2e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Thu, 29 Jan 2026 12:01:24 +0100 Subject: [PATCH 3/8] Remove unused FindProvisioningProfileInfo, currentTime parameter for CheckValidity and code cleanup --- .../codesign_group_provider.go | 4 +- profileutil/info_model.go | 77 ++++++++----------- 2 files changed, 33 insertions(+), 48 deletions(-) diff --git a/exportoptionsgenerator/codesign_group_provider.go b/exportoptionsgenerator/codesign_group_provider.go index 6c186a41..07073a79 100644 --- a/exportoptionsgenerator/codesign_group_provider.go +++ b/exportoptionsgenerator/codesign_group_provider.go @@ -1,6 +1,8 @@ package exportoptionsgenerator import ( + "time" + "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/export" @@ -49,7 +51,7 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate g.logger.Debugf("Installed profiles:") for _, profileInfo := range profiles { - g.logger.Debugf(profileInfo.String(certificates...)) + g.logger.Debugf(profileInfo.String(time.Now, certificates...)) } g.logger.Printf("Resolving CodeSignGroups...") diff --git a/profileutil/info_model.go b/profileutil/info_model.go index 43e2639f..b2329619 100644 --- a/profileutil/info_model.go +++ b/profileutil/info_model.go @@ -33,21 +33,8 @@ type ProvisioningProfileInfoModel struct { Type ProfileType } -func collectCapabilitesPrintableInfo(entitlements plistutil.PlistData) map[string]interface{} { - capabilities := map[string]interface{}{} - - for key, value := range entitlements { - if KnownProfileCapabilitiesMap[ProfileTypeIos][key] || - KnownProfileCapabilitiesMap[ProfileTypeMacOs][key] { - capabilities[key] = value - } - } - - return capabilities -} - // PrintableProvisioningProfileInfo ... -func (info ProvisioningProfileInfoModel) String(installedCertificates ...certificateutil.CertificateInfoModel) string { +func (info ProvisioningProfileInfoModel) String(currentTime func() time.Time, installedCertificates ...certificateutil.CertificateInfoModel) string { printable := map[string]interface{}{} printable["name"] = fmt.Sprintf("%s (%s)", info.Name, info.UUID) printable["export_type"] = string(info.ExportType) @@ -56,7 +43,7 @@ func (info ProvisioningProfileInfoModel) String(installedCertificates ...certifi printable["expiry"] = info.ExpirationDate.String() printable["is_xcode_managed"] = info.IsXcodeManaged() - printable["capabilities"] = collectCapabilitesPrintableInfo(info.Entitlements) + printable["capabilities"] = collectCapabilitiesPrintableInfo(info.Entitlements) if info.ProvisionedDevices != nil { printable["devices"] = info.ProvisionedDevices @@ -77,7 +64,7 @@ func (info ProvisioningProfileInfoModel) String(installedCertificates ...certifi errors = append(errors, "none of the profile's certificates are installed") } - if err := info.CheckValidity(); err != nil { + if err := info.CheckValidity(currentTime); err != nil { errors = append(errors, err.Error()) } if len(errors) > 0 { @@ -93,30 +80,14 @@ func (info ProvisioningProfileInfoModel) String(installedCertificates ...certifi return string(data) } -// IsXcodeManaged ... -func IsXcodeManaged(profileName string) bool { - if strings.HasPrefix(profileName, "XC") { - return true - } - if strings.Contains(profileName, "Provisioning Profile") { - if strings.HasPrefix(profileName, "iOS Team") || - strings.HasPrefix(profileName, "Mac Catalyst Team") || - strings.HasPrefix(profileName, "tvOS Team") || - strings.HasPrefix(profileName, "Mac Team") { - return true - } - } - return false -} - // IsXcodeManaged ... func (info ProvisioningProfileInfoModel) IsXcodeManaged() bool { return IsXcodeManaged(info.Name) } // CheckValidity ... -func (info ProvisioningProfileInfoModel) CheckValidity() error { - timeNow := time.Now() +func (info ProvisioningProfileInfoModel) CheckValidity(currentTime func() time.Time) error { + timeNow := currentTime() if !timeNow.Before(info.ExpirationDate) { return fmt.Errorf("Provisioning Profile is not valid anymore - validity ended at: %s", info.ExpirationDate) } @@ -137,6 +108,22 @@ func (info ProvisioningProfileInfoModel) HasInstalledCertificate(installedCertif return has } +// IsXcodeManaged ... +func IsXcodeManaged(profileName string) bool { + if strings.HasPrefix(profileName, "XC") { + return true + } + if strings.Contains(profileName, "Provisioning Profile") { + if strings.HasPrefix(profileName, "iOS Team") || + strings.HasPrefix(profileName, "Mac Catalyst Team") || + strings.HasPrefix(profileName, "tvOS Team") || + strings.HasPrefix(profileName, "Mac Team") { + return true + } + } + return false +} + // NewProvisioningProfileInfo ... func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { var data plistutil.PlistData @@ -236,19 +223,15 @@ func InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningP return infos, nil } -// FindProvisioningProfileInfo ... -func FindProvisioningProfileInfo(uuid string) (ProvisioningProfileInfoModel, string, error) { - profile, pth, err := FindProvisioningProfile(uuid) - if err != nil { - return ProvisioningProfileInfoModel{}, "", err - } - if pth == "" || profile == nil { - return ProvisioningProfileInfoModel{}, "", nil - } +func collectCapabilitiesPrintableInfo(entitlements plistutil.PlistData) map[string]interface{} { + capabilities := map[string]interface{}{} - info, err := NewProvisioningProfileInfo(*profile) - if err != nil { - return ProvisioningProfileInfoModel{}, "", err + for key, value := range entitlements { + if KnownProfileCapabilitiesMap[ProfileTypeIos][key] || + KnownProfileCapabilitiesMap[ProfileTypeMacOs][key] { + capabilities[key] = value + } } - return info, pth, nil + + return capabilities } From 2913b327238c925e6e640358b15dfed0ccfa4338 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Fri, 30 Jan 2026 11:40:20 +0100 Subject: [PATCH 4/8] Update go-utils, assign profileutil functionalities to structs and integrate profileutil changes --- .../localcodesignasset/profileconverter.go | 7 +- .../localcodesignasset/profileprovider.go | 11 +- .../codesign_group_provider.go | 5 +- .../exportoptionsgenerator.go | 2 +- exportoptionsgenerator/profiles.go | 13 +- go.mod | 4 +- go.sum | 4 +- mocks/PathModifier.go | 24 +- mocks/PathProvider.go | 36 ++- profileutil/capabilities.go | 66 ------ profileutil/info_model.go | 210 +++--------------- profileutil/plist_data.go | 50 ++++- profileutil/profile_printer.go | 82 +++++++ profileutil/profile_reader.go | 152 +++++++++++++ profileutil/time_provider.go | 13 ++ profileutil/util.go | 159 ++++++------- xcarchive/ios.go | 10 +- xcarchive/macos.go | 10 +- 18 files changed, 496 insertions(+), 362 deletions(-) delete mode 100644 profileutil/capabilities.go create mode 100644 profileutil/profile_printer.go create mode 100644 profileutil/profile_reader.go create mode 100644 profileutil/time_provider.go diff --git a/autocodesign/localcodesignasset/profileconverter.go b/autocodesign/localcodesignasset/profileconverter.go index d7f4bc26..958b607d 100644 --- a/autocodesign/localcodesignasset/profileconverter.go +++ b/autocodesign/localcodesignasset/profileconverter.go @@ -3,6 +3,9 @@ package localcodesignasset import ( "os" + "github.com/bitrise-io/go-utils/v2/fileutil" + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/profileutil" ) @@ -22,7 +25,9 @@ func NewProvisioningProfileConverter() ProvisioningProfileConverter { // ProfileInfoToProfile ... func (c provisioningProfileConverter) ProfileInfoToProfile(info profileutil.ProvisioningProfileInfoModel) (autocodesign.Profile, error) { - _, pth, err := profileutil.FindProvisioningProfile(info.UUID) + // TODO: wire in as a dep on the struct + profileReader := profileutil.NewProfileReader(log.NewLogger(), fileutil.NewFileManager(), pathutil.NewPathModifier(), pathutil.NewPathProvider(), pathutil.NewPathChecker()) + _, pth, err := profileReader.FindProvisioningProfile(info.UUID) if err != nil { return nil, err } diff --git a/autocodesign/localcodesignasset/profileprovider.go b/autocodesign/localcodesignasset/profileprovider.go index 971ecb25..668d65f7 100644 --- a/autocodesign/localcodesignasset/profileprovider.go +++ b/autocodesign/localcodesignasset/profileprovider.go @@ -1,6 +1,11 @@ package localcodesignasset -import "github.com/bitrise-io/go-xcode/v2/profileutil" +import ( + "github.com/bitrise-io/go-utils/v2/fileutil" + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-utils/v2/pathutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" +) // ProvisioningProfileProvider can list profile infos. type ProvisioningProfileProvider interface { @@ -16,5 +21,7 @@ func NewProvisioningProfileProvider() ProvisioningProfileProvider { // ListProvisioningProfiles ... func (p provisioningProfileProvider) ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error) { - return profileutil.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos) + // TODO: wire in as a dep on the struct + profileReader := profileutil.NewProfileReader(log.NewLogger(), fileutil.NewFileManager(), pathutil.NewPathModifier(), pathutil.NewPathProvider(), pathutil.NewPathChecker()) + return profileReader.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos) } diff --git a/exportoptionsgenerator/codesign_group_provider.go b/exportoptionsgenerator/codesign_group_provider.go index 07073a79..2e24c25e 100644 --- a/exportoptionsgenerator/codesign_group_provider.go +++ b/exportoptionsgenerator/codesign_group_provider.go @@ -1,8 +1,6 @@ package exportoptionsgenerator import ( - "time" - "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/export" @@ -51,7 +49,8 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate g.logger.Debugf("Installed profiles:") for _, profileInfo := range profiles { - g.logger.Debugf(profileInfo.String(time.Now, certificates...)) + profileStr := profileutil.NewProfilePrinter(g.logger, profileutil.DefaultTimeProvider{}).PrintableProfile(profileInfo, certificates...) + g.logger.Debugf(profileStr) } g.logger.Printf("Resolving CodeSignGroups...") diff --git a/exportoptionsgenerator/exportoptionsgenerator.go b/exportoptionsgenerator/exportoptionsgenerator.go index 66771866..b80bae42 100644 --- a/exportoptionsgenerator/exportoptionsgenerator.go +++ b/exportoptionsgenerator/exportoptionsgenerator.go @@ -42,7 +42,7 @@ func New(xcodeVersionReader xcodeversion.Reader, logger log.Logger) ExportOption return ExportOptionsGenerator{ xcodeVersionReader: xcodeVersionReader, certificateProvider: LocalCodesignIdentityProvider{}, - profileProvider: LocalProvisioningProfileProvider{}, + profileProvider: LocalProvisioningProfileProvider{logger: logger}, codeSignGroupProvider: NewCodeSignGroupProvider(logger), logger: logger, } diff --git a/exportoptionsgenerator/profiles.go b/exportoptionsgenerator/profiles.go index 30dab9a4..5b494172 100644 --- a/exportoptionsgenerator/profiles.go +++ b/exportoptionsgenerator/profiles.go @@ -6,8 +6,9 @@ import ( "os" "path/filepath" - "github.com/bitrise-io/go-utils/pathutil" + "github.com/bitrise-io/go-utils/v2/fileutil" "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/profileutil" ) @@ -24,7 +25,9 @@ type LocalProvisioningProfileProvider struct { // ListProvisioningProfiles ... func (p LocalProvisioningProfileProvider) ListProvisioningProfiles() ([]profileutil.ProvisioningProfileInfoModel, error) { - return profileutil.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos) + // TODO: wire in as a dep on the struct + profileReader := profileutil.NewProfileReader(p.logger, fileutil.NewFileManager(), pathutil.NewPathModifier(), pathutil.NewPathProvider(), pathutil.NewPathChecker()) + return profileReader.InstalledProvisioningProfileInfos(profileutil.ProfileTypeIos) } // GetDefaultProvisioningProfile ... @@ -34,7 +37,7 @@ func (p LocalProvisioningProfileProvider) GetDefaultProvisioningProfile() (profi return profileutil.ProvisioningProfileInfoModel{}, nil } - tmpDir, err := pathutil.NormalizedOSTempDirPath("tmp_default_profile") + tmpDir, err := pathutil.NewPathProvider().CreateTempDir("tmp_default_profile") if err != nil { return profileutil.ProvisioningProfileInfoModel{}, err } @@ -64,7 +67,9 @@ func (p LocalProvisioningProfileProvider) GetDefaultProvisioningProfile() (profi return profileutil.ProvisioningProfileInfoModel{}, err } - defaultProfile, err := profileutil.NewProvisioningProfileInfoFromFile(tmpDst) + // TODO: wire in as a dep on the struct + profileReader := profileutil.NewProfileReader(p.logger, fileutil.NewFileManager(), pathutil.NewPathModifier(), pathutil.NewPathProvider(), pathutil.NewPathChecker()) + defaultProfile, err := profileReader.ProvisioningProfileInfoFromFile(tmpDst) if err != nil { return profileutil.ProvisioningProfileInfoModel{}, err } diff --git a/go.mod b/go.mod index 511325c4..e3b572e7 100644 --- a/go.mod +++ b/go.mod @@ -11,8 +11,9 @@ require ( github.com/bitrise-io/go-steputils v1.0.5 github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.18 github.com/bitrise-io/go-utils v1.0.12 - github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28 + github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.29 github.com/bitrise-io/go-xcode v1.3.1 + github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa github.com/globocom/go-buffer/v2 v2.0.0 github.com/golang-jwt/jwt/v4 v4.5.0 github.com/google/go-querystring v1.1.0 @@ -43,7 +44,6 @@ require ( github.com/envoyproxy/go-control-plane/envoy v1.32.3 // indirect github.com/envoyproxy/protoc-gen-validate v1.1.0 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect github.com/gofrs/uuid/v5 v5.2.0 // indirect diff --git a/go.sum b/go.sum index 9cd1d565..4bd25f99 100644 --- a/go.sum +++ b/go.sum @@ -43,8 +43,8 @@ github.com/bitrise-io/go-steputils/v2 v2.0.0-alpha.18/go.mod h1:/ueNOKnsjcUrlt8C github.com/bitrise-io/go-utils v1.0.1/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY= github.com/bitrise-io/go-utils v1.0.12 h1:iJV1ZpyvSA0NCte/N6x+aIQ9TrNr5sIBlcJBf0dn1dE= github.com/bitrise-io/go-utils v1.0.12/go.mod h1:ZY1DI+fEpZuFpO9szgDeICM4QbqoWVt0RSY3tRI1heY= -github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28 h1:FYfmK7VG4bLGp9n4dSyTbxjpXQh9CQyWazn4aBWnTjk= -github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.28/go.mod h1:3XUplo0dOWc3DqT2XA2SeHToDSg7+j1y1HTHibT2H68= +github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.29 h1:KHtWG4SBPh1nQ/yKLeYYaY2mcjg1Nu/9OABW8mdDCs8= +github.com/bitrise-io/go-utils/v2 v2.0.0-alpha.29/go.mod h1:3XUplo0dOWc3DqT2XA2SeHToDSg7+j1y1HTHibT2H68= github.com/bitrise-io/go-xcode v1.3.1 h1:ioLPHQ+XnSafCpnFJl+d9+qdfIr0Z55yQRlEA66/XxI= github.com/bitrise-io/go-xcode v1.3.1/go.mod h1:9OwsvrhZ4A2JxHVoEY7CPcABAKA+OE7FQqFfBfvbFuY= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= diff --git a/mocks/PathModifier.go b/mocks/PathModifier.go index 53fc2f24..450c478b 100644 --- a/mocks/PathModifier.go +++ b/mocks/PathModifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package mocks @@ -13,6 +13,10 @@ type PathModifier struct { func (_m *PathModifier) AbsPath(pth string) (string, error) { ret := _m.Called(pth) + if len(ret) == 0 { + panic("no return value specified for AbsPath") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string) (string, error)); ok { @@ -33,6 +37,24 @@ func (_m *PathModifier) AbsPath(pth string) (string, error) { return r0, r1 } +// EscapeGlobPath provides a mock function with given fields: path +func (_m *PathModifier) EscapeGlobPath(path string) string { + ret := _m.Called(path) + + if len(ret) == 0 { + panic("no return value specified for EscapeGlobPath") + } + + var r0 string + if rf, ok := ret.Get(0).(func(string) string); ok { + r0 = rf(path) + } else { + r0 = ret.Get(0).(string) + } + + return r0 +} + // NewPathModifier creates a new instance of PathModifier. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewPathModifier(t interface { diff --git a/mocks/PathProvider.go b/mocks/PathProvider.go index 0cc2f8ad..3e1288f3 100644 --- a/mocks/PathProvider.go +++ b/mocks/PathProvider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.30.16. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package mocks @@ -13,6 +13,10 @@ type PathProvider struct { func (_m *PathProvider) CreateTempDir(prefix string) (string, error) { ret := _m.Called(prefix) + if len(ret) == 0 { + panic("no return value specified for CreateTempDir") + } + var r0 string var r1 error if rf, ok := ret.Get(0).(func(string) (string, error)); ok { @@ -33,6 +37,36 @@ func (_m *PathProvider) CreateTempDir(prefix string) (string, error) { return r0, r1 } +// Glob provides a mock function with given fields: pattern +func (_m *PathProvider) Glob(pattern string) ([]string, error) { + ret := _m.Called(pattern) + + if len(ret) == 0 { + panic("no return value specified for Glob") + } + + var r0 []string + var r1 error + if rf, ok := ret.Get(0).(func(string) ([]string, error)); ok { + return rf(pattern) + } + if rf, ok := ret.Get(0).(func(string) []string); ok { + r0 = rf(pattern) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).([]string) + } + } + + if rf, ok := ret.Get(1).(func(string) error); ok { + r1 = rf(pattern) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + // NewPathProvider creates a new instance of PathProvider. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. // The first argument is typically a *testing.T value. func NewPathProvider(t interface { diff --git a/profileutil/capabilities.go b/profileutil/capabilities.go deleted file mode 100644 index 3e5b850b..00000000 --- a/profileutil/capabilities.go +++ /dev/null @@ -1,66 +0,0 @@ -package profileutil - -import ( - "github.com/bitrise-io/go-utils/log" - "github.com/bitrise-io/go-xcode/plistutil" -) - -// MatchTargetAndProfileEntitlements ... -func MatchTargetAndProfileEntitlements(targetEntitlements plistutil.PlistData, profileEntitlements plistutil.PlistData, profileType ProfileType) []string { - missingEntitlements := []string{} - - for key := range targetEntitlements { - _, known := KnownProfileCapabilitiesMap[profileType][key] - if !known { - continue - } - _, found := profileEntitlements[key] - if !found { - missingEntitlements = append(missingEntitlements, key) - } - } - - log.Debugf("Found %v entitlements from %v target", len(missingEntitlements), len(targetEntitlements)) - - return missingEntitlements -} - -// KnownProfileCapabilitiesMap ... -var KnownProfileCapabilitiesMap = map[ProfileType]map[string]bool{ - ProfileTypeMacOs: map[string]bool{ - "com.apple.developer.networking.networkextension": true, - "com.apple.developer.icloud-container-environment": true, - "com.apple.developer.icloud-container-development-container-identifiers": true, - "com.apple.developer.aps-environment": true, - "keychain-access-groups": true, - "com.apple.developer.icloud-services": true, - "com.apple.developer.icloud-container-identifiers": true, - "com.apple.developer.networking.vpn.api": true, - "com.apple.developer.ubiquity-kvstore-identifier": true, - "com.apple.developer.ubiquity-container-identifiers": true, - "com.apple.developer.game-center": true, - "com.apple.application-identifier": true, - "com.apple.developer.team-identifier": true, - "com.apple.developer.maps": true, - }, - ProfileTypeIos: map[string]bool{ - "com.apple.developer.in-app-payments": true, - "com.apple.security.application-groups": true, - "com.apple.developer.default-data-protection": true, - "com.apple.developer.healthkit": true, - "com.apple.developer.homekit": true, - "com.apple.developer.networking.HotspotConfiguration": true, - "inter-app-audio": true, - "keychain-access-groups": true, - "com.apple.developer.networking.multipath": true, - "com.apple.developer.nfc.readersession.formats": true, - "com.apple.developer.networking.networkextension": true, - "aps-environment": true, - "com.apple.developer.associated-domains": true, - "com.apple.developer.siri": true, - "com.apple.developer.networking.vpn.api": true, - "com.apple.external-accessory.wireless-configuration": true, - "com.apple.developer.pass-type-identifiers": true, - "com.apple.developer.icloud-container-identifiers": true, - }, -} diff --git a/profileutil/info_model.go b/profileutil/info_model.go index b2329619..92b04d30 100644 --- a/profileutil/info_model.go +++ b/profileutil/info_model.go @@ -1,21 +1,26 @@ package profileutil import ( - "crypto/x509" - "encoding/json" - "errors" "fmt" - "strings" "time" "github.com/bitrise-io/go-plist" - "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/v2/plistutil" "github.com/fullsailor/pkcs7" ) +// ProfileType ... +type ProfileType string + +// ProfileTypes ... +const ( + ProfileTypeIos ProfileType = "ios" + ProfileTypeMacOs ProfileType = "osx" + ProfileTypeTvOs ProfileType = "tvos" +) + // ProvisioningProfileInfoModel ... type ProvisioningProfileInfoModel struct { UUID string @@ -33,51 +38,36 @@ type ProvisioningProfileInfoModel struct { Type ProfileType } -// PrintableProvisioningProfileInfo ... -func (info ProvisioningProfileInfoModel) String(currentTime func() time.Time, installedCertificates ...certificateutil.CertificateInfoModel) string { - printable := map[string]interface{}{} - printable["name"] = fmt.Sprintf("%s (%s)", info.Name, info.UUID) - printable["export_type"] = string(info.ExportType) - printable["team"] = fmt.Sprintf("%s (%s)", info.TeamName, info.TeamID) - printable["bundle_id"] = info.BundleID - printable["expiry"] = info.ExpirationDate.String() - printable["is_xcode_managed"] = info.IsXcodeManaged() - - printable["capabilities"] = collectCapabilitiesPrintableInfo(info.Entitlements) - - if info.ProvisionedDevices != nil { - printable["devices"] = info.ProvisionedDevices - } - - certificates := []map[string]interface{}{} - for _, certificateInfo := range info.DeveloperCertificates { - certificate := map[string]interface{}{} - certificate["name"] = certificateInfo.CommonName - certificate["serial"] = certificateInfo.Serial - certificate["team_id"] = certificateInfo.TeamID - certificates = append(certificates, certificate) - } - printable["certificates"] = certificates - - errors := []string{} - if installedCertificates != nil && !info.HasInstalledCertificate(installedCertificates) { - errors = append(errors, "none of the profile's certificates are installed") +// NewProvisioningProfileInfo ... +func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { + var data plistutil.PlistData + if _, err := plist.Unmarshal(provisioningProfile.Content, &data); err != nil { + return ProvisioningProfileInfoModel{}, err } + profile := PlistData(data) - if err := info.CheckValidity(currentTime); err != nil { - errors = append(errors, err.Error()) - } - if len(errors) > 0 { - printable["errors"] = errors + profileType, err := profile.GetProfileType() + if err != nil { + return ProvisioningProfileInfoModel{}, err } - data, err := json.MarshalIndent(printable, "", "\t") - if err != nil { - log.Errorf("Failed to marshal: %v, error: %s", printable, err) - return "" + info := ProvisioningProfileInfoModel{ + Type: profileType, + UUID: profile.GetUUID(), + Name: profile.GetName(), + TeamName: profile.GetTeamName(), + TeamID: profile.GetTeamID(), + BundleID: profile.GetBundleIdentifier(), + CreationDate: profile.GetCreationDate(), + ExpirationDate: profile.GetExpirationDate(), + ProvisionsAllDevices: profile.GetProvisionsAllDevices(), + ExportType: profile.GetExportMethod(), + ProvisionedDevices: profile.GetProvisionedDevices(), + DeveloperCertificates: profile.GetDeveloperCertificateInfo(), + Entitlements: profile.GetEntitlements(), } - return string(data) + return info, nil } // IsXcodeManaged ... @@ -89,7 +79,7 @@ func (info ProvisioningProfileInfoModel) IsXcodeManaged() bool { func (info ProvisioningProfileInfoModel) CheckValidity(currentTime func() time.Time) error { timeNow := currentTime() if !timeNow.Before(info.ExpirationDate) { - return fmt.Errorf("Provisioning Profile is not valid anymore - validity ended at: %s", info.ExpirationDate) + return fmt.Errorf("provisioning profile is not valid anymore, validity ended at: %s", info.ExpirationDate) } return nil } @@ -107,131 +97,3 @@ func (info ProvisioningProfileInfoModel) HasInstalledCertificate(installedCertif } return has } - -// IsXcodeManaged ... -func IsXcodeManaged(profileName string) bool { - if strings.HasPrefix(profileName, "XC") { - return true - } - if strings.Contains(profileName, "Provisioning Profile") { - if strings.HasPrefix(profileName, "iOS Team") || - strings.HasPrefix(profileName, "Mac Catalyst Team") || - strings.HasPrefix(profileName, "tvOS Team") || - strings.HasPrefix(profileName, "Mac Team") { - return true - } - } - return false -} - -// NewProvisioningProfileInfo ... -func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { - var data plistutil.PlistData - if _, err := plist.Unmarshal(provisioningProfile.Content, &data); err != nil { - return ProvisioningProfileInfoModel{}, err - } - - platforms, _ := data.GetStringArray("Platform") - if len(platforms) == 0 { - return ProvisioningProfileInfoModel{}, fmt.Errorf("missing Platform array in profile") - } - - platform := strings.ToLower(platforms[0]) - var profileType ProfileType - - switch platform { - case string(ProfileTypeIos): - profileType = ProfileTypeIos - case string(ProfileTypeMacOs): - profileType = ProfileTypeMacOs - case string(ProfileTypeTvOs): - profileType = ProfileTypeTvOs - default: - return ProvisioningProfileInfoModel{}, fmt.Errorf("unknown platform type: %s", platform) - } - - profile := PlistData(data) - info := ProvisioningProfileInfoModel{ - UUID: profile.GetUUID(), - Name: profile.GetName(), - TeamName: profile.GetTeamName(), - TeamID: profile.GetTeamID(), - BundleID: profile.GetBundleIdentifier(), - CreationDate: profile.GetCreationDate(), - ExpirationDate: profile.GetExpirationDate(), - ProvisionsAllDevices: profile.GetProvisionsAllDevices(), - Type: profileType, - } - - info.ExportType = profile.GetExportMethod() - - if devicesList := profile.GetProvisionedDevices(); devicesList != nil { - info.ProvisionedDevices = devicesList - } - - developerCertificates, found := data.GetByteArrayArray("DeveloperCertificates") - if found { - certificates := []*x509.Certificate{} - for _, certificateBytes := range developerCertificates { - certificate, err := certificateutil.CertificateFromDERContent(certificateBytes) - if err == nil && certificate != nil { - certificates = append(certificates, certificate) - } - } - - for _, certificate := range certificates { - if certificate != nil { - info.DeveloperCertificates = append(info.DeveloperCertificates, certificateutil.NewCertificateInfo(*certificate, nil)) - } - } - } - - info.Entitlements = profile.GetEntitlements() - - return info, nil -} - -// NewProvisioningProfileInfoFromFile ... -func NewProvisioningProfileInfoFromFile(pth string) (ProvisioningProfileInfoModel, error) { - provisioningProfile, err := ProvisioningProfileFromFile(pth) - if err != nil { - return ProvisioningProfileInfoModel{}, err - } - if provisioningProfile != nil { - return NewProvisioningProfileInfo(*provisioningProfile) - } - return ProvisioningProfileInfoModel{}, errors.New("failed to parse provisioning profile infos") -} - -// InstalledProvisioningProfileInfos ... -func InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningProfileInfoModel, error) { - provisioningProfiles, err := InstalledProvisioningProfiles(profileType) - if err != nil { - return nil, err - } - - infos := []ProvisioningProfileInfoModel{} - for _, provisioningProfile := range provisioningProfiles { - if provisioningProfile != nil { - info, err := NewProvisioningProfileInfo(*provisioningProfile) - if err != nil { - return nil, err - } - infos = append(infos, info) - } - } - return infos, nil -} - -func collectCapabilitiesPrintableInfo(entitlements plistutil.PlistData) map[string]interface{} { - capabilities := map[string]interface{}{} - - for key, value := range entitlements { - if KnownProfileCapabilitiesMap[ProfileTypeIos][key] || - KnownProfileCapabilitiesMap[ProfileTypeMacOs][key] { - capabilities[key] = value - } - } - - return capabilities -} diff --git a/profileutil/plist_data.go b/profileutil/plist_data.go index 56467962..677803dd 100644 --- a/profileutil/plist_data.go +++ b/profileutil/plist_data.go @@ -1,30 +1,40 @@ package profileutil import ( + "fmt" "strings" "time" - "github.com/bitrise-io/go-plist" + "github.com/bitrise-io/go-xcode/certificateutil" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/plistutil" + "github.com/bitrise-io/go-xcode/v2/plistutil" ) // PlistData ... type PlistData plistutil.PlistData -// NewPlistDataFromFile ... -func NewPlistDataFromFile(provisioningProfilePth string) (PlistData, error) { - provisioningProfilePKCS7, err := ProvisioningProfileFromFile(provisioningProfilePth) - if err != nil { - return PlistData{}, err +func (profile PlistData) GetProfileType() (ProfileType, error) { + data := plistutil.PlistData(profile) + platforms, _ := data.GetStringArray("Platform") + if len(platforms) == 0 { + return "", fmt.Errorf("missing Platform array in profile") } - var plistData plistutil.PlistData - if _, err := plist.Unmarshal(provisioningProfilePKCS7.Content, &plistData); err != nil { - return PlistData{}, err + platform := strings.ToLower(platforms[0]) + var profileType ProfileType + + switch platform { + case string(ProfileTypeIos): + profileType = ProfileTypeIos + case string(ProfileTypeMacOs): + profileType = ProfileTypeMacOs + case string(ProfileTypeTvOs): + profileType = ProfileTypeTvOs + default: + return "", fmt.Errorf("unknown platform type: %s", platform) } - return PlistData(plistData), nil + return profileType, nil } // GetUUID ... @@ -149,6 +159,24 @@ func (profile PlistData) GetDeveloperCertificates() [][]byte { return developerCertificates } +// GetDeveloperCertificateInfo ... +func (profile PlistData) GetDeveloperCertificateInfo() []certificateutil.CertificateInfoModel { + certificateBytesList := profile.GetDeveloperCertificates() + + var certificateInfos []certificateutil.CertificateInfoModel + for _, certificateBytes := range certificateBytesList { + certificate, err := certificateutil.CertificateFromDERContent(certificateBytes) + if err != nil || certificate == nil { + continue + } + + certificateInfo := certificateutil.NewCertificateInfo(*certificate, nil) + certificateInfos = append(certificateInfos, certificateInfo) + } + + return certificateInfos +} + // GetTeamName ... func (profile PlistData) GetTeamName() string { data := plistutil.PlistData(profile) diff --git a/profileutil/profile_printer.go b/profileutil/profile_printer.go new file mode 100644 index 00000000..bd4a1b6e --- /dev/null +++ b/profileutil/profile_printer.go @@ -0,0 +1,82 @@ +package profileutil + +import ( + "encoding/json" + "fmt" + + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/v2/plistutil" +) + +type ProfilePrinter struct { + logger log.Logger + timeProvider TimeProvider +} + +func NewProfilePrinter(logger log.Logger, timeProvider TimeProvider) *ProfilePrinter { + return &ProfilePrinter{ + logger: logger, + timeProvider: timeProvider, + } +} + +// PrintableProfile ... +func (printer ProfilePrinter) PrintableProfile(profile ProvisioningProfileInfoModel, installedCertificates ...certificateutil.CertificateInfoModel) string { + printable := map[string]interface{}{} + printable["name"] = fmt.Sprintf("%s (%s)", profile.Name, profile.UUID) + printable["export_type"] = string(profile.ExportType) + printable["team"] = fmt.Sprintf("%s (%s)", profile.TeamName, profile.TeamID) + printable["bundle_id"] = profile.BundleID + printable["expiry"] = profile.ExpirationDate.String() + printable["is_xcode_managed"] = profile.IsXcodeManaged() + + printable["capabilities"] = collectCapabilitiesPrintableInfo(profile.Entitlements) + + if profile.ProvisionedDevices != nil { + printable["devices"] = profile.ProvisionedDevices + } + + var certificates []map[string]interface{} + for _, certificateInfo := range profile.DeveloperCertificates { + certificate := map[string]interface{}{} + certificate["name"] = certificateInfo.CommonName + certificate["serial"] = certificateInfo.Serial + certificate["team_id"] = certificateInfo.TeamID + certificates = append(certificates, certificate) + } + printable["certificates"] = certificates + + var errs []string + if installedCertificates != nil && !profile.HasInstalledCertificate(installedCertificates) { + errs = append(errs, "none of the profile's certificates are installed") + } + + if err := profile.CheckValidity(printer.timeProvider.Now); err != nil { + errs = append(errs, err.Error()) + } + if len(errs) > 0 { + printable["errors"] = errs + } + + data, err := json.MarshalIndent(printable, "", "\t") + if err != nil { + printer.logger.Errorf("Failed to marshal: %v, error: %s", printable, err) + return "" + } + + return string(data) +} + +func collectCapabilitiesPrintableInfo(entitlements plistutil.PlistData) map[string]interface{} { + capabilities := map[string]interface{}{} + + for key, value := range entitlements { + if KnownProfileCapabilitiesMap[ProfileTypeIos][key] || + KnownProfileCapabilitiesMap[ProfileTypeMacOs][key] { + capabilities[key] = value + } + } + + return capabilities +} diff --git a/profileutil/profile_reader.go b/profileutil/profile_reader.go new file mode 100644 index 00000000..1e91ed2e --- /dev/null +++ b/profileutil/profile_reader.go @@ -0,0 +1,152 @@ +package profileutil + +import ( + "errors" + "io" + "path/filepath" + + "github.com/bitrise-io/go-utils/v2/fileutil" + "github.com/bitrise-io/go-utils/v2/log" + "github.com/bitrise-io/go-utils/v2/pathutil" + "github.com/fullsailor/pkcs7" +) + +// ProvProfileSystemDirPath ... +const ProvProfileSystemDirPath = "~/Library/MobileDevice/Provisioning Profiles" + +type ProfileReader struct { + logger log.Logger + fileManager fileutil.FileManager + pathModifier pathutil.PathModifier + pathProvider pathutil.PathProvider + pathChecker pathutil.PathChecker +} + +func NewProfileReader(logger log.Logger, fileManager fileutil.FileManager, pathModifier pathutil.PathModifier, pathProvider pathutil.PathProvider, pathChecker pathutil.PathChecker) ProfileReader { + return ProfileReader{ + logger: logger, + fileManager: fileManager, + pathModifier: pathModifier, + pathProvider: pathProvider, + pathChecker: pathChecker, + } +} + +// ProvisioningProfileFromFile ... +func (reader ProfileReader) ProvisioningProfileFromFile(pth string) (*pkcs7.PKCS7, error) { + f, err := reader.fileManager.Open(pth) + if err != nil { + return nil, err + } + defer func() { + if err := f.Close(); err != nil { + reader.logger.Warnf("Failed to close file %s, error: %s", pth, err) + } + }() + + content, err := io.ReadAll(f) + if err != nil { + return nil, err + } + return ProvisioningProfileFromContent(content) +} + +// InstalledProvisioningProfiles ... +func (reader ProfileReader) InstalledProvisioningProfiles(profileType ProfileType) ([]*pkcs7.PKCS7, error) { + ext := ".mobileprovision" + if profileType == ProfileTypeMacOs { + ext = ".provisionprofile" + } + + absProvProfileDirPath, err := reader.pathModifier.AbsPath(ProvProfileSystemDirPath) + if err != nil { + return nil, err + } + + pattern := filepath.Join(reader.pathModifier.EscapeGlobPath(absProvProfileDirPath), "*"+ext) + pths, err := reader.pathProvider.Glob(pattern) + if err != nil { + return nil, err + } + + var profiles []*pkcs7.PKCS7 + for _, pth := range pths { + profile, err := reader.ProvisioningProfileFromFile(pth) + if err != nil { + return nil, err + } + profiles = append(profiles, profile) + } + return profiles, nil +} + +// FindProvisioningProfile ... +func (reader ProfileReader) FindProvisioningProfile(uuid string) (*pkcs7.PKCS7, string, error) { + absProvProfileDirPath, err := reader.pathModifier.AbsPath(ProvProfileSystemDirPath) + if err != nil { + return nil, "", err + } + + iosProvisioningProfileExt := ".mobileprovision" + pth := filepath.Join(absProvProfileDirPath, uuid+iosProvisioningProfileExt) + if exist, err := reader.pathChecker.IsPathExists(pth); err != nil { + return nil, "", err + } else if exist { + profile, err := reader.ProvisioningProfileFromFile(pth) + if err != nil { + return nil, "", err + } + return profile, pth, nil + } + + macOsProvisioningProfileExt := ".provisionprofile" + pth = filepath.Join(absProvProfileDirPath, uuid+macOsProvisioningProfileExt) + if exist, err := reader.pathChecker.IsPathExists(pth); err != nil { + return nil, "", err + } else if exist { + profile, err := reader.ProvisioningProfileFromFile(pth) + if err != nil { + return nil, "", err + } + return profile, pth, nil + } + + return nil, "", nil +} + +// ProvisioningProfileInfoFromFile ... +func (reader ProfileReader) ProvisioningProfileInfoFromFile(pth string) (ProvisioningProfileInfoModel, error) { + provisioningProfile, err := reader.ProvisioningProfileFromFile(pth) + if err != nil { + return ProvisioningProfileInfoModel{}, err + } + if provisioningProfile != nil { + return NewProvisioningProfileInfo(*provisioningProfile) + } + return ProvisioningProfileInfoModel{}, errors.New("failed to parse provisioning profile infos") +} + +// InstalledProvisioningProfileInfos ... +func (reader ProfileReader) InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningProfileInfoModel, error) { + provisioningProfiles, err := reader.InstalledProvisioningProfiles(profileType) + if err != nil { + return nil, err + } + + var infos []ProvisioningProfileInfoModel + for _, provisioningProfile := range provisioningProfiles { + if provisioningProfile != nil { + info, err := NewProvisioningProfileInfo(*provisioningProfile) + if err != nil { + return nil, err + } + infos = append(infos, info) + } + } + return infos, nil +} + +// ProvisioningProfileFromContent ... +func ProvisioningProfileFromContent(content []byte) (*pkcs7.PKCS7, error) { + return pkcs7.Parse(content) +} diff --git a/profileutil/time_provider.go b/profileutil/time_provider.go new file mode 100644 index 00000000..1c80a4ee --- /dev/null +++ b/profileutil/time_provider.go @@ -0,0 +1,13 @@ +package profileutil + +import "time" + +type TimeProvider interface { + Now() time.Time +} + +type DefaultTimeProvider struct{} + +func (DefaultTimeProvider) Now() time.Time { + return time.Now() +} diff --git a/profileutil/util.go b/profileutil/util.go index 015d4780..a11dff09 100644 --- a/profileutil/util.go +++ b/profileutil/util.go @@ -1,110 +1,85 @@ package profileutil import ( - "path/filepath" + "strings" - "github.com/bitrise-io/go-utils/fileutil" - "github.com/bitrise-io/go-utils/pathutil" - "github.com/fullsailor/pkcs7" + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-xcode/v2/plistutil" ) -// ProfileType ... -type ProfileType string - -// ProfileTypeIos ... -const ProfileTypeIos ProfileType = "ios" - -// ProfileTypeMacOs ... -const ProfileTypeMacOs ProfileType = "osx" - -// ProfileTypeTvOs ... -const ProfileTypeTvOs ProfileType = "tvos" - -// ProvProfileSystemDirPath ... -const ProvProfileSystemDirPath = "~/Library/MobileDevice/Provisioning Profiles" - -// ProvisioningProfileFromContent ... -func ProvisioningProfileFromContent(content []byte) (*pkcs7.PKCS7, error) { - return pkcs7.Parse(content) -} - -// ProvisioningProfileFromFile ... -func ProvisioningProfileFromFile(pth string) (*pkcs7.PKCS7, error) { - content, err := fileutil.ReadBytesFromFile(pth) - if err != nil { - return nil, err +// IsXcodeManaged ... +func IsXcodeManaged(profileName string) bool { + if strings.HasPrefix(profileName, "XC") { + return true } - return ProvisioningProfileFromContent(content) -} - -// InstalledProvisioningProfiles ... -func InstalledProvisioningProfiles(profileType ProfileType) ([]*pkcs7.PKCS7, error) { - ext := ".mobileprovision" - if profileType == ProfileTypeMacOs { - ext = ".provisionprofile" - } - - absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) - if err != nil { - return nil, err - } - - pattern := filepath.Join(pathutil.EscapeGlobPath(absProvProfileDirPath), "*"+ext) - pths, err := filepath.Glob(pattern) - if err != nil { - return nil, err - } - - profiles := []*pkcs7.PKCS7{} - for _, pth := range pths { - profile, err := ProvisioningProfileFromFile(pth) - if err != nil { - return nil, err + if strings.Contains(profileName, "Provisioning Profile") { + if strings.HasPrefix(profileName, "iOS Team") || + strings.HasPrefix(profileName, "Mac Catalyst Team") || + strings.HasPrefix(profileName, "tvOS Team") || + strings.HasPrefix(profileName, "Mac Team") { + return true } - profiles = append(profiles, profile) } - return profiles, nil + return false } -// FindProvisioningProfile ... -func FindProvisioningProfile(uuid string) (*pkcs7.PKCS7, string, error) { - { - iosProvisioningProfileExt := ".mobileprovision" - absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) - if err != nil { - return nil, "", err - } +// MatchTargetAndProfileEntitlements ... +func MatchTargetAndProfileEntitlements(targetEntitlements plistutil.PlistData, profileEntitlements plistutil.PlistData, profileType ProfileType) []string { + missingEntitlements := []string{} - pth := filepath.Join(absProvProfileDirPath, uuid+iosProvisioningProfileExt) - if exist, err := pathutil.IsPathExists(pth); err != nil { - return nil, "", err - } else if exist { - profile, err := ProvisioningProfileFromFile(pth) - if err != nil { - return nil, "", err - } - return profile, pth, nil + for key := range targetEntitlements { + _, known := KnownProfileCapabilitiesMap[profileType][key] + if !known { + continue + } + _, found := profileEntitlements[key] + if !found { + missingEntitlements = append(missingEntitlements, key) } } - { - macOsProvisioningProfileExt := ".provisionprofile" - absProvProfileDirPath, err := pathutil.AbsPath(ProvProfileSystemDirPath) - if err != nil { - return nil, "", err - } + // TODO: migrate to logger + log.Debugf("Found %v entitlements from %v target", len(missingEntitlements), len(targetEntitlements)) - pth := filepath.Join(absProvProfileDirPath, uuid+macOsProvisioningProfileExt) - if exist, err := pathutil.IsPathExists(pth); err != nil { - return nil, "", err - } else if exist { - profile, err := ProvisioningProfileFromFile(pth) - if err != nil { - return nil, "", err - } - return profile, pth, nil - } - } + return missingEntitlements +} - return nil, "", nil +// KnownProfileCapabilitiesMap ... +var KnownProfileCapabilitiesMap = map[ProfileType]map[string]bool{ + ProfileTypeMacOs: map[string]bool{ + "com.apple.developer.networking.networkextension": true, + "com.apple.developer.icloud-container-environment": true, + "com.apple.developer.icloud-container-development-container-identifiers": true, + "com.apple.developer.aps-environment": true, + "keychain-access-groups": true, + "com.apple.developer.icloud-services": true, + "com.apple.developer.icloud-container-identifiers": true, + "com.apple.developer.networking.vpn.api": true, + "com.apple.developer.ubiquity-kvstore-identifier": true, + "com.apple.developer.ubiquity-container-identifiers": true, + "com.apple.developer.game-center": true, + "com.apple.application-identifier": true, + "com.apple.developer.team-identifier": true, + "com.apple.developer.maps": true, + }, + ProfileTypeIos: map[string]bool{ + "com.apple.developer.in-app-payments": true, + "com.apple.security.application-groups": true, + "com.apple.developer.default-data-protection": true, + "com.apple.developer.healthkit": true, + "com.apple.developer.homekit": true, + "com.apple.developer.networking.HotspotConfiguration": true, + "inter-app-audio": true, + "keychain-access-groups": true, + "com.apple.developer.networking.multipath": true, + "com.apple.developer.nfc.readersession.formats": true, + "com.apple.developer.networking.networkextension": true, + "aps-environment": true, + "com.apple.developer.associated-domains": true, + "com.apple.developer.siri": true, + "com.apple.developer.networking.vpn.api": true, + "com.apple.external-accessory.wireless-configuration": true, + "com.apple.developer.pass-type-identifiers": true, + "com.apple.developer.icloud-container-identifiers": true, + }, } diff --git a/xcarchive/ios.go b/xcarchive/ios.go index 07f3fa67..86d97d0a 100644 --- a/xcarchive/ios.go +++ b/xcarchive/ios.go @@ -7,6 +7,8 @@ import ( "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" + "github.com/bitrise-io/go-utils/v2/fileutil" + "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/plistutil" @@ -29,9 +31,15 @@ func (app IosBaseApplication) BundleIdentifier() string { // NewIosBaseApplication ... func NewIosBaseApplication(path string) (IosBaseApplication, error) { + // TODO: wire in as a dep on the struct + logger := log.NewLogger() + fileManager := fileutil.NewFileManager() + pathModifier := pathutil.NewPathModifier() + pathProvider := pathutil.NewPathProvider() pathChecker := pathutil.NewPathChecker() envRepo := env.NewRepository() cmdFactory := command.NewFactory(envRepo) + profileReader := profileutil.NewProfileReader(logger, fileManager, pathModifier, pathProvider, pathChecker) var infoPlist plistutil.PlistData { @@ -57,7 +65,7 @@ func NewIosBaseApplication(path string) (IosBaseApplication, error) { return IosBaseApplication{}, fmt.Errorf("profile not exists at: %s", provisioningProfilePath) } - profile, err := profileutil.NewProvisioningProfileInfoFromFile(provisioningProfilePath) + profile, err := profileReader.ProvisioningProfileInfoFromFile(provisioningProfilePath) if err != nil { return IosBaseApplication{}, err } diff --git a/xcarchive/macos.go b/xcarchive/macos.go index c443c4b7..16da3c31 100644 --- a/xcarchive/macos.go +++ b/xcarchive/macos.go @@ -6,6 +6,8 @@ import ( "github.com/bitrise-io/go-utils/v2/command" "github.com/bitrise-io/go-utils/v2/env" + "github.com/bitrise-io/go-utils/v2/fileutil" + "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/plistutil" "github.com/bitrise-io/go-xcode/v2/profileutil" @@ -25,9 +27,15 @@ func (app macosBaseApplication) BundleIdentifier() string { } func newMacosBaseApplication(path string) (macosBaseApplication, error) { + // TODO: wire in as a dep on the struct + logger := log.NewLogger() + fileManager := fileutil.NewFileManager() + pathModifier := pathutil.NewPathModifier() + pathProvider := pathutil.NewPathProvider() pathChecker := pathutil.NewPathChecker() envRepo := env.NewRepository() cmdFactory := command.NewFactory(envRepo) + profileReader := profileutil.NewProfileReader(logger, fileManager, pathModifier, pathProvider, pathChecker) var infoPlist plistutil.PlistData { @@ -50,7 +58,7 @@ func newMacosBaseApplication(path string) (macosBaseApplication, error) { if exist, err := pathChecker.IsPathExists(provisioningProfilePath); err != nil { return macosBaseApplication{}, fmt.Errorf("failed to check if profile exists at: %s, error: %s", provisioningProfilePath, err) } else if exist { - profile, err := profileutil.NewProvisioningProfileInfoFromFile(provisioningProfilePath) + profile, err := profileReader.ProvisioningProfileInfoFromFile(provisioningProfilePath) if err != nil { return macosBaseApplication{}, err } From c574c57bbcaae062410a8a765c28ebc29df93189 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Fri, 30 Jan 2026 12:02:31 +0100 Subject: [PATCH 5/8] Fix lint issues --- profileutil/plist_data.go | 1 + profileutil/profile_printer.go | 2 ++ profileutil/time_provider.go | 3 +++ 3 files changed, 6 insertions(+) diff --git a/profileutil/plist_data.go b/profileutil/plist_data.go index 677803dd..0fd3e4e8 100644 --- a/profileutil/plist_data.go +++ b/profileutil/plist_data.go @@ -13,6 +13,7 @@ import ( // PlistData ... type PlistData plistutil.PlistData +// GetProfileType ... func (profile PlistData) GetProfileType() (ProfileType, error) { data := plistutil.PlistData(profile) platforms, _ := data.GetStringArray("Platform") diff --git a/profileutil/profile_printer.go b/profileutil/profile_printer.go index bd4a1b6e..c959e82e 100644 --- a/profileutil/profile_printer.go +++ b/profileutil/profile_printer.go @@ -9,11 +9,13 @@ import ( "github.com/bitrise-io/go-xcode/v2/plistutil" ) +// ProfilePrinter ... type ProfilePrinter struct { logger log.Logger timeProvider TimeProvider } +// NewProfilePrinter ... func NewProfilePrinter(logger log.Logger, timeProvider TimeProvider) *ProfilePrinter { return &ProfilePrinter{ logger: logger, diff --git a/profileutil/time_provider.go b/profileutil/time_provider.go index 1c80a4ee..3943f51c 100644 --- a/profileutil/time_provider.go +++ b/profileutil/time_provider.go @@ -2,12 +2,15 @@ package profileutil import "time" +// TimeProvider ... type TimeProvider interface { Now() time.Time } +// DefaultTimeProvider ... type DefaultTimeProvider struct{} +// Now ... func (DefaultTimeProvider) Now() time.Time { return time.Now() } From be33b34271a58ca419421357dc43653e5d5f5b0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Fri, 30 Jan 2026 12:57:21 +0100 Subject: [PATCH 6/8] Fix lint issues --- profileutil/profile_reader.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/profileutil/profile_reader.go b/profileutil/profile_reader.go index 1e91ed2e..5e010a7f 100644 --- a/profileutil/profile_reader.go +++ b/profileutil/profile_reader.go @@ -14,6 +14,7 @@ import ( // ProvProfileSystemDirPath ... const ProvProfileSystemDirPath = "~/Library/MobileDevice/Provisioning Profiles" +// ProfileReader ... type ProfileReader struct { logger log.Logger fileManager fileutil.FileManager @@ -22,6 +23,7 @@ type ProfileReader struct { pathChecker pathutil.PathChecker } +// NewProfileReader ... func NewProfileReader(logger log.Logger, fileManager fileutil.FileManager, pathModifier pathutil.PathModifier, pathProvider pathutil.PathProvider, pathChecker pathutil.PathChecker) ProfileReader { return ProfileReader{ logger: logger, From 0d50ad77bc7c9a858129cca696df9fd805363ec4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Fri, 30 Jan 2026 14:33:35 +0100 Subject: [PATCH 7/8] Code cleanup --- artifacts/ipa_reader.go | 7 +------ .../profiledownloader/profiledownloader.go | 7 +------ autocodesign/profiles.go | 14 ++------------ profileutil/info_model.go | 14 ++++++++++++-- 4 files changed, 16 insertions(+), 26 deletions(-) diff --git a/artifacts/ipa_reader.go b/artifacts/ipa_reader.go index 0185327f..61a2964c 100644 --- a/artifacts/ipa_reader.go +++ b/artifacts/ipa_reader.go @@ -24,12 +24,7 @@ func (reader IPAReader) ProvisioningProfileInfo() (*profileutil.ProvisioningProf return nil, err } - profilePKCS7, err := profileutil.ProvisioningProfileFromContent(b) - if err != nil { - return nil, fmt.Errorf("failed to parse embedded.mobilprovision: %w", err) - } - - provisioningProfileInfo, err := profileutil.NewProvisioningProfileInfo(*profilePKCS7) + provisioningProfileInfo, err := profileutil.NewProvisioningProfileInfoFromPKCS7Content(b) if err != nil { return nil, fmt.Errorf("failed to read profile info: %w", err) } diff --git a/autocodesign/profiledownloader/profiledownloader.go b/autocodesign/profiledownloader/profiledownloader.go index 08c06590..3a9ddcb6 100644 --- a/autocodesign/profiledownloader/profiledownloader.go +++ b/autocodesign/profiledownloader/profiledownloader.go @@ -49,12 +49,7 @@ func (d downloader) GetProfiles() ([]autocodesign.LocalProfile, error) { return nil, fmt.Errorf("profile (%s) is empty", url) } - parsedProfile, err := profileutil.ProvisioningProfileFromContent(content) - if err != nil { - return nil, fmt.Errorf("invalid pkcs7 file format: %w", err) - } - - profileInfo, err := profileutil.NewProvisioningProfileInfo(*parsedProfile) + profileInfo, err := profileutil.NewProvisioningProfileInfoFromPKCS7Content(content) if err != nil { return nil, fmt.Errorf("unknown provisioning profile format: %w", err) } diff --git a/autocodesign/profiles.go b/autocodesign/profiles.go index f5e06a34..ee3ca6a5 100644 --- a/autocodesign/profiles.go +++ b/autocodesign/profiles.go @@ -381,12 +381,7 @@ func checkProfileEntitlements(client DevPortalClient, prof Profile, appEntitleme // ParseRawProfileDeviceUDIDs reads the device UDIDs from the provisioning profile. func ParseRawProfileDeviceUDIDs(profileContents []byte) (DeviceUDIDs, error) { - pkcs, err := profileutil.ProvisioningProfileFromContent(profileContents) - if err != nil { - return nil, fmt.Errorf("failed to parse pkcs7 from profile content: %s", err) - } - - profile, err := profileutil.NewProvisioningProfileInfo(*pkcs) + profile, err := profileutil.NewProvisioningProfileInfoFromPKCS7Content(profileContents) if err != nil { return nil, fmt.Errorf("failed to parse profile info from pkcs7 content: %s", err) } @@ -396,12 +391,7 @@ func ParseRawProfileDeviceUDIDs(profileContents []byte) (DeviceUDIDs, error) { // ParseRawProfileEntitlements ... func ParseRawProfileEntitlements(profileContents []byte) (Entitlements, error) { - pkcs, err := profileutil.ProvisioningProfileFromContent(profileContents) - if err != nil { - return nil, fmt.Errorf("failed to parse pkcs7 from profile content: %s", err) - } - - profile, err := profileutil.NewProvisioningProfileInfo(*pkcs) + profile, err := profileutil.NewProvisioningProfileInfoFromPKCS7Content(profileContents) if err != nil { return nil, fmt.Errorf("failed to parse profile info from pkcs7 content: %s", err) } diff --git a/profileutil/info_model.go b/profileutil/info_model.go index 92b04d30..a34dc761 100644 --- a/profileutil/info_model.go +++ b/profileutil/info_model.go @@ -39,9 +39,9 @@ type ProvisioningProfileInfoModel struct { } // NewProvisioningProfileInfo ... -func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { +func NewProvisioningProfileInfo(profilePKCS7 pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { var data plistutil.PlistData - if _, err := plist.Unmarshal(provisioningProfile.Content, &data); err != nil { + if _, err := plist.Unmarshal(profilePKCS7.Content, &data); err != nil { return ProvisioningProfileInfoModel{}, err } profile := PlistData(data) @@ -70,6 +70,16 @@ func NewProvisioningProfileInfo(provisioningProfile pkcs7.PKCS7) (ProvisioningPr return info, nil } +// NewProvisioningProfileInfoFromPKCS7Content ... +func NewProvisioningProfileInfoFromPKCS7Content(content []byte) (ProvisioningProfileInfoModel, error) { + profilePKCS7, err := pkcs7.Parse(content) + if err != nil { + return ProvisioningProfileInfoModel{}, err + } + + return NewProvisioningProfileInfo(*profilePKCS7) +} + // IsXcodeManaged ... func (info ProvisioningProfileInfoModel) IsXcodeManaged() bool { return IsXcodeManaged(info.Name) From 010c45207e04d9fa8dab54b145bd43400e8c9341 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krisztia=CC=81n=20Go=CC=88drei?= Date: Fri, 30 Jan 2026 15:02:18 +0100 Subject: [PATCH 8/8] Cleanup unused functions --- .../localcodesignasset/profileconverter.go | 36 +++++- profileutil/profile_reader.go | 113 ++++++------------ 2 files changed, 67 insertions(+), 82 deletions(-) diff --git a/autocodesign/localcodesignasset/profileconverter.go b/autocodesign/localcodesignasset/profileconverter.go index 958b607d..57727442 100644 --- a/autocodesign/localcodesignasset/profileconverter.go +++ b/autocodesign/localcodesignasset/profileconverter.go @@ -2,9 +2,8 @@ package localcodesignasset import ( "os" + "path/filepath" - "github.com/bitrise-io/go-utils/v2/fileutil" - "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" "github.com/bitrise-io/go-xcode/v2/profileutil" @@ -25,9 +24,7 @@ func NewProvisioningProfileConverter() ProvisioningProfileConverter { // ProfileInfoToProfile ... func (c provisioningProfileConverter) ProfileInfoToProfile(info profileutil.ProvisioningProfileInfoModel) (autocodesign.Profile, error) { - // TODO: wire in as a dep on the struct - profileReader := profileutil.NewProfileReader(log.NewLogger(), fileutil.NewFileManager(), pathutil.NewPathModifier(), pathutil.NewPathProvider(), pathutil.NewPathChecker()) - _, pth, err := profileReader.FindProvisioningProfile(info.UUID) + pth, err := findProvisioningProfile(info.UUID) if err != nil { return nil, err } @@ -38,3 +35,32 @@ func (c provisioningProfileConverter) ProfileInfoToProfile(info profileutil.Prov return NewProfile(info, content), nil } + +func findProvisioningProfile(uuid string) (string, error) { + // TODO: wire in as a dep on the struct + pathModifier := pathutil.NewPathModifier() + pathChecker := pathutil.NewPathChecker() + + absProvProfileDirPath, err := pathModifier.AbsPath(profileutil.ProvProfileSystemDirPath) + if err != nil { + return "", err + } + + iosProvisioningProfileExt := ".mobileprovision" + pth := filepath.Join(absProvProfileDirPath, uuid+iosProvisioningProfileExt) + if exist, err := pathChecker.IsPathExists(pth); err != nil { + return "", err + } else if exist { + return pth, nil + } + + macOsProvisioningProfileExt := ".provisionprofile" + pth = filepath.Join(absProvProfileDirPath, uuid+macOsProvisioningProfileExt) + if exist, err := pathChecker.IsPathExists(pth); err != nil { + return "", err + } else if exist { + return pth, nil + } + + return "", nil +} diff --git a/profileutil/profile_reader.go b/profileutil/profile_reader.go index 5e010a7f..fd425dc3 100644 --- a/profileutil/profile_reader.go +++ b/profileutil/profile_reader.go @@ -34,8 +34,39 @@ func NewProfileReader(logger log.Logger, fileManager fileutil.FileManager, pathM } } -// ProvisioningProfileFromFile ... -func (reader ProfileReader) ProvisioningProfileFromFile(pth string) (*pkcs7.PKCS7, error) { +// ProvisioningProfileInfoFromFile ... +func (reader ProfileReader) ProvisioningProfileInfoFromFile(pth string) (ProvisioningProfileInfoModel, error) { + provisioningProfile, err := reader.provisioningProfileFromFile(pth) + if err != nil { + return ProvisioningProfileInfoModel{}, err + } + if provisioningProfile != nil { + return NewProvisioningProfileInfo(*provisioningProfile) + } + return ProvisioningProfileInfoModel{}, errors.New("failed to parse provisioning profile infos") +} + +// InstalledProvisioningProfileInfos ... +func (reader ProfileReader) InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningProfileInfoModel, error) { + provisioningProfiles, err := reader.installedProvisioningProfiles(profileType) + if err != nil { + return nil, err + } + + var infos []ProvisioningProfileInfoModel + for _, provisioningProfile := range provisioningProfiles { + if provisioningProfile != nil { + info, err := NewProvisioningProfileInfo(*provisioningProfile) + if err != nil { + return nil, err + } + infos = append(infos, info) + } + } + return infos, nil +} + +func (reader ProfileReader) provisioningProfileFromFile(pth string) (*pkcs7.PKCS7, error) { f, err := reader.fileManager.Open(pth) if err != nil { return nil, err @@ -50,11 +81,10 @@ func (reader ProfileReader) ProvisioningProfileFromFile(pth string) (*pkcs7.PKCS if err != nil { return nil, err } - return ProvisioningProfileFromContent(content) + return pkcs7.Parse(content) } -// InstalledProvisioningProfiles ... -func (reader ProfileReader) InstalledProvisioningProfiles(profileType ProfileType) ([]*pkcs7.PKCS7, error) { +func (reader ProfileReader) installedProvisioningProfiles(profileType ProfileType) ([]*pkcs7.PKCS7, error) { ext := ".mobileprovision" if profileType == ProfileTypeMacOs { ext = ".provisionprofile" @@ -73,7 +103,7 @@ func (reader ProfileReader) InstalledProvisioningProfiles(profileType ProfileTyp var profiles []*pkcs7.PKCS7 for _, pth := range pths { - profile, err := reader.ProvisioningProfileFromFile(pth) + profile, err := reader.provisioningProfileFromFile(pth) if err != nil { return nil, err } @@ -81,74 +111,3 @@ func (reader ProfileReader) InstalledProvisioningProfiles(profileType ProfileTyp } return profiles, nil } - -// FindProvisioningProfile ... -func (reader ProfileReader) FindProvisioningProfile(uuid string) (*pkcs7.PKCS7, string, error) { - absProvProfileDirPath, err := reader.pathModifier.AbsPath(ProvProfileSystemDirPath) - if err != nil { - return nil, "", err - } - - iosProvisioningProfileExt := ".mobileprovision" - pth := filepath.Join(absProvProfileDirPath, uuid+iosProvisioningProfileExt) - if exist, err := reader.pathChecker.IsPathExists(pth); err != nil { - return nil, "", err - } else if exist { - profile, err := reader.ProvisioningProfileFromFile(pth) - if err != nil { - return nil, "", err - } - return profile, pth, nil - } - - macOsProvisioningProfileExt := ".provisionprofile" - pth = filepath.Join(absProvProfileDirPath, uuid+macOsProvisioningProfileExt) - if exist, err := reader.pathChecker.IsPathExists(pth); err != nil { - return nil, "", err - } else if exist { - profile, err := reader.ProvisioningProfileFromFile(pth) - if err != nil { - return nil, "", err - } - return profile, pth, nil - } - - return nil, "", nil -} - -// ProvisioningProfileInfoFromFile ... -func (reader ProfileReader) ProvisioningProfileInfoFromFile(pth string) (ProvisioningProfileInfoModel, error) { - provisioningProfile, err := reader.ProvisioningProfileFromFile(pth) - if err != nil { - return ProvisioningProfileInfoModel{}, err - } - if provisioningProfile != nil { - return NewProvisioningProfileInfo(*provisioningProfile) - } - return ProvisioningProfileInfoModel{}, errors.New("failed to parse provisioning profile infos") -} - -// InstalledProvisioningProfileInfos ... -func (reader ProfileReader) InstalledProvisioningProfileInfos(profileType ProfileType) ([]ProvisioningProfileInfoModel, error) { - provisioningProfiles, err := reader.InstalledProvisioningProfiles(profileType) - if err != nil { - return nil, err - } - - var infos []ProvisioningProfileInfoModel - for _, provisioningProfile := range provisioningProfiles { - if provisioningProfile != nil { - info, err := NewProvisioningProfileInfo(*provisioningProfile) - if err != nil { - return nil, err - } - infos = append(infos, info) - } - } - return infos, nil -} - -// ProvisioningProfileFromContent ... -func ProvisioningProfileFromContent(content []byte) (*pkcs7.PKCS7, error) { - return pkcs7.Parse(content) -}