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..61a2964c 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 ... @@ -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/autocodesign.go b/autocodesign/autocodesign.go index ea8aca9c..3b021b67 100644 --- a/autocodesign/autocodesign.go +++ b/autocodesign/autocodesign.go @@ -12,9 +12,9 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-utils/v2/retry" "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..57727442 100644 --- a/autocodesign/localcodesignasset/profileconverter.go +++ b/autocodesign/localcodesignasset/profileconverter.go @@ -2,9 +2,11 @@ package localcodesignasset import ( "os" + "path/filepath" - "github.com/bitrise-io/go-xcode/profileutil" + "github.com/bitrise-io/go-utils/v2/pathutil" "github.com/bitrise-io/go-xcode/v2/autocodesign" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // ProvisioningProfileConverter ... @@ -22,7 +24,7 @@ func NewProvisioningProfileConverter() ProvisioningProfileConverter { // ProfileInfoToProfile ... func (c provisioningProfileConverter) ProfileInfoToProfile(info profileutil.ProvisioningProfileInfoModel) (autocodesign.Profile, error) { - _, pth, err := profileutil.FindProvisioningProfile(info.UUID) + pth, err := findProvisioningProfile(info.UUID) if err != nil { return nil, err } @@ -33,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/autocodesign/localcodesignasset/profilelookup.go b/autocodesign/localcodesignasset/profilelookup.go index a2ea24de..be20160a 100644 --- a/autocodesign/localcodesignasset/profilelookup.go +++ b/autocodesign/localcodesignasset/profilelookup.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "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..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/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/autocodesign/profiledownloader/profiledownloader.go b/autocodesign/profiledownloader/profiledownloader.go index fd01cf4f..1bd6b800 100644 --- a/autocodesign/profiledownloader/profiledownloader.go +++ b/autocodesign/profiledownloader/profiledownloader.go @@ -11,9 +11,9 @@ 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/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 { @@ -65,12 +65,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 f3c12a04..4746c921 100644 --- a/autocodesign/profiles.go +++ b/autocodesign/profiles.go @@ -11,8 +11,8 @@ import ( v1log "github.com/bitrise-io/go-utils/log" "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-utils/v2/retry" - "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" ) @@ -386,12 +386,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) } @@ -401,12 +396,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/codesign/codesign.go b/codesign/codesign.go index acedb441..92bb0d18 100644 --- a/codesign/codesign.go +++ b/codesign/codesign.go @@ -9,7 +9,6 @@ import ( "github.com/bitrise-io/go-utils/v2/retry" "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" @@ -18,6 +17,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" ) @@ -509,7 +509,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) } @@ -543,7 +543,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 2ef05ee5..737fcffe 100644 --- a/exportoptionsgenerator/codesign_group_provider.go +++ b/exportoptionsgenerator/codesign_group_provider.go @@ -4,9 +4,10 @@ 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" + profileutilv1 "github.com/bitrise-io/go-xcode/profileutil" codesigngroup "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // CodeSignGroupProvider ... @@ -49,12 +50,13 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate g.logger.Debugf("Installed profiles:") for _, profileInfo := range profiles { - g.logger.Debugf(profileInfo.String(certificates...)) + profileStr := profileutil.NewProfilePrinter(g.logger, profileutil.DefaultTimeProvider{}).PrintableProfile(profileInfo, certificates...) + g.logger.Debugf(profileStr) } g.logger.Println() g.logger.Printf("Resolving code signing groups...") - codeSignGroups := codesigngroup.BuildFilterableList(certificates, profiles, bundleIDs) + codeSignGroups := codesigngroup.BuildFilterableList(certificates, profileutil.V1Profiles(profiles), bundleIDs) if len(codeSignGroups) == 0 { g.logger.Errorf("Failed to find code signing groups for specified export method (%s)", exportMethod) } @@ -113,7 +115,7 @@ func (g codeSignGroupProvider) DetermineCodesignGroup(certificates []certificate var iosCodeSignGroups []codesigngroup.Ios 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 e17f70c2..c8aad34d 100644 --- a/exportoptionsgenerator/exportoptionsgenerator.go +++ b/exportoptionsgenerator/exportoptionsgenerator.go @@ -6,9 +6,9 @@ import ( "github.com/bitrise-io/go-utils/v2/log" "github.com/bitrise-io/go-xcode/exportoptions" - "github.com/bitrise-io/go-xcode/profileutil" "github.com/bitrise-io/go-xcode/v2/exportoptionsgenerator/internal/codesigngroup" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" "github.com/bitrise-io/go-xcode/v2/xcodeversion" ) @@ -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/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..5b494172 100644 --- a/exportoptionsgenerator/profiles.go +++ b/exportoptionsgenerator/profiles.go @@ -6,9 +6,10 @@ 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-xcode/profileutil" + "github.com/bitrise-io/go-utils/v2/pathutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) // ProvisioningProfileProvider can list profile infos. @@ -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/mocks/PathModifier.go b/mocks/PathModifier.go index 591e0364..450c478b 100644 --- a/mocks/PathModifier.go +++ b/mocks/PathModifier.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package mocks diff --git a/mocks/PathProvider.go b/mocks/PathProvider.go index d79cd309..3e1288f3 100644 --- a/mocks/PathProvider.go +++ b/mocks/PathProvider.go @@ -1,4 +1,4 @@ -// Code generated by mockery v2.53.3. DO NOT EDIT. +// Code generated by mockery v2.53.4. DO NOT EDIT. package mocks diff --git a/profileutil/info_model.go b/profileutil/info_model.go new file mode 100644 index 00000000..a34dc761 --- /dev/null +++ b/profileutil/info_model.go @@ -0,0 +1,109 @@ +package profileutil + +import ( + "fmt" + "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/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 + 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 +} + +// NewProvisioningProfileInfo ... +func NewProvisioningProfileInfo(profilePKCS7 pkcs7.PKCS7) (ProvisioningProfileInfoModel, error) { + var data plistutil.PlistData + if _, err := plist.Unmarshal(profilePKCS7.Content, &data); err != nil { + return ProvisioningProfileInfoModel{}, err + } + profile := PlistData(data) + + profileType, err := profile.GetProfileType() + if err != nil { + return ProvisioningProfileInfoModel{}, err + } + + 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 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) +} + +// CheckValidity ... +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 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 +} 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..0fd3e4e8 --- /dev/null +++ b/profileutil/plist_data.go @@ -0,0 +1,200 @@ +package profileutil + +import ( + "fmt" + "strings" + "time" + + "github.com/bitrise-io/go-xcode/certificateutil" + "github.com/bitrise-io/go-xcode/exportoptions" + "github.com/bitrise-io/go-xcode/v2/plistutil" +) + +// PlistData ... +type PlistData plistutil.PlistData + +// GetProfileType ... +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") + } + + 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 profileType, 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 +} + +// 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) + 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/profile_printer.go b/profileutil/profile_printer.go new file mode 100644 index 00000000..c959e82e --- /dev/null +++ b/profileutil/profile_printer.go @@ -0,0 +1,84 @@ +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" +) + +// ProfilePrinter ... +type ProfilePrinter struct { + logger log.Logger + timeProvider TimeProvider +} + +// NewProfilePrinter ... +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..fd425dc3 --- /dev/null +++ b/profileutil/profile_reader.go @@ -0,0 +1,113 @@ +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" + +// ProfileReader ... +type ProfileReader struct { + logger log.Logger + fileManager fileutil.FileManager + pathModifier pathutil.PathModifier + pathProvider pathutil.PathProvider + 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, + fileManager: fileManager, + pathModifier: pathModifier, + pathProvider: pathProvider, + pathChecker: pathChecker, + } +} + +// 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 + } + 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 pkcs7.Parse(content) +} + +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 +} diff --git a/profileutil/time_provider.go b/profileutil/time_provider.go new file mode 100644 index 00000000..3943f51c --- /dev/null +++ b/profileutil/time_provider.go @@ -0,0 +1,16 @@ +package profileutil + +import "time" + +// TimeProvider ... +type TimeProvider interface { + Now() time.Time +} + +// DefaultTimeProvider ... +type DefaultTimeProvider struct{} + +// Now ... +func (DefaultTimeProvider) Now() time.Time { + return time.Now() +} diff --git a/profileutil/util.go b/profileutil/util.go new file mode 100644 index 00000000..a11dff09 --- /dev/null +++ b/profileutil/util.go @@ -0,0 +1,85 @@ +package profileutil + +import ( + "strings" + + "github.com/bitrise-io/go-utils/log" + "github.com/bitrise-io/go-xcode/v2/plistutil" +) + +// 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 +} + +// 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) + } + } + + // TODO: migrate to logger + 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/v1_adapter.go b/profileutil/v1_adapter.go new file mode 100644 index 00000000..5a4d3f79 --- /dev/null +++ b/profileutil/v1_adapter.go @@ -0,0 +1,72 @@ +package profileutil + +import ( + profileutilv1 "github.com/bitrise-io/go-xcode/profileutil" +) + +// V2Profile ... +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), + } +} + +// V1Profiles ... +func V1Profiles(models []ProvisioningProfileInfoModel) []profileutilv1.ProvisioningProfileInfoModel { + profiles := make([]profileutilv1.ProvisioningProfileInfoModel, len(models)) + for i, model := range models { + profiles[i] = V1Profile(model) + } + return profiles +} + +// V1Profile ... +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..86d97d0a 100644 --- a/xcarchive/ios.go +++ b/xcarchive/ios.go @@ -7,10 +7,12 @@ 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/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 ... @@ -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/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..16da3c31 100644 --- a/xcarchive/macos.go +++ b/xcarchive/macos.go @@ -6,9 +6,11 @@ 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/profileutil" "github.com/bitrise-io/go-xcode/v2/plistutil" + "github.com/bitrise-io/go-xcode/v2/profileutil" ) type macosBaseApplication struct { @@ -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 }