From 96eae1a23dfe69a43de5c9188826c7fe777ddf36 Mon Sep 17 00:00:00 2001 From: Alex Baker Date: Fri, 3 Nov 2023 13:50:03 -0700 Subject: [PATCH] Adding support for Xcode and LocalTesting environments --- appstoreserverlibrary/models/Environment.py | 2 + appstoreserverlibrary/receipt_utility.py | 25 ++++- appstoreserverlibrary/signed_data_verifier.py | 6 +- tests/resources/certs/testCA.der | Bin 0 -> 390 bytes tests/resources/certs/testSigningKey.p8 | 5 + .../mock_signed_data/legacyTransaction | 1 + .../mock_signed_data/missingX5CHeaderClaim | 1 + tests/resources/mock_signed_data/renewalInfo | 1 + .../mock_signed_data/testNotification | 1 + .../mock_signed_data/transactionInfo | 1 + .../resources/mock_signed_data/wrongBundleId | 1 + tests/resources/models/apiException.json | 4 + .../models/apiTooManyRequestsException.json | 4 + tests/resources/models/apiUnknownError.json | 4 + tests/resources/models/appTransaction.json | 13 +++ ...alDateForAllActiveSubscribersResponse.json | 3 + ...extendSubscriptionRenewalDateResponse.json | 6 ++ .../getAllSubscriptionStatusesResponse.json | 35 +++++++ .../getNotificationHistoryResponse.json | 27 +++++ .../models/getRefundHistoryResponse.json | 8 ++ ...criptionRenewalDateExtensionsResponse.json | 7 ++ .../getTestNotificationStatusResponse.json | 12 +++ .../models/lookupOrderIdResponse.json | 7 ++ .../requestTestNotificationResponse.json | 3 + .../resources/models/signedNotification.json | 16 +++ tests/resources/models/signedRenewalInfo.json | 16 +++ .../models/signedSummaryNotification.json | 21 ++++ tests/resources/models/signedTransaction.json | 25 +++++ .../models/transactionHistoryResponse.json | 11 ++ .../models/transactionInfoResponse.json | 3 + tests/resources/xcode/xcode-app-receipt-empty | 1 + .../xcode/xcode-app-receipt-with-transaction | 1 + .../xcode/xcode-signed-app-transaction | 1 + .../resources/xcode/xcode-signed-renewal-info | 1 + .../resources/xcode/xcode-signed-transaction | 1 + tests/test_receipt_utility.py | 37 +++++++ tests/test_xcode_signed_data.py | 95 ++++++++++++++++++ tests/util.py | 45 +++++++++ 38 files changed, 448 insertions(+), 3 deletions(-) create mode 100644 tests/resources/certs/testCA.der create mode 100644 tests/resources/certs/testSigningKey.p8 create mode 100644 tests/resources/mock_signed_data/legacyTransaction create mode 100644 tests/resources/mock_signed_data/missingX5CHeaderClaim create mode 100644 tests/resources/mock_signed_data/renewalInfo create mode 100644 tests/resources/mock_signed_data/testNotification create mode 100644 tests/resources/mock_signed_data/transactionInfo create mode 100644 tests/resources/mock_signed_data/wrongBundleId create mode 100644 tests/resources/models/apiException.json create mode 100644 tests/resources/models/apiTooManyRequestsException.json create mode 100644 tests/resources/models/apiUnknownError.json create mode 100644 tests/resources/models/appTransaction.json create mode 100644 tests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json create mode 100644 tests/resources/models/extendSubscriptionRenewalDateResponse.json create mode 100644 tests/resources/models/getAllSubscriptionStatusesResponse.json create mode 100644 tests/resources/models/getNotificationHistoryResponse.json create mode 100644 tests/resources/models/getRefundHistoryResponse.json create mode 100644 tests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json create mode 100644 tests/resources/models/getTestNotificationStatusResponse.json create mode 100644 tests/resources/models/lookupOrderIdResponse.json create mode 100644 tests/resources/models/requestTestNotificationResponse.json create mode 100644 tests/resources/models/signedNotification.json create mode 100644 tests/resources/models/signedRenewalInfo.json create mode 100644 tests/resources/models/signedSummaryNotification.json create mode 100644 tests/resources/models/signedTransaction.json create mode 100644 tests/resources/models/transactionHistoryResponse.json create mode 100644 tests/resources/models/transactionInfoResponse.json create mode 100644 tests/resources/xcode/xcode-app-receipt-empty create mode 100644 tests/resources/xcode/xcode-app-receipt-with-transaction create mode 100644 tests/resources/xcode/xcode-signed-app-transaction create mode 100644 tests/resources/xcode/xcode-signed-renewal-info create mode 100644 tests/resources/xcode/xcode-signed-transaction create mode 100644 tests/test_receipt_utility.py create mode 100644 tests/test_xcode_signed_data.py create mode 100644 tests/util.py diff --git a/appstoreserverlibrary/models/Environment.py b/appstoreserverlibrary/models/Environment.py index ab1abf7..ce53c4d 100644 --- a/appstoreserverlibrary/models/Environment.py +++ b/appstoreserverlibrary/models/Environment.py @@ -10,3 +10,5 @@ class Environment(Enum): """ SANDBOX = "Sandbox" PRODUCTION = "Production" + XCODE = "Xcode" + LOCAL_TESTING = "LocalTesting" diff --git a/appstoreserverlibrary/receipt_utility.py b/appstoreserverlibrary/receipt_utility.py index 00865e9..504005d 100644 --- a/appstoreserverlibrary/receipt_utility.py +++ b/appstoreserverlibrary/receipt_utility.py @@ -20,7 +20,7 @@ def extract_transaction_id_from_app_receipt(self, app_receipt: str) -> Optional[ :param appReceipt: The unmodified app receipt :return: A transaction id from the array of in-app purchases, null if the receipt contains no in-app purchases """ - decoder = asn1.Decoder() + decoder = IndefiniteFormAwareDecoder() decoder.start(b64decode(app_receipt, validate=True)) tag = decoder.peek() if tag.typ != asn1.Types.Constructed or tag.nr != asn1.Numbers.Sequence: @@ -39,6 +39,11 @@ def extract_transaction_id_from_app_receipt(self, app_receipt: str) -> Optional[ decoder.read() decoder.enter() tag, value = decoder.read() + # Xcode uses nested OctetStrings, we extract the inner string in this case + if tag.typ == asn1.Types.Constructed and tag.nr == asn1.Numbers.OctetString: + inner_decoder = asn1.Decoder() + inner_decoder.start(value) + tag, value = inner_decoder.read() if tag.typ != asn1.Types.Primitive or tag.nr != asn1.Numbers.OctetString: raise ValueError() decoder = asn1.Decoder() @@ -92,4 +97,20 @@ def extract_transaction_id_from_transaction_receipt(self, transaction_receipt: s inner_matching_result = re.search('"transaction-id"\s+=\s+"([a-zA-Z0-9+/=]+)";', decoded_inner_level) if inner_matching_result: return inner_matching_result.group(1) - return None \ No newline at end of file + return None + +class IndefiniteFormAwareDecoder(asn1.Decoder): + def _read_length(self) -> int: + index, input_data = self.m_stack[-1] + try: + byte = input_data[index] + except IndexError: + raise asn1.Error('Premature end of input.') + if byte == 0x80: + # Xcode receipts use indefinite length encoding, not supported by all parsers + # Indefinite length encoding is only entered, but never left during parsing for receipts + # We therefore round up indefinite length encoding to be the remaining length + self._read_byte() + index, input_data = self.m_stack[-1] + return len(input_data) - index + return super()._read_length() \ No newline at end of file diff --git a/appstoreserverlibrary/signed_data_verifier.py b/appstoreserverlibrary/signed_data_verifier.py index b6e41e2..553e3d1 100644 --- a/appstoreserverlibrary/signed_data_verifier.py +++ b/appstoreserverlibrary/signed_data_verifier.py @@ -114,6 +114,11 @@ def verify_and_decode_app_transaction(self, signed_app_transaction: str) -> AppT def _decode_signed_object(self, signed_obj: str) -> dict: try: + decoded_jwt = jwt.decode(signed_obj, options={"verify_signature": False}) + if self._environment == Environment.XCODE or self._environment == Environment.LOCAL_TESTING: + # Data is not signed by the App Store, and verification should be skipped + # The environment MUST be checked in the public method calling this + return decoded_jwt unverified_headers: dict = jwt.get_unverified_header(signed_obj) x5c_header: List[str] = unverified_headers.get("x5c") if x5c_header is None or len(x5c_header) == 0: @@ -121,7 +126,6 @@ def _decode_signed_object(self, signed_obj: str) -> dict: algorithm_header: str = unverified_headers.get("alg") if algorithm_header is None or "ES256" != algorithm_header: raise Exception("Algorithm was not ES256") - decoded_jwt = jwt.decode(signed_obj, options={"verify_signature": False}) signed_date = decoded_jwt.get('signedDate') if decoded_jwt.get('signedDate') is not None else decoded_jwt.get('receiptCreationDate') effective_date = time.time() if self._enable_online_checks or signed_date is None else int(signed_date) // 1000 signing_key = self._chain_verifier.verify_chain(x5c_header, self._enable_online_checks, effective_date) diff --git a/tests/resources/certs/testCA.der b/tests/resources/certs/testCA.der new file mode 100644 index 0000000000000000000000000000000000000000..54a42b6819d776228bc61c5284cd96772880748c GIT binary patch literal 390 zcmXqLVr(*KV$@u~%*4pV#L2K#<_Xgy`B~8hTx=X#Z64=rS(up(%nZ2=IN6v(S=fY` zLW2#34Fo|P4jwM&#GK5u{Gz{e{M+Syo&BN50dt+ZE=&qlsT1Og8A{FO-k5MQs=_w7 zyOCvI8uQkU%M*VusJrt@g-M}e;))Yz>rEL>Y42@cbuReRzD|=8<-a%nT$#49@cPf? FG5{`Qc$NSF literal 0 HcmV?d00001 diff --git a/tests/resources/certs/testSigningKey.p8 b/tests/resources/certs/testSigningKey.p8 new file mode 100644 index 0000000..0622800 --- /dev/null +++ b/tests/resources/certs/testSigningKey.p8 @@ -0,0 +1,5 @@ +-----BEGIN PRIVATE KEY----- +MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgSpP55ELdXswj9JRZ +APRwtTfS4CNRqpKIs+28rNHiPAqhRANCAASs8nLES7b+goKslppNVOurf0MonZdw +3pb6TxS8Z/5j+UNY1sWK1ChxpuwNS9I3R50cfdQo/lA9PPhw6XIg8ytd +-----END PRIVATE KEY----- diff --git a/tests/resources/mock_signed_data/legacyTransaction b/tests/resources/mock_signed_data/legacyTransaction new file mode 100644 index 0000000..534391d --- /dev/null +++ b/tests/resources/mock_signed_data/legacyTransaction @@ -0,0 +1 @@ +ewoicHVyY2hhc2UtaW5mbyIgPSAiZXdvaWRISmhibk5oWTNScGIyNHRhV1FpSUQwZ0lqTXpPVGt6TXprNUlqc0tmUW89IjsKfQo= \ No newline at end of file diff --git a/tests/resources/mock_signed_data/missingX5CHeaderClaim b/tests/resources/mock_signed_data/missingX5CHeaderClaim new file mode 100644 index 0000000..b4112b4 --- /dev/null +++ b/tests/resources/mock_signed_data/missingX5CHeaderClaim @@ -0,0 +1 @@ +eyJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1Y3dyb25nIjpbIk1JSUJvRENDQVVhZ0F3SUJBZ0lCRERBS0JnZ3Foa2pPUFFRREF6QkZNUXN3Q1FZRFZRUUdFd0pWVXpFTE1Ba0dBMVVFQ0F3Q1EwRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1CNFhEVEl6TURFd05USXhNekV6TkZvWERUTXpNREV3TVRJeE16RXpORm93UFRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eERUQUxCZ05WQkFvTUJFeGxZV1l3V1RBVEJnY3Foa2pPUFFJQkJnZ3Foa2pPUFFNQkJ3TkNBQVRpdFlIRWFZVnVjOGc5QWpUT3dFck12R3lQeWtQYStwdXZUSThoSlRIWlpETEdhczJxWDErRXJ4Z1FUSmdWWHY3Nm5tTGhoUkpIK2oyNUFpQUk4aUdzb3k4d0xUQUpCZ05WSFJNRUFqQUFNQTRHQTFVZER3RUIvd1FFQXdJSGdEQVFCZ29xaGtpRzkyTmtCZ3NCQkFJRkFEQUtCZ2dxaGtqT1BRUURBd05JQURCRkFpQlg0YytUMEZwNW5KNVFSQ2xSZnU1UFNCeVJ2TlB0dWFUc2swdlBCM1dBSUFJaEFOZ2FhdUFqL1lQOXMwQWtFaHlKaHhRTy82UTJ6b3VaK0gxQ0lPZWhuTXpRIiwiTUlJQm56Q0NBVVdnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQXpBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1CNFhEVEl6TURFd05USXhNekV3TlZvWERUTXpNREV3TVRJeE16RXdOVm93UlRFTE1Ba0dBMVVFQmhNQ1ZWTXhDekFKQmdOVkJBZ01Ba05CTVJJd0VBWURWUVFIREFsRGRYQmxjblJwYm04eEZUQVRCZ05WQkFvTURFbHVkR1Z5YldWa2FXRjBaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCQlVONVY5cktqZlJpTUFJb2pFQTBBdjVNcDBvRitPMGNMNGd6clRGMTc4aW5VSHVnajdFdDQ2TnJrUTdoS2dNVm5qb2dxNDVRMXJNcytjTUhWTklMV3FqTlRBek1BOEdBMVVkRXdRSU1BWUJBZjhDQVFBd0RnWURWUjBQQVFIL0JBUURBZ0VHTUJBR0NpcUdTSWIzWTJRR0FnRUVBZ1VBTUFvR0NDcUdTTTQ5QkFNREEwZ0FNRVVDSVFDbXNJS1lzNDF1bGxzc0hYNHJWdmVVVDBaN0lzNS9oTEsxbEZQVHR1bjNoQUlnYzIrMlJHNStnTmNGVmNzK1hKZUVsNEdaK29qbDNST09tbGwreWU3ZHluUT0iLCJNSUlCZ2pDQ0FTbWdBd0lCQWdJSkFMVWM1QUxpSDVwYk1Bb0dDQ3FHU000OUJBTURNRFl4Q3pBSkJnTlZCQVlUQWxWVE1STXdFUVlEVlFRSURBcERZV3hwWm05eWJtbGhNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh3SGhjTk1qTXdNVEExTWpFek1ESXlXaGNOTXpNd01UQXlNakV6TURJeVdqQTJNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNRmt3RXdZSEtvWkl6ajBDQVFZSUtvWkl6ajBEQVFjRFFnQUVjKy9CbCtnb3NwbzZ0ZjlaN2lvNXRkS2RybE4xWWRWbnFFaEVEWERTaHpkQUpQUWlqYW1YSU1IZjh4V1dUYTF6Z29ZVHhPS3BidUp0RHBsejFYcmlUYU1nTUI0d0RBWURWUjBUQkFVd0F3RUIvekFPQmdOVkhROEJBZjhFQkFNQ0FRWXdDZ1lJS29aSXpqMEVBd01EUndBd1JBSWdlbVdRWG5NQWRUYWQySkRKV25nOVU0dUJCTDVtQTdXSTA1SDdvSDdjNmlRQ0lIaVJxTWpOZnpVQXlpdTloNnJPVS9LK2lUUjBJLzNZL05TV3NYSFgrYWNjIl19.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.1TFhjDR4WwQJNgizVGYXz3WE3ajxTdH1wKLQQ71MtrkadSxxOo3yPo_6L9Z03unIU7YK-NRNzSIb5bh5WqTprQ \ No newline at end of file diff --git a/tests/resources/mock_signed_data/renewalInfo b/tests/resources/mock_signed_data/renewalInfo new file mode 100644 index 0000000..d14a20d --- /dev/null +++ b/tests/resources/mock_signed_data/renewalInfo @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJzaWduZWREYXRlIjoxNjcyOTU2MTU0MDAwfQ.FbK2OL-t6l4892W7fzWyus_g9mIl2CzWLbVt7Kgcnt6zzVulF8bzovgpe0v_y490blROGixy8KDoe2dSU53-Xw \ No newline at end of file diff --git a/tests/resources/mock_signed_data/testNotification b/tests/resources/mock_signed_data/testNotification new file mode 100644 index 0000000..7bb78cf --- /dev/null +++ b/tests/resources/mock_signed_data/testNotification @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImFwcEFwcGxlSWQiOjEyMzQsImVudmlyb25tZW50IjoiU2FuZGJveCIsImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsInNpZ25lZERhdGUiOjE2ODEzMTQzMjQwMDAsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.VVXYwuNm2Y3XsOUva-BozqatRCsDuykA7xIe_CCRw6aIAAxJ1nb2sw871jfZ6dcgNhUuhoZ93hfbc1v_5zB7Og \ No newline at end of file diff --git a/tests/resources/mock_signed_data/transactionInfo b/tests/resources/mock_signed_data/transactionInfo new file mode 100644 index 0000000..3ddf0b0 --- /dev/null +++ b/tests/resources/mock_signed_data/transactionInfo @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJDekFLQmdncWhrak9QUVFEQWpCTk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1SVXdFd1lEVlFRS0RBeEpiblJsY20xbFpHbGhkR1V3SGhjTk1qTXdNVEEwTVRZek56TXhXaGNOTXpJeE1qTXhNVFl6TnpNeFdqQkZNUXN3Q1FZRFZRUUdFd0pWVXpFVE1CRUdBMVVFQ0F3S1EyRnNhV1p2Y201cFlURVNNQkFHQTFVRUJ3d0pRM1Z3WlhKMGFXNXZNUTB3Q3dZRFZRUUtEQVJNWldGbU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRTRyV0J4R21GYm5QSVBRSTB6c0JLekx4c2o4cEQydnFicjB5UElTVXgyV1F5eG1yTnFsOWZoSzhZRUV5WUZWNysrcDVpNFlVU1Ivbzl1UUlnQ1BJaHJLTWZNQjB3Q1FZRFZSMFRCQUl3QURBUUJnb3Foa2lHOTJOa0Jnc0JCQUlUQURBS0JnZ3Foa2pPUFFRREFnTklBREJGQWlFQWtpRVprb0ZNa2o0Z1huK1E5alhRWk1qWjJnbmpaM2FNOE5ZcmdmVFVpdlFDSURKWVowRmFMZTduU0lVMkxXTFRrNXRYVENjNEU4R0pTWWYvc1lSeEVGaWUiLCJNSUlCbHpDQ0FUMmdBd0lCQWdJQkJqQUtCZ2dxaGtqT1BRUURBakEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qWXdNVm9YRFRNeU1USXpNVEUyTWpZd01Wb3dUVEVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekVWTUJNR0ExVUVDZ3dNU1c1MFpYSnRaV1JwWVhSbE1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRUZRM2xYMnNxTjlHSXdBaWlNUURRQy9reW5TZ1g0N1J3dmlET3RNWFh2eUtkUWU2Q1BzUzNqbzJ1UkR1RXFBeFdlT2lDcmpsRFdzeXo1d3dkVTBndGFxTWxNQ013RHdZRFZSMFRCQWd3QmdFQi93SUJBREFRQmdvcWhraUc5Mk5rQmdJQkJBSVRBREFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUVBdm56TWNWMjY4Y1JiMS9GcHlWMUVoVDNXRnZPenJCVVdQNi9Ub1RoRmF2TUNJRmJhNXQ2WUt5MFIySkR0eHF0T2pKeTY2bDZWN2QvUHJBRE5wa21JUFcraSIsIk1JSUJYRENDQVFJQ0NRQ2ZqVFVHTERuUjlqQUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TkRFMk1qQXpNbG9YRFRNek1ERXdNVEUyTWpBek1sb3dOakVMTUFrR0ExVUVCaE1DVlZNeEV6QVJCZ05WQkFnTUNrTmhiR2xtYjNKdWFXRXhFakFRQmdOVkJBY01DVU4xY0dWeWRHbHViekJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCSFB2d1pmb0tMS2FPclgvV2U0cU9iWFNuYTVUZFdIVlo2aElSQTF3MG9jM1FDVDBJbzJwbHlEQjMvTVZsazJ0YzRLR0U4VGlxVzdpYlE2WmM5VjY0azB3Q2dZSUtvWkl6ajBFQXdNRFNBQXdSUUloQU1USGhXdGJBUU4waFN4SVhjUDRDS3JEQ0gvZ3N4V3B4NmpUWkxUZVorRlBBaUIzNW53azVxMHpjSXBlZnZZSjBNVS95R0dIU1dlejBicTBwRFlVTy9ubUR3PT0iXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJlbnZpcm9ubWVudCI6IlNhbmRib3giLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlIiwic2lnbmVkRGF0ZSI6MTY3Mjk1NjE1NDAwMH0.PnHWpeIJZ8f2Q218NSGLo_aR0IBEJvC6PxmxKXh-qfYTrZccx2suGl223OSNAX78e4Ylf2yJCG2N-FfU-NIhZQ \ No newline at end of file diff --git a/tests/resources/mock_signed_data/wrongBundleId b/tests/resources/mock_signed_data/wrongBundleId new file mode 100644 index 0000000..cc7091e --- /dev/null +++ b/tests/resources/mock_signed_data/wrongBundleId @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQm9EQ0NBVWFnQXdJQkFnSUJEREFLQmdncWhrak9QUVFEQXpCRk1Rc3dDUVlEVlFRR0V3SlZVekVMTUFrR0ExVUVDQXdDUTBFeEVqQVFCZ05WQkFjTUNVTjFjR1Z5ZEdsdWJ6RVZNQk1HQTFVRUNnd01TVzUwWlhKdFpXUnBZWFJsTUI0WERUSXpNREV3TlRJeE16RXpORm9YRFRNek1ERXdNVEl4TXpFek5Gb3dQVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RFRBTEJnTlZCQW9NQkV4bFlXWXdXVEFUQmdjcWhrak9QUUlCQmdncWhrak9QUU1CQndOQ0FBVGl0WUhFYVlWdWM4ZzlBalRPd0VyTXZHeVB5a1BhK3B1dlRJOGhKVEhaWkRMR2FzMnFYMStFcnhnUVRKZ1ZYdjc2bm1MaGhSSkgrajI1QWlBSThpR3NveTh3TFRBSkJnTlZIUk1FQWpBQU1BNEdBMVVkRHdFQi93UUVBd0lIZ0RBUUJnb3Foa2lHOTJOa0Jnc0JCQUlGQURBS0JnZ3Foa2pPUFFRREF3TklBREJGQWlCWDRjK1QwRnA1bko1UVJDbFJmdTVQU0J5UnZOUHR1YVRzazB2UEIzV0FJQUloQU5nYWF1QWovWVA5czBBa0VoeUpoeFFPLzZRMnpvdVorSDFDSU9laG5NelEiLCJNSUlCbnpDQ0FVV2dBd0lCQWdJQkN6QUtCZ2dxaGtqT1BRUURBekEyTVFzd0NRWURWUVFHRXdKVlV6RVRNQkVHQTFVRUNBd0tRMkZzYVdadmNtNXBZVEVTTUJBR0ExVUVCd3dKUTNWd1pYSjBhVzV2TUI0WERUSXpNREV3TlRJeE16RXdOVm9YRFRNek1ERXdNVEl4TXpFd05Wb3dSVEVMTUFrR0ExVUVCaE1DVlZNeEN6QUpCZ05WQkFnTUFrTkJNUkl3RUFZRFZRUUhEQWxEZFhCbGNuUnBibTh4RlRBVEJnTlZCQW9NREVsdWRHVnliV1ZrYVdGMFpUQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJCVU41VjlyS2pmUmlNQUlvakVBMEF2NU1wMG9GK08wY0w0Z3pyVEYxNzhpblVIdWdqN0V0NDZOcmtRN2hLZ01WbmpvZ3E0NVExck1zK2NNSFZOSUxXcWpOVEF6TUE4R0ExVWRFd1FJTUFZQkFmOENBUUF3RGdZRFZSMFBBUUgvQkFRREFnRUdNQkFHQ2lxR1NJYjNZMlFHQWdFRUFnVUFNQW9HQ0NxR1NNNDlCQU1EQTBnQU1FVUNJUUNtc0lLWXM0MXVsbHNzSFg0clZ2ZVVUMFo3SXM1L2hMSzFsRlBUdHVuM2hBSWdjMisyUkc1K2dOY0ZWY3MrWEplRWw0R1orb2psM1JPT21sbCt5ZTdkeW5RPSIsIk1JSUJnakNDQVNtZ0F3SUJBZ0lKQUxVYzVBTGlINXBiTUFvR0NDcUdTTTQ5QkFNRE1EWXhDekFKQmdOVkJBWVRBbFZUTVJNd0VRWURWUVFJREFwRFlXeHBabTl5Ym1saE1SSXdFQVlEVlFRSERBbERkWEJsY25ScGJtOHdIaGNOTWpNd01UQTFNakV6TURJeVdoY05Nek13TVRBeU1qRXpNREl5V2pBMk1Rc3dDUVlEVlFRR0V3SlZVekVUTUJFR0ExVUVDQXdLUTJGc2FXWnZjbTVwWVRFU01CQUdBMVVFQnd3SlEzVndaWEowYVc1dk1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRWMrL0JsK2dvc3BvNnRmOVo3aW81dGRLZHJsTjFZZFZucUVoRURYRFNoemRBSlBRaWphbVhJTUhmOHhXV1RhMXpnb1lUeE9LcGJ1SnREcGx6MVhyaVRhTWdNQjR3REFZRFZSMFRCQVV3QXdFQi96QU9CZ05WSFE4QkFmOEVCQU1DQVFZd0NnWUlLb1pJemowRUF3TURSd0F3UkFJZ2VtV1FYbk1BZFRhZDJKREpXbmc5VTR1QkJMNW1BN1dJMDVIN29IN2M2aVFDSUhpUnFNak5melVBeWl1OWg2ck9VL0sraVRSMEkvM1kvTlNXc1hIWCthY2MiXSwidHlwIjoiSldUIiwiYWxnIjoiRVMyNTYifQ.eyJkYXRhIjp7ImJ1bmRsZUlkIjoiY29tLmV4YW1wbGUud3JvbmcifSwibm90aWZpY2F0aW9uVVVJRCI6IjlhZDU2YmQyLTBiYzYtNDJlMC1hZjI0LWZkOTk2ZDg3YTFlNiIsIm5vdGlmaWNhdGlvblR5cGUiOiJURVNUIn0.WWE31hTB_mcv2O_lf-xI-MNY3d8txc0MzpqFx4QnYDfFIxB95Lo2Fm3r46YSjLLdL7xCWdEJrJP5bHgRCejAGg \ No newline at end of file diff --git a/tests/resources/models/apiException.json b/tests/resources/models/apiException.json new file mode 100644 index 0000000..a270c53 --- /dev/null +++ b/tests/resources/models/apiException.json @@ -0,0 +1,4 @@ +{ + "errorCode": 5000000, + "errorMessage": "An unknown error occurred." +} \ No newline at end of file diff --git a/tests/resources/models/apiTooManyRequestsException.json b/tests/resources/models/apiTooManyRequestsException.json new file mode 100644 index 0000000..3b3cb9c --- /dev/null +++ b/tests/resources/models/apiTooManyRequestsException.json @@ -0,0 +1,4 @@ +{ + "errorCode": 4290000, + "errorMessage": "Rate limit exceeded." +} \ No newline at end of file diff --git a/tests/resources/models/apiUnknownError.json b/tests/resources/models/apiUnknownError.json new file mode 100644 index 0000000..9377dd7 --- /dev/null +++ b/tests/resources/models/apiUnknownError.json @@ -0,0 +1,4 @@ +{ + "errorCode": 9990000, + "errorMessage": "Testing error." +} \ No newline at end of file diff --git a/tests/resources/models/appTransaction.json b/tests/resources/models/appTransaction.json new file mode 100644 index 0000000..b3b937b --- /dev/null +++ b/tests/resources/models/appTransaction.json @@ -0,0 +1,13 @@ +{ + "receiptType": "LocalTesting", + "appAppleId": 531412, + "bundleId": "com.example", + "applicationVersion": "1.2.3", + "versionExternalIdentifier": 512, + "receiptCreationDate": 1698148900000, + "originalPurchaseDate": 1698148800000, + "originalApplicationVersion": "1.1.2", + "deviceVerification": "device_verification_value", + "deviceVerificationNonce": "48ccfa42-7431-4f22-9908-7e88983e105a", + "preorderDate": 1698148700000 +} \ No newline at end of file diff --git a/tests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json b/tests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json new file mode 100644 index 0000000..21fd582 --- /dev/null +++ b/tests/resources/models/extendRenewalDateForAllActiveSubscribersResponse.json @@ -0,0 +1,3 @@ +{ + "requestIdentifier": "758883e8-151b-47b7-abd0-60c4d804c2f5" +} \ No newline at end of file diff --git a/tests/resources/models/extendSubscriptionRenewalDateResponse.json b/tests/resources/models/extendSubscriptionRenewalDateResponse.json new file mode 100644 index 0000000..6c5f89f --- /dev/null +++ b/tests/resources/models/extendSubscriptionRenewalDateResponse.json @@ -0,0 +1,6 @@ +{ + "originalTransactionId": "2312412", + "webOrderLineItemId": "9993", + "success": true, + "effectiveDate": 1698148900000 +} \ No newline at end of file diff --git a/tests/resources/models/getAllSubscriptionStatusesResponse.json b/tests/resources/models/getAllSubscriptionStatusesResponse.json new file mode 100644 index 0000000..b5d139c --- /dev/null +++ b/tests/resources/models/getAllSubscriptionStatusesResponse.json @@ -0,0 +1,35 @@ +{ + "environment": "LocalTesting", + "bundleId": "com.example", + "appAppleId": 5454545, + "data": [ + { + "subscriptionGroupIdentifier": "sub_group_one", + "lastTransactions": [ + { + "status": 1, + "originalTransactionId": "3749183", + "signedTransactionInfo": "signed_transaction_one", + "signedRenewalInfo": "signed_renewal_one" + }, + { + "status": 5, + "originalTransactionId": "5314314134", + "signedTransactionInfo": "signed_transaction_two", + "signedRenewalInfo": "signed_renewal_two" + } + ] + }, + { + "subscriptionGroupIdentifier": "sub_group_two", + "lastTransactions": [ + { + "status": 2, + "originalTransactionId": "3413453", + "signedTransactionInfo": "signed_transaction_three", + "signedRenewalInfo": "signed_renewal_three" + } + ] + } + ] +} \ No newline at end of file diff --git a/tests/resources/models/getNotificationHistoryResponse.json b/tests/resources/models/getNotificationHistoryResponse.json new file mode 100644 index 0000000..75c27b7 --- /dev/null +++ b/tests/resources/models/getNotificationHistoryResponse.json @@ -0,0 +1,27 @@ +{ + "paginationToken": "57715481-805a-4283-8499-1c19b5d6b20a", + "hasMore": true, + "notificationHistory": [ + { + "sendAttempts": [ + { + "attemptDate": 1698148900000, + "sendAttemptResult": "NO_RESPONSE" + }, { + "attemptDate": 1698148950000, + "sendAttemptResult": "SUCCESS" + } + ], + "signedPayload": "signed_payload_one" + }, + { + "sendAttempts": [ + { + "attemptDate": 1698148800000, + "sendAttemptResult": "CIRCULAR_REDIRECT" + } + ], + "signedPayload": "signed_payload_two" + } + ] +} \ No newline at end of file diff --git a/tests/resources/models/getRefundHistoryResponse.json b/tests/resources/models/getRefundHistoryResponse.json new file mode 100644 index 0000000..d1ff65a --- /dev/null +++ b/tests/resources/models/getRefundHistoryResponse.json @@ -0,0 +1,8 @@ +{ + "signedTransactions": [ + "signed_transaction_one", + "signed_transaction_two" + ], + "revision": "revision_output", + "hasMore": true +} \ No newline at end of file diff --git a/tests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json b/tests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json new file mode 100644 index 0000000..9bd7ddc --- /dev/null +++ b/tests/resources/models/getStatusOfSubscriptionRenewalDateExtensionsResponse.json @@ -0,0 +1,7 @@ +{ + "requestIdentifier": "20fba8a0-2b80-4a7d-a17f-85c1854727f8", + "complete": true, + "completeDate": 1698148900000, + "succeededCount": 30, + "failedCount": 2 +} \ No newline at end of file diff --git a/tests/resources/models/getTestNotificationStatusResponse.json b/tests/resources/models/getTestNotificationStatusResponse.json new file mode 100644 index 0000000..9f83b90 --- /dev/null +++ b/tests/resources/models/getTestNotificationStatusResponse.json @@ -0,0 +1,12 @@ +{ + "signedPayload": "signed_payload", + "sendAttempts": [ + { + "attemptDate": 1698148900000, + "sendAttemptResult": "NO_RESPONSE" + }, { + "attemptDate": 1698148950000, + "sendAttemptResult": "SUCCESS" + } + ] +} \ No newline at end of file diff --git a/tests/resources/models/lookupOrderIdResponse.json b/tests/resources/models/lookupOrderIdResponse.json new file mode 100644 index 0000000..09a43d5 --- /dev/null +++ b/tests/resources/models/lookupOrderIdResponse.json @@ -0,0 +1,7 @@ +{ + "status": 1, + "signedTransactions": [ + "signed_transaction_one", + "signed_transaction_two" + ] +} \ No newline at end of file diff --git a/tests/resources/models/requestTestNotificationResponse.json b/tests/resources/models/requestTestNotificationResponse.json new file mode 100644 index 0000000..3ecc9e2 --- /dev/null +++ b/tests/resources/models/requestTestNotificationResponse.json @@ -0,0 +1,3 @@ +{ + "testNotificationToken": "ce3af791-365e-4c60-841b-1674b43c1609" +} \ No newline at end of file diff --git a/tests/resources/models/signedNotification.json b/tests/resources/models/signedNotification.json new file mode 100644 index 0000000..bd473c4 --- /dev/null +++ b/tests/resources/models/signedNotification.json @@ -0,0 +1,16 @@ +{ + "notificationType": "SUBSCRIBED", + "subtype": "INITIAL_BUY", + "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", + "data": { + "environment": "LocalTesting", + "appAppleId": 41234, + "bundleId": "com.example", + "bundleVersion": "1.2.3", + "signedTransactionInfo": "signed_transaction_info_value", + "signedRenewalInfo": "signed_renewal_info_value", + "status": 1 + }, + "version": "2.0", + "signedDate": 1698148900000 +} \ No newline at end of file diff --git a/tests/resources/models/signedRenewalInfo.json b/tests/resources/models/signedRenewalInfo.json new file mode 100644 index 0000000..17c07a8 --- /dev/null +++ b/tests/resources/models/signedRenewalInfo.json @@ -0,0 +1,16 @@ +{ + "expirationIntent": 1, + "originalTransactionId": "12345", + "autoRenewProductId": "com.example.product.2", + "productId": "com.example.product", + "autoRenewStatus": 1, + "isInBillingRetryPeriod": true, + "priceIncreaseStatus": 0, + "gracePeriodExpiresDate": 1698148900000, + "offerType": 2, + "offerIdentifier": "abc.123", + "signedDate": 1698148800000, + "environment": "LocalTesting", + "recentSubscriptionStartDate": 1698148800000, + "renewalDate": 1698148850000 +} \ No newline at end of file diff --git a/tests/resources/models/signedSummaryNotification.json b/tests/resources/models/signedSummaryNotification.json new file mode 100644 index 0000000..3a22ec0 --- /dev/null +++ b/tests/resources/models/signedSummaryNotification.json @@ -0,0 +1,21 @@ +{ + "notificationType": "RENEWAL_EXTENSION", + "subtype": "SUMMARY", + "notificationUUID": "002e14d5-51f5-4503-b5a8-c3a1af68eb20", + "version": "2.0", + "signedDate": 1698148900000, + "summary": { + "environment": "LocalTesting", + "appAppleId": 41234, + "bundleId": "com.example", + "productId": "com.example.product", + "requestIdentifier": "efb27071-45a4-4aca-9854-2a1e9146f265", + "storefrontCountryCodes": [ + "CAN", + "USA", + "MEX" + ], + "succeededCount": 5, + "failedCount": 2 + } +} \ No newline at end of file diff --git a/tests/resources/models/signedTransaction.json b/tests/resources/models/signedTransaction.json new file mode 100644 index 0000000..3c44a56 --- /dev/null +++ b/tests/resources/models/signedTransaction.json @@ -0,0 +1,25 @@ +{ + "transactionId":"23456", + "originalTransactionId":"12345", + "webOrderLineItemId":"34343", + "bundleId":"com.example", + "productId":"com.example.product", + "subscriptionGroupIdentifier":"55555", + "purchaseDate":1698148900000, + "originalPurchaseDate":1698148800000, + "expiresDate":1698149000000, + "quantity":1, + "type":"Auto-Renewable Subscription", + "appAccountToken": "7e3fb20b-4cdb-47cc-936d-99d65f608138", + "inAppOwnershipType":"PURCHASED", + "signedDate":1698148900000, + "revocationReason": 1, + "revocationDate": 1698148950000, + "isUpgraded": true, + "offerType":1, + "offerIdentifier": "abc.123", + "environment":"LocalTesting", + "transactionReason":"PURCHASE", + "storefront":"USA", + "storefrontId":"143441" +} \ No newline at end of file diff --git a/tests/resources/models/transactionHistoryResponse.json b/tests/resources/models/transactionHistoryResponse.json new file mode 100644 index 0000000..c5cc638 --- /dev/null +++ b/tests/resources/models/transactionHistoryResponse.json @@ -0,0 +1,11 @@ +{ + "revision": "revision_output", + "hasMore": true, + "bundleId": "com.example", + "appAppleId": 323232, + "environment": "LocalTesting", + "signedTransactions": [ + "signed_transaction_value", + "signed_transaction_value2" + ] +} \ No newline at end of file diff --git a/tests/resources/models/transactionInfoResponse.json b/tests/resources/models/transactionInfoResponse.json new file mode 100644 index 0000000..57d84e2 --- /dev/null +++ b/tests/resources/models/transactionInfoResponse.json @@ -0,0 +1,3 @@ +{ + "signedTransactionInfo": "signed_transaction_info_value" +} \ No newline at end of file diff --git a/tests/resources/xcode/xcode-app-receipt-empty b/tests/resources/xcode/xcode-app-receipt-empty new file mode 100644 index 0000000..f5acce8 --- /dev/null +++ b/tests/resources/xcode/xcode-app-receipt-empty @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/tests/resources/xcode/xcode-app-receipt-with-transaction b/tests/resources/xcode/xcode-app-receipt-with-transaction new file mode 100644 index 0000000..6b2d6e7 --- /dev/null +++ b/tests/resources/xcode/xcode-app-receipt-with-transaction @@ -0,0 +1 @@ +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 \ No newline at end of file diff --git a/tests/resources/xcode/xcode-signed-app-transaction b/tests/resources/xcode/xcode-signed-app-transaction new file mode 100644 index 0000000..3d11326 --- /dev/null +++ b/tests/resources/xcode/xcode-signed-app-transaction @@ -0,0 +1 @@ +eyJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IkFwcGxlX1hjb2RlX0tleSJ9.eyJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJhcHBsaWNhdGlvblZlcnNpb24iOiIxIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiI0OGM4YjkyZC1jZTBkLTQyMjktYmVkZi1lNjFiNGY5Y2ZjOTIiLCJyZWNlaXB0VHlwZSI6Ilhjb2RlIiwicmVjZWlwdENyZWF0aW9uRGF0ZSI6MTY5NzY4MDEyMjI1Ny40NDcsImRldmljZVZlcmlmaWNhdGlvbiI6ImNZVXNYYzUzRWJZYzBwT2VYRzVkNlwvMzFMR0hlVkdmODRzcVNOME9ySmk1dVwvajJIODlXV0tnUzhOMGhNc01sZiIsInJlcXVlc3REYXRlIjoxNjk3NjgwMTIyMjU3LjQ0Nywib3JpZ2luYWxBcHBsaWNhdGlvblZlcnNpb24iOiIxIiwib3JpZ2luYWxQdXJjaGFzZURhdGUiOi02MjEzNTc2OTYwMDAwMH0.Dpdk_VsO2MUCevwyS407alJpPc1Nq_UIP9EiDHaQBxlyi35NFnsKUVNuFNcGWrGRCCImnb4QGBKHfQC2i4sPCg \ No newline at end of file diff --git a/tests/resources/xcode/xcode-signed-renewal-info b/tests/resources/xcode/xcode-signed-renewal-info new file mode 100644 index 0000000..d18123c --- /dev/null +++ b/tests/resources/xcode/xcode-signed-renewal-info @@ -0,0 +1 @@ +eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJ4NWMiOlsiTUlJQnpEQ0NBWEdnQXdJQkFnSUJBVEFLQmdncWhrak9QUVFEQWpCSU1TSXdJQVlEVlFRREV4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1TSXdJQVlEVlFRS0V4bFRkRzl5WlV0cGRDQlVaWE4wYVc1bklHbHVJRmhqYjJSbE1CNFhEVEl6TVRBeE9UQXhORFV6TmxvWERUSTBNVEF4T0RBeE5EVXpObG93U0RFaU1DQUdBMVVFQXhNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRFaU1DQUdBMVVFQ2hNWlUzUnZjbVZMYVhRZ1ZHVnpkR2x1WnlCcGJpQllZMjlrWlRCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQktYRVFnWWpDb3VQdFRzdEdyS3BZOEk1M25IN3JiREhuY0lMR25vZ1NBdWxJSTNzXC91Zk0wZzlEYzNCY3I0OTdBVWd6R1R2V3Bpd0p4cGVCMzcxTmdWK2pUREJLTUJJR0ExVWRFd0VCXC93UUlNQVlCQWY4Q0FRQXdKQVlEVlIwUkJCMHdHNEVaVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEFPQmdOVkhROEJBZjhFQkFNQ0I0QXdDZ1lJS29aSXpqMEVBd0lEU1FBd1JnSWhBTVp2VllKNjRDRitoMmZtc213dnpBY2VQcklEMTNycElKR0JFVytXZ3BwdEFpRUF4V2l5NCtUMXp0MzdWc3UwdmI2WXVtMCtOTHREcUhsSzZycE1jdjZKZm5BPSJdLCJhbGciOiJFUzI1NiJ9.eyJkZXZpY2VWZXJpZmljYXRpb24iOiJ1K1cxb1FUcXZGSE9RK1pCZTRRMHhQTUMyOGtxRUZ2YmJzRVBwTEtEVlJGdjFHSkdlZ21yTkhWb09ZTU9QdmIyIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZGV2aWNlVmVyaWZpY2F0aW9uTm9uY2UiOiIzNDM5OTE5ZS04N2M5LTQ3YjYtYWVlZS0yODIzZjdhOWQzYzMiLCJyZW5ld2FsRGF0ZSI6MTcwMDM1ODMzNjA0OS43Mjk3LCJvcmlnaW5hbFRyYW5zYWN0aW9uSWQiOiIwIiwicmVjZW50U3Vic2NyaXB0aW9uU3RhcnREYXRlIjoxNjk3Njc5OTM2MDQ5LjcyOTcsImF1dG9SZW5ld1N0YXR1cyI6MSwic2lnbmVkRGF0ZSI6MTY5NzY3OTkzNjcxMS4wNzQ3LCJlbnZpcm9ubWVudCI6Ilhjb2RlIiwiYXV0b1JlbmV3UHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIn0.WnT3aB9Lwjbr0ICUGn_5CdglzedVd7eOkrqirhcWFvwJZzN1FajuMV6gFEbgD82aL0Ix6HGZcwkNDlVNLvYOEQ \ No newline at end of file diff --git a/tests/resources/xcode/xcode-signed-transaction b/tests/resources/xcode/xcode-signed-transaction new file mode 100644 index 0000000..6daf517 --- /dev/null +++ b/tests/resources/xcode/xcode-signed-transaction @@ -0,0 +1 @@ +eyJraWQiOiJBcHBsZV9YY29kZV9LZXkiLCJ0eXAiOiJKV1QiLCJhbGciOiJFUzI1NiIsIng1YyI6WyJNSUlCekRDQ0FYR2dBd0lCQWdJQkFUQUtCZ2dxaGtqT1BRUURBakJJTVNJd0lBWURWUVFERXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTVNJd0lBWURWUVFLRXhsVGRHOXlaVXRwZENCVVpYTjBhVzVuSUdsdUlGaGpiMlJsTUI0WERUSXpNVEF4T1RBeE5EVXpObG9YRFRJME1UQXhPREF4TkRVek5sb3dTREVpTUNBR0ExVUVBeE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEVpTUNBR0ExVUVDaE1aVTNSdmNtVkxhWFFnVkdWemRHbHVaeUJwYmlCWVkyOWtaVEJaTUJNR0J5cUdTTTQ5QWdFR0NDcUdTTTQ5QXdFSEEwSUFCS1hFUWdZakNvdVB0VHN0R3JLcFk4STUzbkg3cmJESG5jSUxHbm9nU0F1bElJM3NcL3VmTTBnOURjM0JjcjQ5N0FVZ3pHVHZXcGl3SnhwZUIzNzFOZ1YralREQktNQklHQTFVZEV3RUJcL3dRSU1BWUJBZjhDQVFBd0pBWURWUjBSQkIwd0c0RVpVM1J2Y21WTGFYUWdWR1Z6ZEdsdVp5QnBiaUJZWTI5a1pUQU9CZ05WSFE4QkFmOEVCQU1DQjRBd0NnWUlLb1pJemowRUF3SURTUUF3UmdJaEFNWnZWWUo2NENGK2gyZm1zbXd2ekFjZVBySUQxM3JwSUpHQkVXK1dncHB0QWlFQXhXaXk0K1QxenQzN1ZzdTB2YjZZdW0wK05MdERxSGxLNnJwTWN2NkpmbkE9Il19.eyJpbkFwcE93bmVyc2hpcFR5cGUiOiJQVVJDSEFTRUQiLCJwdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5Nywic3Vic2NyaXB0aW9uR3JvdXBJZGVudGlmaWVyIjoiNkYzQTkzQUIiLCJzaWduZWREYXRlIjoxNjk3Njc5OTM2MDU2LjQ4NSwib3JpZ2luYWxQdXJjaGFzZURhdGUiOjE2OTc2Nzk5MzYwNDkuNzI5NywiaXNVcGdyYWRlZCI6ZmFsc2UsImRldmljZVZlcmlmaWNhdGlvbiI6InNHRG5wZytvemI4dXdEU3VDRFoyb1ZabzFDS3JiQjh1alI4VnhDeGh5a1J3eUJJSzZ4NlhDeUVSbTh5V3J6RTgiLCJvZmZlclR5cGUiOjEsInF1YW50aXR5IjoxLCJ0cmFuc2FjdGlvbklkIjoiMCIsInR5cGUiOiJBdXRvLVJlbmV3YWJsZSBTdWJzY3JpcHRpb24iLCJ0cmFuc2FjdGlvblJlYXNvbiI6IlBVUkNIQVNFIiwicHJvZHVjdElkIjoicGFzcy5wcmVtaXVtIiwiZXhwaXJlc0RhdGUiOjE3MDAzNTgzMzYwNDkuNzI5NywiZW52aXJvbm1lbnQiOiJYY29kZSIsInN0b3JlZnJvbnRJZCI6IjE0MzQ0MSIsIm9yaWdpbmFsVHJhbnNhY3Rpb25JZCI6IjAiLCJidW5kbGVJZCI6ImNvbS5leGFtcGxlLm5hdHVyZWxhYi5iYWNreWFyZGJpcmRzLmV4YW1wbGUiLCJkZXZpY2VWZXJpZmljYXRpb25Ob25jZSI6IjdlZGVhODdkLTk4ZjAtNDJkMC05NjgyLTQ5Y2E4MTAyMmY3MyIsIndlYk9yZGVyTGluZUl0ZW1JZCI6IjAiLCJzdG9yZWZyb250IjoiVVNBIn0.rkJYnvujStteRkMHhoIR2ThmNFnyKcx5XxIakXYdh-1oKtEVEU5zQAiONaLDpBDO5JhLLrTbfp7LS5tMiqmgHw \ No newline at end of file diff --git a/tests/test_receipt_utility.py b/tests/test_receipt_utility.py new file mode 100644 index 0000000..97d3a7d --- /dev/null +++ b/tests/test_receipt_utility.py @@ -0,0 +1,37 @@ +# Copyright (c) 2023 Apple Inc. Licensed under MIT License. + +import unittest +from appstoreserverlibrary.receipt_utility import ReceiptUtility + +from tests.util import read_data_from_file + +APP_RECEIPT_EXPECTED_TRANSACTION_ID = "0" +TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID = "33993399" + +class ReceiptUtilityTest(unittest.TestCase): + def test_xcode_app_receipt_extraction_with_no_transactions(self): + receipt = read_data_from_file("tests/resources/xcode/xcode-app-receipt-empty") + + receipt_util = ReceiptUtility() + + extracted_transaction_id = receipt_util.extract_transaction_id_from_app_receipt(receipt) + + self.assertIsNone(extracted_transaction_id) + + def test_xcode_app_receipt_extraction_with_transactions(self): + receipt = read_data_from_file("tests/resources/xcode/xcode-app-receipt-with-transaction") + + receipt_util = ReceiptUtility() + + extracted_transaction_id = receipt_util.extract_transaction_id_from_app_receipt(receipt) + + self.assertEqual(APP_RECEIPT_EXPECTED_TRANSACTION_ID, extracted_transaction_id) + + def test_transaction_receipt_extraction(self): + receipt = read_data_from_file("tests/resources/mock_signed_data/legacyTransaction") + + receipt_util = ReceiptUtility() + + extracted_transaction_id = receipt_util.extract_transaction_id_from_transaction_receipt(receipt) + + self.assertEqual(TRANSACTION_RECEIPT_EXPECTED_TRANSACTION_ID, extracted_transaction_id) \ No newline at end of file diff --git a/tests/test_xcode_signed_data.py b/tests/test_xcode_signed_data.py new file mode 100644 index 0000000..ccc2ca2 --- /dev/null +++ b/tests/test_xcode_signed_data.py @@ -0,0 +1,95 @@ +# Copyright (c) 2023 Apple Inc. Licensed under MIT License. + +import unittest +from appstoreserverlibrary.models.AutoRenewStatus import AutoRenewStatus +from appstoreserverlibrary.models.Environment import Environment +from appstoreserverlibrary.models.InAppOwnershipType import InAppOwnershipType +from appstoreserverlibrary.models.OfferType import OfferType +from appstoreserverlibrary.models.TransactionReason import TransactionReason +from appstoreserverlibrary.models.Type import Type +from appstoreserverlibrary.receipt_utility import ReceiptUtility +from appstoreserverlibrary.signed_data_verifier import VerificationException + +from tests.util import get_signed_data_verifier, read_data_from_file + +XCODE_BUNDLE_ID = "com.example.naturelab.backyardbirds.example" + +class ReceiptUtilityTest(unittest.TestCase): + def test_xcode_signed_app_transaction(self): + verifier = get_signed_data_verifier(Environment.XCODE, XCODE_BUNDLE_ID) + encoded_app_transaction = read_data_from_file("tests/resources/xcode/xcode-signed-app-transaction") + + app_transaction = verifier.verify_and_decode_app_transaction(encoded_app_transaction) + + self.assertIsNotNone(app_transaction) + self.assertIsNone(app_transaction.appAppleId) + self.assertEqual(XCODE_BUNDLE_ID, app_transaction.bundleId) + self.assertEqual("1", app_transaction.applicationVersion) + self.assertIsNone(app_transaction.versionExternalIdentifier) + self.assertEqual(-62135769600000, app_transaction.originalPurchaseDate) + self.assertEqual("1", app_transaction.originalApplicationVersion) + self.assertEqual("cYUsXc53EbYc0pOeXG5d6/31LGHeVGf84sqSN0OrJi5u/j2H89WWKgS8N0hMsMlf", app_transaction.deviceVerification) + self.assertEqual("48c8b92d-ce0d-4229-bedf-e61b4f9cfc92", app_transaction.deviceVerificationNonce) + self.assertIsNone(app_transaction.preorderDate) + self.assertEqual(Environment.XCODE, app_transaction.receiptType) + + def test_xcode_signed_transaction(self): + verifier = get_signed_data_verifier(Environment.XCODE, XCODE_BUNDLE_ID) + encoded_transaction = read_data_from_file("tests/resources/xcode/xcode-signed-transaction") + + transaction = verifier.verify_and_decode_signed_transaction(encoded_transaction) + + self.assertEqual("0", transaction.originalTransactionId) + self.assertEqual("0", transaction.transactionId) + self.assertEqual("0", transaction.webOrderLineItemId) + self.assertEqual(XCODE_BUNDLE_ID, transaction.bundleId) + self.assertEqual("pass.premium", transaction.productId) + self.assertEqual("6F3A93AB", transaction.subscriptionGroupIdentifier) + self.assertEqual(1697679936049, transaction.purchaseDate) + self.assertEqual(1697679936049, transaction.originalPurchaseDate) + self.assertEqual(1700358336049, transaction.expiresDate) + self.assertEqual(1, transaction.quantity) + self.assertEqual(Type.AUTO_RENEWABLE_SUBSCRIPTION, transaction.type) + self.assertIsNone(transaction.appAccountToken) + self.assertEqual(InAppOwnershipType.PURCHASED, transaction.inAppOwnershipType) + self.assertEqual(1697679936056, transaction.signedDate) + self.assertIsNone(transaction.revocationReason) + self.assertIsNone(transaction.revocationDate) + self.assertFalse(transaction.isUpgraded) + self.assertEqual(OfferType.INTRODUCTORY_OFFER, transaction.offerType) + self.assertIsNone(transaction.offerIdentifier) + self.assertEqual(Environment.XCODE, transaction.environment) + self.assertEqual("USA", transaction.storefront) + self.assertEqual("143441", transaction.storefrontId) + self.assertEqual(TransactionReason.PURCHASE, transaction.transactionReason) + + def test_xcode_signed_renewal_info(self): + verifier = get_signed_data_verifier(Environment.XCODE, XCODE_BUNDLE_ID) + encoded_renewal_info = read_data_from_file("tests/resources/xcode/xcode-signed-renewal-info") + + renewal_info = verifier.verify_and_decode_renewal_info(encoded_renewal_info) + + self.assertIsNone(renewal_info.expirationIntent) + self.assertEqual("0", renewal_info.originalTransactionId) + self.assertEqual("pass.premium", renewal_info.autoRenewProductId) + self.assertEqual("pass.premium", renewal_info.productId) + self.assertEqual(AutoRenewStatus.ON, renewal_info.autoRenewStatus) + self.assertIsNone(renewal_info.isInBillingRetryPeriod) + self.assertIsNone(renewal_info.priceIncreaseStatus) + self.assertIsNone(renewal_info.gracePeriodExpiresDate) + self.assertIsNone(renewal_info.offerType) + self.assertIsNone(renewal_info.offerIdentifier) + self.assertEqual(1697679936711, renewal_info.signedDate) + self.assertEqual(Environment.XCODE, renewal_info.environment) + self.assertEqual(1697679936049, renewal_info.recentSubscriptionStartDate) + self.assertEqual(1700358336049, renewal_info.renewalDate) + + def test_xcode_signed_app_transaction_with_production_environment(self): + verifier = get_signed_data_verifier(Environment.PRODUCTION, XCODE_BUNDLE_ID) + encoded_app_transaction = read_data_from_file("tests/resources/xcode/xcode-signed-app-transaction") + try: + verifier.verify_and_decode_app_transaction(encoded_app_transaction) + except VerificationException: + return + self.assertFalse(True) + diff --git a/tests/util.py b/tests/util.py new file mode 100644 index 0000000..8dd9373 --- /dev/null +++ b/tests/util.py @@ -0,0 +1,45 @@ +# Copyright (c) 2023 Apple Inc. Licensed under MIT License. + +from typing import Any, Dict +import jwt +from jwt.api_jwt import decode_complete +import json +import os + +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives import serialization +from appstoreserverlibrary.models.Environment import Environment + +from appstoreserverlibrary.signed_data_verifier import SignedDataVerifier + +def create_signed_data_from_json(path: str) -> str: + data = read_data_from_file(path) + decoded_data = json.loads(data) + private_key = ec.generate_private_key(ES256).private_bytes(encoding=serialization.Encoding.PEM, format=serialization.PrivateFormat.TraditionalOpenSSL, encryption_algorithm=serialization.NoEncryption()).decode() + return jwt.encode(payload=decoded_data, key=private_key, algorithm='ES256') + +def decode_json_from_signed_date(data: str) -> Dict[str, Any]: + public_key = ec.generate_private_key(ES256).public_key().public_bytes(encoding=serialization.Encoding.PEM, format=serialization.PublicFormat.SubjectPublicKeyInfo).decode() + return decode_complete(jwt=data, key=public_key, algorithms=['ES256'], options={"verify_signature": False}) + +def read_data_from_file(path: str) -> str: + full_path = os.path.join(path) + with open(full_path, mode='r') as test_file: + return test_file.read() + +def read_data_from_binary_file(path: str) -> str: + full_path = os.path.join(path) + with open(full_path, mode='rb') as test_file: + return test_file.read() + +class ES256(ec.EllipticCurve): + name="prime256v1" + key_size = 256 + +def get_signed_data_verifier(env: Environment, bundle_id: str, app_apple_id: int = 1234) -> SignedDataVerifier: + verifier = SignedDataVerifier([read_data_from_binary_file('tests/resources/certs/testCA.der')], False, env, bundle_id, app_apple_id) + verifier._chain_verifier.enable_strict_checks = False # We don't have authority identifiers on test certs + return verifier + +def get_default_signed_data_verifier(): + return get_signed_data_verifier(Environment.LOCAL_TESTING, "com.example") \ No newline at end of file