From f22ee304f369e81dee7ab28140c9c0d8f14f827e Mon Sep 17 00:00:00 2001 From: Maxwell Elliott Date: Tue, 20 Aug 2024 12:55:50 -0400 Subject: [PATCH] Adds enterprise support Allows sign-here to use a flag into it execution to enable enterprise support --- .bazelrc | 4 +- MODULE.bazel.lock | 8 +- Makefile | 7 + .../CreateProvisioningProfileCommand.swift | 22 +-- .../DeleteProvisioningProfileCommand.swift | 22 +-- .../Services/iTunesConnectService.swift | 144 +++++++++--------- ...reateProvisioningProfileCommandTests.swift | 5 +- ...eleteProvisioningProfileCommandTests.swift | 7 +- ...tServiceTests_test_createCertificate.1.txt | 2 +- ...teCertificate_unableToDecodeResponse.1.txt | 2 +- ...nnectServiceTests_test_createProfile.1.txt | 2 +- ...sts_test_createProfile_decodingError.1.txt | 2 +- ...est_createProfile_iosAppStoreProfile.1.txt | 2 +- ...s_test_createProfile_withProfileName.1.txt | 2 +- ...ficates_privateKeyMatches_enterprise.1.txt | 25 +++ ...ficates_privateKeyMatches_enterprise.2.txt | 3 + ...ficates_privateKeyMatches_enterprise.3.txt | 8 + .../iTunesConnectServiceTests.swift | 66 +++++++- 18 files changed, 226 insertions(+), 107 deletions(-) create mode 100644 Makefile create mode 100644 Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.1.txt create mode 100644 Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.2.txt create mode 100644 Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.3.txt diff --git a/.bazelrc b/.bazelrc index aac68a8..7aa78e9 100644 --- a/.bazelrc +++ b/.bazelrc @@ -1,3 +1,5 @@ +common --enable_bzlmod + build --incompatible_disallow_empty_glob build --apple_platform_type=macos build --incompatible_strict_action_env @@ -8,4 +10,4 @@ build --crosstool_top=@local_config_apple_cc//:toolchain build --host_crosstool_top=@local_config_apple_cc//:toolchain test --test_output=errors test --test_summary=detailed -common --enable_bzlmod +test:record_snapshots --spawn_strategy=local diff --git a/MODULE.bazel.lock b/MODULE.bazel.lock index 2f67297..310c65f 100644 --- a/MODULE.bazel.lock +++ b/MODULE.bazel.lock @@ -44,8 +44,8 @@ "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", "https://bcr.bazel.build/modules/rules_cc/0.0.9/source.json": "1f1ba6fea244b616de4a554a0f4983c91a9301640c8fe0dd1d410254115c8430", "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", - "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", - "https://bcr.bazel.build/modules/rules_java/7.6.1/source.json": "8f3f3076554e1558e8e468b2232991c510ecbcbed9e6f8c06ac31c93bcf38362", + "https://bcr.bazel.build/modules/rules_java/7.6.5/MODULE.bazel": "481164be5e02e4cab6e77a36927683263be56b7e36fef918b458d7a8a1ebadb1", + "https://bcr.bazel.build/modules/rules_java/7.6.5/source.json": "a805b889531d1690e3c72a7a7e47a870d00323186a9904b36af83aa3d053ee8d", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/source.json": "a075731e1b46bc8425098512d038d416e966ab19684a10a34f4741295642fc35", "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", @@ -74,8 +74,8 @@ "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/source.json": "f1ef7d3f9e0e26d4b23d1c39b5f5de71f584dd7d1b4ef83d9bbba6ec7a6a6459", "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", "https://bcr.bazel.build/modules/zlib/1.2.12/MODULE.bazel": "3b1a8834ada2a883674be8cbd36ede1b6ec481477ada359cd2d3ddc562340b27", - "https://bcr.bazel.build/modules/zlib/1.3/MODULE.bazel": "6a9c02f19a24dcedb05572b2381446e27c272cd383aed11d41d99da9e3167a72", - "https://bcr.bazel.build/modules/zlib/1.3/source.json": "b6b43d0737af846022636e6e255fd4a96fee0d34f08f3830e6e0bac51465c37c" + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", + "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d" }, "selectedYankedVersions": {}, "moduleExtensions": { diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..1d10140 --- /dev/null +++ b/Makefile @@ -0,0 +1,7 @@ +.PHONY: record_snapshots +record_snapshots: + bazel test //Tests/... \ + --config=record_snapshots \ + --test_env=BUILD_WORKSPACE_DIRECTORY=$$(pwd) \ + --test_env=SNAPSHOT_DIRECTORY="$$(pwd)/Tests/SignHereLibraryTests" \ + --test_env=RERECORD_SNAPSHOTS=TRUE diff --git a/Sources/SignHereLibrary/Commands/CreateProvisioningProfileCommand.swift b/Sources/SignHereLibrary/Commands/CreateProvisioningProfileCommand.swift index 0eb986f..5552bf5 100644 --- a/Sources/SignHereLibrary/Commands/CreateProvisioningProfileCommand.swift +++ b/Sources/SignHereLibrary/Commands/CreateProvisioningProfileCommand.swift @@ -126,6 +126,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { case certificateSigningRequestSubject = "certificateSigningRequestSubject" case profileName = "profileName" case autoRegenerate = "autoRegenerate" + case enterprise = "enterprise" } @Option(help: "The key identifier of the private key (https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests)") @@ -189,6 +190,9 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { @Flag(help: "Defines if the profile should be regenerated in case it already exists (optional)") internal var autoRegenerate = false + @Flag(help: "Controls if the enterprise API should be used.") + internal var enterprise: Bool = false + private let files: Files private let log: Log private let shell: Shell @@ -206,10 +210,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { shell = shellImp uuid = UUIDImp() iTunesConnectService = iTunesConnectServiceImp( - network: NetworkImp(), - files: filesImp, - shell: shellImp, - clock: clockImp + enterprise: false ) } @@ -236,7 +237,8 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { bundleIdentifierName: String?, platform: String, profileName: String?, - autoRegenerate: Bool + autoRegenerate: Bool, + enterprise: Bool ) { self.files = files self.log = log @@ -261,6 +263,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { self.platform = platform self.profileName = profileName self.autoRegenerate = autoRegenerate + self.enterprise = enterprise } internal init(from decoder: Decoder) throws { @@ -268,6 +271,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { let clockImp: Clock = ClockImp() let shellImp: Shell = ShellImp() let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + let enterprise: Bool = try container.decode(Bool.self, forKey: .enterprise) self.init( files: filesImp, log: LogImp(), @@ -275,10 +279,7 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { shell: shellImp, uuid: UUIDImp(), iTunesConnectService: iTunesConnectServiceImp( - network: NetworkImp(), - files: filesImp, - shell: shellImp, - clock: clockImp + enterprise: enterprise ), keyIdentifier: try container.decode(String.self, forKey: .keyIdentifier), issuerID: try container.decode(String.self, forKey: .issuerID), @@ -296,7 +297,8 @@ internal struct CreateProvisioningProfileCommand: ParsableCommand { bundleIdentifierName: try container.decodeIfPresent(String.self, forKey: .bundleIdentifierName), platform: try container.decode(String.self, forKey: .platform), profileName: try container.decodeIfPresent(String.self, forKey: .profileName), - autoRegenerate: try container.decode(Bool.self, forKey: .autoRegenerate) + autoRegenerate: try container.decode(Bool.self, forKey: .autoRegenerate), + enterprise: enterprise ) } diff --git a/Sources/SignHereLibrary/Commands/DeleteProvisioningProfileCommand.swift b/Sources/SignHereLibrary/Commands/DeleteProvisioningProfileCommand.swift index f1d2cc1..da4b29b 100644 --- a/Sources/SignHereLibrary/Commands/DeleteProvisioningProfileCommand.swift +++ b/Sources/SignHereLibrary/Commands/DeleteProvisioningProfileCommand.swift @@ -24,6 +24,7 @@ internal struct DeleteProvisioningProfileCommand: ParsableCommand { case keyIdentifier = "keyIdentifier" case issuerID = "issuerID" case itunesConnectKeyPath = "itunesConnectKeyPath" + case enterprise = "enterprise" } @Option(help: "The iTunes Connect API ID of the provisioning profile to delete (https://developer.apple.com/documentation/appstoreconnectapi/profile)") @@ -38,6 +39,9 @@ internal struct DeleteProvisioningProfileCommand: ParsableCommand { @Option(help: "The path to the private key (https://developer.apple.com/documentation/appstoreconnectapi/generating_tokens_for_api_requests)") internal var itunesConnectKeyPath: String + @Flag(help: "Controls if the enterprise API should be used.") + internal var enterprise: Bool = false + private let files: Files private let jsonWebTokenService: JSONWebTokenService private let iTunesConnectService: iTunesConnectService @@ -47,10 +51,7 @@ internal struct DeleteProvisioningProfileCommand: ParsableCommand { files = filesImp jsonWebTokenService = JSONWebTokenServiceImp(clock: ClockImp()) iTunesConnectService = iTunesConnectServiceImp( - network: NetworkImp(), - files: filesImp, - shell: ShellImp(), - clock: ClockImp() + enterprise: false ) } @@ -61,7 +62,8 @@ internal struct DeleteProvisioningProfileCommand: ParsableCommand { provisioningProfileId: String, keyIdentifier: String, issuerID: String, - itunesConnectKeyPath: String + itunesConnectKeyPath: String, + enterprise: Bool ) { self.files = files self.jsonWebTokenService = jsonWebTokenService @@ -70,24 +72,24 @@ internal struct DeleteProvisioningProfileCommand: ParsableCommand { self.keyIdentifier = keyIdentifier self.issuerID = issuerID self.itunesConnectKeyPath = itunesConnectKeyPath + self.enterprise = enterprise } internal init(from decoder: Decoder) throws { let filesImp: Files = FilesImp() let container: KeyedDecodingContainer = try decoder.container(keyedBy: CodingKeys.self) + let enterprise: Bool = try container.decode(Bool.self, forKey: .enterprise) self.init( files: filesImp, jsonWebTokenService: JSONWebTokenServiceImp(clock: ClockImp()), iTunesConnectService: iTunesConnectServiceImp( - network: NetworkImp(), - files: filesImp, - shell: ShellImp(), - clock: ClockImp() + enterprise: enterprise ), provisioningProfileId: try container.decode(String.self, forKey: .provisioningProfileId), keyIdentifier: try container.decode(String.self, forKey: .keyIdentifier), issuerID: try container.decode(String.self, forKey: .issuerID), - itunesConnectKeyPath: try container.decode(String.self, forKey: .itunesConnectKeyPath) + itunesConnectKeyPath: try container.decode(String.self, forKey: .itunesConnectKeyPath), + enterprise: enterprise ) } diff --git a/Sources/SignHereLibrary/Services/iTunesConnectService.swift b/Sources/SignHereLibrary/Services/iTunesConnectService.swift index 97def27..6d8695a 100644 --- a/Sources/SignHereLibrary/Services/iTunesConnectService.swift +++ b/Sources/SignHereLibrary/Services/iTunesConnectService.swift @@ -115,23 +115,39 @@ internal class iTunesConnectServiceImp: iTunesConnectService { static let contentTypeHeaderName: String = "Content-Type" static let httpsScheme: String = "https" static let itcHost: String = "api.appstoreconnect.apple.com" + static let enterpriseHost: String = "api.enterprise.developer.apple.com" } private let network: Network private let files: Files private let shell: Shell private let clock: Clock + private let enterprise: Bool init( network: Network, files: Files, shell: Shell, - clock: Clock + clock: Clock, + enterprise: Bool ) { self.network = network self.files = files self.shell = shell self.clock = clock + self.enterprise = enterprise + } + + convenience init( + enterprise: Bool + ) { + self.init( + network: NetworkImp(), + files: FilesImp(), + shell: ShellImp(), + clock: ClockImp(), + enterprise: enterprise + ) } func fetchActiveCertificates( @@ -141,18 +157,13 @@ internal class iTunesConnectServiceImp: iTunesConnectService { certificateType: String ) throws -> [DownloadCertificateResponse.DownloadCertificateResponseData] { let currentDate: Date = clock.now() - var urlComponents: URLComponents = .init() - urlComponents.scheme = Constants.httpsScheme - urlComponents.host = Constants.itcHost - urlComponents.path = "/v1/certificates" - urlComponents.queryItems = [ - .init(name: "filter[certificateType]", value: certificateType), - .init(name: "limit", value: "200") - ] - guard let url: URL = urlComponents.url - else { - throw Error.unableToCreateURL(urlComponents: urlComponents) - } + let url: URL = try createRequestURL( + path: "/v1/certificates", + queryItems: [ + .init(name: "filter[certificateType]", value: certificateType), + .init(name: "limit", value: "200") + ] + ) var certificatesData: [DownloadCertificateResponse.DownloadCertificateResponseData] = [] var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") @@ -198,11 +209,9 @@ internal class iTunesConnectServiceImp: iTunesConnectService { csr: Path, certificateType: String ) throws -> CreateCertificateResponse { - let urlString: String = "https://api.appstoreconnect.apple.com/v1/certificates" - guard let url: URL = .init(string: urlString) - else { - throw Error.invalidURL(string: urlString) - } + let url: URL = try createRequestURL( + path: "/v1/certificates" + ) var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") request.httpBody = try JSONEncoder().encode( @@ -236,19 +245,14 @@ internal class iTunesConnectServiceImp: iTunesConnectService { bundleIdentifierName: String?, platform: String ) throws -> String { - var urlComponents: URLComponents = .init() - urlComponents.scheme = Constants.httpsScheme - urlComponents.host = Constants.itcHost - urlComponents.path = "/v1/bundleIds" - urlComponents.queryItems = [ - .init(name: "filter[identifier]", value: bundleIdentifier), - .init(name: "filter[platform]", value: "IOS"), - .init(name: "limit", value: "200") - ] - guard let url: URL = urlComponents.url - else { - throw Error.unableToCreateURL(urlComponents: urlComponents) - } + let url: URL = try createRequestURL( + path: "/v1/bundleIds", + queryItems: [ + .init(name: "filter[identifier]", value: bundleIdentifier), + .init(name: "filter[platform]", value: "IOS"), + .init(name: "limit", value: "200") + ] + ) var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: "Accept") @@ -297,19 +301,14 @@ internal class iTunesConnectServiceImp: iTunesConnectService { } func fetchITCDeviceIDs(jsonWebToken: String) throws -> Set { - var urlComponents: URLComponents = .init() - urlComponents.scheme = Constants.httpsScheme - urlComponents.host = Constants.itcHost - urlComponents.path = "/v1/devices" - urlComponents.queryItems = [ - .init(name: "filter[status]", value: "ENABLED"), - .init(name: "filter[platform]", value: "IOS"), - .init(name: "limit", value: "200") - ] - guard let url: URL = urlComponents.url - else { - throw Error.unableToCreateURL(urlComponents: urlComponents) - } + let url: URL = try createRequestURL( + path: "/v1/devices", + queryItems: [ + .init(name: "filter[status]", value: "ENABLED"), + .init(name: "filter[platform]", value: "IOS"), + .init(name: "limit", value: "200") + ] + ) var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: "Accept") @@ -359,11 +358,9 @@ internal class iTunesConnectServiceImp: iTunesConnectService { profileType: String, profileName: String? = nil ) throws -> CreateProfileResponse { - let urlString: String = "https://api.appstoreconnect.apple.com/v1/profiles" - guard let url: URL = .init(string: urlString) - else { - throw Error.invalidURL(string: urlString) - } + let url: URL = try createRequestURL( + path: "/v1/profiles" + ) var request: URLRequest = .init(url: url) request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: "Accept") request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: Constants.contentTypeHeaderName) @@ -424,14 +421,9 @@ internal class iTunesConnectServiceImp: iTunesConnectService { jsonWebToken: String, id: String ) throws { - var urlComponents: URLComponents = .init() - urlComponents.scheme = Constants.httpsScheme - urlComponents.host = Constants.itcHost - urlComponents.path = "/v1/profiles/\(id)" - guard let url: URL = urlComponents.url - else { - throw Error.unableToCreateURL(urlComponents: urlComponents) - } + let url: URL = try createRequestURL( + path: "/v1/profiles/\(id)" + ) var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: "Accept") @@ -448,18 +440,13 @@ internal class iTunesConnectServiceImp: iTunesConnectService { jsonWebToken: String, name: String ) throws -> [ProfileResponseData] { - var urlComponents: URLComponents = .init() - urlComponents.scheme = Constants.httpsScheme - urlComponents.host = Constants.itcHost - urlComponents.path = "/v1/profiles" - urlComponents.queryItems = [ - .init(name: "filter[name]", value: name), - .init(name: "include", value: "devices") - ] - guard let url: URL = urlComponents.url - else { - throw Error.unableToCreateURL(urlComponents: urlComponents) - } + let url: URL = try createRequestURL( + path: "/v1/profiles", + queryItems: [ + .init(name: "filter[name]", value: name), + .init(name: "include", value: "devices") + ] + ) var request: URLRequest = .init(url: url) request.setValue("Bearer \(jsonWebToken)", forHTTPHeaderField: "Authorization") request.setValue(Constants.applicationJSONHeaderValue, forHTTPHeaderField: "Accept") @@ -477,6 +464,25 @@ internal class iTunesConnectServiceImp: iTunesConnectService { } } + private func createRequestURL( + path: String, + queryItems: [URLQueryItem] = [] + ) throws -> URL { + let host: String = enterprise ? Constants.enterpriseHost : Constants.itcHost + var urlComponents: URLComponents = .init() + urlComponents.scheme = Constants.httpsScheme + urlComponents.host = host + urlComponents.path = path + if !queryItems.isEmpty { + urlComponents.queryItems = queryItems + } + guard let url: URL = urlComponents.url + else { + throw Error.unableToCreateURL(urlComponents: urlComponents) + } + return url + } + private func createITCApiJSONDecoder() -> JSONDecoder { let jsonDecoder: JSONDecoder = .init() let dateFormatter: DateFormatter = .init() diff --git a/Tests/SignHereLibraryTests/CreateProvisioningProfileCommandTests.swift b/Tests/SignHereLibraryTests/CreateProvisioningProfileCommandTests.swift index b8c2cfb..653b24a 100644 --- a/Tests/SignHereLibraryTests/CreateProvisioningProfileCommandTests.swift +++ b/Tests/SignHereLibraryTests/CreateProvisioningProfileCommandTests.swift @@ -58,7 +58,8 @@ final class CreateProvisioningProfileCommandTests: XCTestCase { bundleIdentifierName: "bundleIdentifierName", platform: "platform", profileName: "profileName", - autoRegenerate: false + autoRegenerate: false, + enterprise: false ) isRecording = false } @@ -337,7 +338,7 @@ final class CreateProvisioningProfileCommandTests: XCTestCase { // GIVEN var previousProfileWasDeleted = false let responseObject = createCreateProfileResponse().data - + files.uniqueTemporaryPathHandler = { Path("/unique_temporary_path_\(self.files.uniqueTemporaryPathCallCount)") } diff --git a/Tests/SignHereLibraryTests/DeleteProvisioningProfileCommandTests.swift b/Tests/SignHereLibraryTests/DeleteProvisioningProfileCommandTests.swift index 36b704c..64d95df 100644 --- a/Tests/SignHereLibraryTests/DeleteProvisioningProfileCommandTests.swift +++ b/Tests/SignHereLibraryTests/DeleteProvisioningProfileCommandTests.swift @@ -32,7 +32,8 @@ final class DeleteProvisioningProfileCommandTests: XCTestCase { provisioningProfileId: "provisioningProfileId", keyIdentifier: "keyIdentifier", issuerID: "issuerID", - itunesConnectKeyPath: "/itunesConnectKeyPath" + itunesConnectKeyPath: "/itunesConnectKeyPath", + enterprise: false ) } @@ -53,7 +54,8 @@ final class DeleteProvisioningProfileCommandTests: XCTestCase { "provisioningProfileId": "provisioningProfileId", "keyIdentifier": "keyIdentifier", "issuerID": "issuerID", - "itunesConnectKeyPath": "/itunesConnectKeyPath" + "itunesConnectKeyPath": "/itunesConnectKeyPath", + "enterprise": true } """.utf8) @@ -65,6 +67,7 @@ final class DeleteProvisioningProfileCommandTests: XCTestCase { XCTAssertEqual(subject.keyIdentifier, "keyIdentifier") XCTAssertEqual(subject.issuerID, "issuerID") XCTAssertEqual(subject.itunesConnectKeyPath, "/itunesConnectKeyPath") + XCTAssertTrue(subject.enterprise) } func test_execute() throws { diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate.1.txt index 2a139fa..749ee70 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"certificateType\":\"certificateType\",\"csrContent\":\"\"},\"type\":\"certificates\"}}" \ + --data "{\"data\":{\"attributes\":{\"csrContent\":\"\",\"certificateType\":\"certificateType\"},\"type\":\"certificates\"}}" \ "https://api.appstoreconnect.apple.com/v1/certificates" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate_unableToDecodeResponse.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate_unableToDecodeResponse.1.txt index 2a139fa..dd1e550 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate_unableToDecodeResponse.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createCertificate_unableToDecodeResponse.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"certificateType\":\"certificateType\",\"csrContent\":\"\"},\"type\":\"certificates\"}}" \ + --data "{\"data\":{\"type\":\"certificates\",\"attributes\":{\"csrContent\":\"\",\"certificateType\":\"certificateType\"}}}" \ "https://api.appstoreconnect.apple.com/v1/certificates" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile.1.txt index 1951f61..5a8f3e6 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"name\":\"certificateId_profileType_0.0\",\"profileType\":\"profileType\"},\"type\":\"profiles\",\"relationships\":{\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"bundleId\":{\"data\":{\"id\":\"bundleId\",\"type\":\"bundleIds\"}}}}}" \ + --data "{\"data\":{\"attributes\":{\"name\":\"certificateId_profileType_0.0\",\"profileType\":\"profileType\"},\"type\":\"profiles\",\"relationships\":{\"certificates\":{\"data\":[{\"type\":\"certificates\",\"id\":\"certificateId\"}]},\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"bundleId\":{\"data\":{\"type\":\"bundleIds\",\"id\":\"bundleId\"}}}}}" \ "https://api.appstoreconnect.apple.com/v1/profiles" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_decodingError.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_decodingError.1.txt index 1951f61..69f581c 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_decodingError.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_decodingError.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"name\":\"certificateId_profileType_0.0\",\"profileType\":\"profileType\"},\"type\":\"profiles\",\"relationships\":{\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"bundleId\":{\"data\":{\"id\":\"bundleId\",\"type\":\"bundleIds\"}}}}}" \ + --data "{\"data\":{\"relationships\":{\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"bundleId\":{\"data\":{\"type\":\"bundleIds\",\"id\":\"bundleId\"}},\"certificates\":{\"data\":[{\"type\":\"certificates\",\"id\":\"certificateId\"}]}},\"type\":\"profiles\",\"attributes\":{\"profileType\":\"profileType\",\"name\":\"certificateId_profileType_0.0\"}}}" \ "https://api.appstoreconnect.apple.com/v1/profiles" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_iosAppStoreProfile.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_iosAppStoreProfile.1.txt index f249b12..8be609b 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_iosAppStoreProfile.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_iosAppStoreProfile.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"name\":\"certificateId_IOS_APP_STORE_0.0\",\"profileType\":\"IOS_APP_STORE\"},\"type\":\"profiles\",\"relationships\":{\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"bundleId\":{\"data\":{\"id\":\"bundleId\",\"type\":\"bundleIds\"}}}}}" \ + --data "{\"data\":{\"attributes\":{\"name\":\"certificateId_IOS_APP_STORE_0.0\",\"profileType\":\"IOS_APP_STORE\"},\"type\":\"profiles\",\"relationships\":{\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"bundleId\":{\"data\":{\"type\":\"bundleIds\",\"id\":\"bundleId\"}}}}}" \ "https://api.appstoreconnect.apple.com/v1/profiles" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_withProfileName.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_withProfileName.1.txt index 4840d90..3fe1cf5 100644 --- a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_withProfileName.1.txt +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_createProfile_withProfileName.1.txt @@ -3,5 +3,5 @@ curl \ --header "Accept: application/json" \ --header "Authorization: Bearer jsonWebToken" \ --header "Content-Type: application/json" \ - --data "{\"data\":{\"attributes\":{\"name\":\"mySpecialProfile\",\"profileType\":\"profileType\"},\"type\":\"profiles\",\"relationships\":{\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"bundleId\":{\"data\":{\"id\":\"bundleId\",\"type\":\"bundleIds\"}}}}}" \ + --data "{\"data\":{\"attributes\":{\"profileType\":\"profileType\",\"name\":\"mySpecialProfile\"},\"relationships\":{\"certificates\":{\"data\":[{\"id\":\"certificateId\",\"type\":\"certificates\"}]},\"devices\":{\"data\":[{\"id\":\"deviceId\",\"type\":\"devices\"}]},\"bundleId\":{\"data\":{\"type\":\"bundleIds\",\"id\":\"bundleId\"}}},\"type\":\"profiles\"}}" \ "https://api.appstoreconnect.apple.com/v1/profiles" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.1.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.1.txt new file mode 100644 index 0000000..7c030b7 --- /dev/null +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.1.txt @@ -0,0 +1,25 @@ +▿ 2 elements + ▿ (4 elements) + - .0: "/usr/bin/env" + ▿ .1: 6 elements + - "/opensslPath" + - "rsa" + - "-noout" + - "-modulus" + - "-in" + - "/privateKeyPath" + - .2: Optional>.none + - .3: Optional.none + ▿ (4 elements) + - .0: "/usr/bin/env" + ▿ .1: 8 elements + - "/opensslPath" + - "x509" + - "-inform" + - "der" + - "-noout" + - "-modulus" + - "-in" + - "/unique_temporary_path_1/validate_private_key.cer" + - .2: Optional>.none + - .3: Optional.none diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.2.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.2.txt new file mode 100644 index 0000000..187c9f1 --- /dev/null +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.2.txt @@ -0,0 +1,3 @@ +curl \ + --header "Authorization: Bearer jsonWebToken" \ + "https://api.enterprise.developer.apple.com/v1/certificates?filter%5BcertificateType%5D=certificateType&limit=200" \ No newline at end of file diff --git a/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.3.txt b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.3.txt new file mode 100644 index 0000000..06b1cda --- /dev/null +++ b/Tests/SignHereLibraryTests/__Snapshots__/iTunesConnectServiceTests/iTunesConnectServiceTests_test_fetchActiveCertificates_privateKeyMatches_enterprise.3.txt @@ -0,0 +1,8 @@ +▿ 1 element + ▿ DownloadCertificateResponseData + ▿ attributes: DownloadCertificateResponseDataAttributes + - certificateContent: "dGVzdAo=" + - certificateType: "certificateType" + - displayName: "activeCertDisplayName" + - expirationDate: 1970-01-01T00:01:40Z + - id: "activeCertID" diff --git a/Tests/SignHereLibraryTests/iTunesConnectServiceTests.swift b/Tests/SignHereLibraryTests/iTunesConnectServiceTests.swift index 7a8e362..e89d75e 100644 --- a/Tests/SignHereLibraryTests/iTunesConnectServiceTests.swift +++ b/Tests/SignHereLibraryTests/iTunesConnectServiceTests.swift @@ -33,7 +33,8 @@ final class iTunesConnectServiceTests: XCTestCase { network: network, files: files, shell: shell, - clock: clock + clock: clock, + enterprise: false ) isRecording = false @@ -53,7 +54,11 @@ final class iTunesConnectServiceTests: XCTestCase { network: network, files: files, shell: shell, - clock: clock + clock: clock, + enterprise: true + )) + XCTAssertNotNil(iTunesConnectServiceImp( + enterprise: true )) } @@ -160,6 +165,61 @@ final class iTunesConnectServiceTests: XCTestCase { ) } + func test_fetchActiveCertificates_privateKeyMatches_enterprise() throws { + // GIVEN + subject = iTunesConnectServiceImp( + network: network, + files: files, + shell: shell, + clock: clock, + enterprise: true + ) + files.uniqueTemporaryPathHandler = { + Path("/unique_temporary_path_\(self.files.uniqueTemporaryPathCallCount)") + } + var executeLaunchPaths: [ShellOutput] = [ + .init(status: 0, data: .init("modulus".utf8), errorData: .init()), // ME: Private Key Modulus + .init(status: 0, data: .init("modulus".utf8), errorData: .init()) // ME: Certificate Modulus + ] + shell.executeLaunchPathHandler = { _, _, _, _ in + executeLaunchPaths.removeFirst() + } + var fileDataReads: [Data] = [ + Data("CSRContent".utf8) + ] + files.readPathHandler = { _ in + fileDataReads.removeFirst() + } + let jsonEncoder: JSONEncoder = createJSONEncoder() + var networkExecutes: [Data] = [ + try jsonEncoder.encode(createDownloadCertificateResponse(nextURL: nil)) + ] + network.executeHandler = { _ in + networkExecutes.removeFirst() + } + + // WHEN + let value: [DownloadCertificateResponse.DownloadCertificateResponseData] = try subject.fetchActiveCertificates( + jsonWebToken: "jsonWebToken", + opensslPath: "/opensslPath", + privateKeyPath: "/privateKeyPath", + certificateType: "certificateType" + ) + + // THEN + assertSnapshot( + matching: shell.executeLaunchPathArgValues, + as: .dump + ) + for argValue in network.executeArgValues { + assertSnapshot(matching: argValue, as: .curl) + } + assertSnapshot( + matching: value, + as: .dump + ) + } + func test_fetchActiveCertificates_privateKeyMatches_pagedResults() throws { // GIVEN files.uniqueTemporaryPathHandler = { @@ -882,7 +942,7 @@ final class iTunesConnectServiceTests: XCTestCase { ) ) } - + private func createFetchProfileResponse() -> GetProfilesResponse { .init( data: [ProfileResponseData(