├── .github └── PULL_REQUEST_TEMPLATE.md ├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── LICENSE ├── Makefile ├── NOTICE ├── README.md ├── build.xml ├── composer.json ├── phar-stub.php ├── phpunit-phar.xml ├── phpunit.xml ├── src └── Amazon │ └── InstantAccess │ ├── Controllers │ ├── AccountLinkingController.php │ ├── Controller.php │ └── PurchaseController.php │ ├── Log │ └── Logger.php │ ├── Serialization │ ├── Enums │ │ ├── Enum.php │ │ ├── FulfillPurchaseReasonValue.php │ │ ├── FulfillPurchaseResponseValue.php │ │ ├── GetUserIdResponseValue.php │ │ ├── InstantAccessOperationValue.php │ │ ├── RevokePurchaseReasonValue.php │ │ ├── RevokePurchaseResponseValue.php │ │ ├── SubscriptionActivateResponseValue.php │ │ ├── SubscriptionDeactivatePeriodValue.php │ │ ├── SubscriptionDeactivateReasonValue.php │ │ └── SubscriptionDeactivateResponseValue.php │ ├── FulfillPurchaseRequest.php │ ├── FulfillPurchaseResponse.php │ ├── GetUserIdRequest.php │ ├── GetUserIdResponse.php │ ├── InstantAccessRequest.php │ ├── InstantAccessResponse.php │ ├── RevokePurchaseRequest.php │ ├── RevokePurchaseResponse.php │ ├── SubscriptionActivateRequest.php │ ├── SubscriptionActivateResponse.php │ ├── SubscriptionDeactivateRequest.php │ └── SubscriptionDeactivateResponse.php │ ├── Signature │ ├── AuthorizationHeader.php │ ├── Credential.php │ ├── CredentialStore.php │ ├── Request.php │ └── Signer.php │ └── Utils │ ├── DateUtils.php │ ├── HttpUtils.php │ └── IOUtils.php ├── tools └── RequestBuilder.php └── tst ├── Amazon └── InstantAccess │ ├── Controllers │ ├── AccountLinkingControllerTest.php │ └── PurchaseControllerTest.php │ ├── Serialization │ ├── Enums │ │ └── InstantAccessOperationValueTest.php │ ├── FulfillPurchaseRequestTest.php │ ├── FulfillPurchaseResponseTest.php │ ├── GetUserIdRequestTest.php │ ├── GetUserIdResponseTest.php │ ├── InstantAccessRequestTest.php │ ├── InstantAccessResponseTest.php │ ├── RevokePurchaseRequestTest.php │ ├── RevokePurchaseResponseTest.php │ ├── SubscriptionActivateRequestTest.php │ ├── SubscriptionActivateResponseTest.php │ ├── SubscriptionDeactivateRequestTest.php │ └── SubscriptionDeactivateResponseTest.php │ ├── Signature │ ├── AuthorizationHeaderTest.php │ ├── CredentialStoreTest.php │ ├── CredentialTest.php │ ├── RequestTest.php │ └── SignerTest.php │ └── Utils │ ├── HttpUtilsTest.php │ └── IOUtilsTest.php ├── bootstrap-phar.php └── bootstrap.php /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | *Issue #, if available:* 2 | 3 | *Description of changes:* 4 | 5 | 6 | By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license. 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build 2 | vendor 3 | composer.lock 4 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | ## Code of Conduct 2 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 3 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 4 | opensource-codeofconduct@amazon.com with any additional questions or comments. 5 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing Guidelines 2 | 3 | Thank you for your interest in contributing to our project. Whether it's a bug report, new feature, correction, or additional 4 | documentation, we greatly value feedback and contributions from our community. 5 | 6 | Please read through this document before submitting any issues or pull requests to ensure we have all the necessary 7 | information to effectively respond to your bug report or contribution. 8 | 9 | 10 | ## Reporting Bugs/Feature Requests 11 | 12 | We welcome you to use the GitHub issue tracker to report bugs or suggest features. 13 | 14 | When filing an issue, please check [existing open](https://github.com/amzn/amazon-instant-access-sdk-php/issues), or [recently closed](https://github.com/amzn/amazon-instant-access-sdk-php/issues?utf8=%E2%9C%93&q=is%3Aissue%20is%3Aclosed%20), issues to make sure somebody else hasn't already 15 | reported the issue. Please try to include as much information as you can. Details like these are incredibly useful: 16 | 17 | * A reproducible test case or series of steps 18 | * The version of our code being used 19 | * Any modifications you've made relevant to the bug 20 | * Anything unusual about your environment or deployment 21 | 22 | 23 | ## Contributing via Pull Requests 24 | Contributions via pull requests are much appreciated. Before sending us a pull request, please ensure that: 25 | 26 | 1. You are working against the latest source on the *master* branch. 27 | 2. You check existing open, and recently merged, pull requests to make sure someone else hasn't addressed the problem already. 28 | 3. You open an issue to discuss any significant work - we would hate for your time to be wasted. 29 | 30 | To send us a pull request, please: 31 | 32 | 1. Fork the repository. 33 | 2. Modify the source; please focus on the specific change you are contributing. If you also reformat all the code, it will be hard for us to focus on your change. 34 | 3. Ensure local tests pass. 35 | 4. Commit to your fork using clear commit messages. 36 | 5. Send us a pull request, answering any default questions in the pull request interface. 37 | 6. Pay attention to any automated CI failures reported in the pull request, and stay involved in the conversation. 38 | 39 | GitHub provides additional document on [forking a repository](https://help.github.com/articles/fork-a-repo/) and 40 | [creating a pull request](https://help.github.com/articles/creating-a-pull-request/). 41 | 42 | 43 | ## Finding contributions to work on 44 | Looking at the existing issues is a great way to find something to contribute on. As our projects, by default, use the default GitHub issue labels ((enhancement/bug/duplicate/help wanted/invalid/question/wontfix), looking at any ['help wanted'](https://github.com/amzn/amazon-instant-access-sdk-php/labels/help%20wanted) issues is a great place to start. 45 | 46 | 47 | ## Code of Conduct 48 | This project has adopted the [Amazon Open Source Code of Conduct](https://aws.github.io/code-of-conduct). 49 | For more information see the [Code of Conduct FAQ](https://aws.github.io/code-of-conduct-faq) or contact 50 | opensource-codeofconduct@amazon.com with any additional questions or comments. 51 | 52 | 53 | ## Security issue notifications 54 | If you discover a potential security issue in this project we ask that you notify AWS/Amazon Security via our [vulnerability reporting page](http://aws.amazon.com/security/vulnerability-reporting/). Please do **not** create a public github issue. 55 | 56 | 57 | ## Licensing 58 | 59 | See the [LICENSE](https://github.com/amzn/amazon-instant-access-sdk-php/blob/master/LICENSE) file for our project's licensing. We will ask you to confirm the licensing of your contribution. 60 | 61 | We may ask you to sign a [Contributor License Agreement (CLA)](http://en.wikipedia.org/wiki/Contributor_License_Agreement) for larger changes. 62 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | include $(shell brazil-makefiles) 2 | 3 | build: clean 4 | @echo "build" 5 | 6 | clean: 7 | @echo "clean" 8 | 9 | test: 10 | @echo "test" 11 | -------------------------------------------------------------------------------- /NOTICE: -------------------------------------------------------------------------------- 1 | Amazon Instance Access SDK for PHP 2 | Copyright 2015 Amazon.com, Inc. or its affiliates. All Rights Reserved. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Amazon Instant Access SDK for PHP 2 | 3 | Amazon Instant Access (AIA), is a fulfillment technology for virtual content that is purchased on the Amazon web site and needs to be 4 | delivered to a third party server. The **Amazon Instant Access SDK for PHP** enables PHP developers to easily integrate their systems and 5 | onboard with the AIA system. 6 | -------------------------------------------------------------------------------- /build.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "amazon/instantaccess-sdk-php", 3 | "version": "1.1.0", 4 | "homepage": "http://developer.amazon.com/", 5 | "description":"Amazon Instant Access SDK for PHP", 6 | "keywords":["amazon","sdk"], 7 | "type":"library", 8 | "license":"Apache-2.0", 9 | "authors":[ 10 | { 11 | "name":"Amazon Digital Services, Inc.", 12 | "homepage":"http://developer.amazon.com" 13 | } 14 | ], 15 | "require": { 16 | "php": ">=5.5.37", 17 | "psr/log": "1.0.*" 18 | }, 19 | "suggest": { 20 | "monolog/monolog": "Adds support for logging" 21 | }, 22 | "require-dev": { 23 | "symfony/class-loader": "3.0.*", 24 | "monolog/monolog": "1.6.*", 25 | "phpunit/phpunit": "4.8.*" 26 | }, 27 | "autoload": { 28 | "psr-4": { 29 | "": "src/" 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /phar-stub.php: -------------------------------------------------------------------------------- 1 | addPrefix('Amazon', 'phar://instant-access-sdk-php.phar/src'); 25 | $classLoader->addPrefix('Psr', 'phar://instant-access-sdk-php.phar/vendor/psr/log'); 26 | $classLoader->addPrefix('Monolog', 'phar://instant-access-sdk-php.phar/vendor/monolog/monolog/src'); 27 | 28 | // activate the autoloader 29 | $classLoader->register(); 30 | 31 | return $classLoader; 32 | 33 | __HALT_COMPILER(); 34 | -------------------------------------------------------------------------------- /phpunit-phar.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | ./tst/Amazon/InstantAccess 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | ./tst/Amazon/InstantAccess 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | ./src/Amazon/InstantAccess 25 | 26 | ./src/Amazon/InstantAccess/Log/Logger.php 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Controllers/AccountLinkingController.php: -------------------------------------------------------------------------------- 1 | getUserIdCallback = $callback; 45 | } 46 | 47 | protected function processOperation($operation) 48 | { 49 | switch ($operation) { 50 | case InstantAccessOperationValue::GET_USER_ID: 51 | $callback = $this->getUserIdCallback; 52 | $request = GetUserIdRequest::createFromJson($this->request->getBody()); 53 | break; 54 | default: 55 | throw new \InvalidArgumentException( 56 | sprintf('Operation %s is not supported by %s', $operation, get_class($this)) 57 | ); 58 | } 59 | 60 | if (!$callback) { 61 | throw new \UnexpectedValueException(sprintf('Callback not set for %s', $operation)); 62 | } 63 | 64 | $iaResponse = $callback($request); 65 | 66 | return $iaResponse; 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Controllers/Controller.php: -------------------------------------------------------------------------------- 1 | credentialStore = $credentialStore; 48 | $this->signer = $signer ?: new Signer(); 49 | } 50 | 51 | /** 52 | * Processes the specific operation and return a response. 53 | * 54 | * @param string $operation the name of the operation being processed 55 | * 56 | * @return InstantAccessResponse an object that extends {@link InstantAccessResponse} object 57 | */ 58 | abstract protected function processOperation($operation); 59 | 60 | /** 61 | * Processes a request created from $_SERVER and the body of the request. 62 | * 63 | * At the end this function sets the response headers and writes the content. After calling this, be sure 64 | * to write the string returned to the output stream. 65 | * 66 | * NOTE: This consumes the body of the request which can cause issues when you try and read it again. 67 | * 68 | * @uses Controller::processOperation() to call the correct callback 69 | * 70 | * @param array $server the $_SERVER array 71 | * @param string|null $requestBody the path/stream of the body of the request, defaults to php://input 72 | * 73 | * @return string the response string ready to be sent 74 | */ 75 | public function process(array $server, $requestBody = 'php://input') 76 | { 77 | try { 78 | Logger::getLogger()->info(sprintf('Started processing request received by %s', get_class($this))); 79 | 80 | // convert all errors to exception so they can be caught and not just break the script 81 | set_error_handler( 82 | function ($code, $message, $file, $line, $context) { 83 | throw new \Exception($message, $code); 84 | } 85 | ); 86 | 87 | // read content of request to a string 88 | $body = file_get_contents($requestBody); 89 | 90 | // create request object 91 | $this->request = new Request($server, $body); 92 | 93 | Logger::getLogger()->debug(sprintf('Request: %s', (string)$this->request)); 94 | 95 | // verify the request againts the credential store 96 | if (!$this->signer->verify($this->request, $this->credentialStore)) { 97 | throw new \Exception('Request validation failed.'); 98 | } 99 | 100 | // deserialize the content to a InstantAccessRequest object so we can check which operation is going 101 | // to be called 102 | $iaRequest = InstantAccessRequest::createFromJson($this->request->getBody()); 103 | 104 | Logger::getLogger()->info(sprintf('Processing request as %s operation', $iaRequest->getOperation())); 105 | 106 | // process the request according to the operation 107 | $iaResponse = $this->processOperation($iaRequest->getOperation()); 108 | 109 | // check if the response is valid 110 | if (!is_subclass_of($iaResponse, 'Amazon\InstantAccess\Serialization\InstantAccessResponse')) { 111 | throw new \UnexpectedValueException( 112 | 'Invalid response returned by the controller. ' . 113 | 'It needs to be a subclass of InstantAccessResponse' 114 | ); 115 | } 116 | 117 | HttpUtils::setResponseHeader('HTTP/1.1 200 OK', true, 200); 118 | HttpUtils::setResponseHeader('Content-Type: application/json'); 119 | 120 | $response = $iaResponse->toJson(); 121 | 122 | Logger::getLogger()->debug(sprintf('Response: %s', $response)); 123 | Logger::getLogger()->info(sprintf('Request processed succesfully')); 124 | } catch (\Exception $e) { 125 | Logger::getLogger()->error(sprintf('Unable to process request: %s', $e)); 126 | HttpUtils::setResponseHeader('HTTP/1.1 500 Internal Server Error', true, 500); 127 | HttpUtils::setResponseHeader('Content-Type: text/plain'); 128 | $response = ''; 129 | } 130 | 131 | // restore previous error handler 132 | restore_error_handler(); 133 | 134 | return $response; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Controllers/PurchaseController.php: -------------------------------------------------------------------------------- 1 | 32 | * {@link PurchaseController::onFulfillPurchase()}
33 | * {@link PurchaseController::onRevokePurchase()}
34 | * {@link PurchaseController::onSubscriptionActivate()}
35 | * {@link PurchaseController::onSubscriptionDeactivate()}
36 | *
37 | * The callbacks are called when the {@link Controller::process()} method is invoked. 38 | */ 39 | class PurchaseController extends Controller 40 | { 41 | /** @var \Closure */ 42 | private $fulfillPurchaseCallback; 43 | /** @var \Closure */ 44 | private $revokePurchaseCallback; 45 | /** @var \Closure */ 46 | private $subscriptionActivateCallback; 47 | /** @var \Closure */ 48 | private $subscriptionDeactivateCallback; 49 | 50 | /** 51 | * Set the callback function for fulfill purchase 52 | * 53 | * @param \Closure $callback a callable object that receives a {@link FulfillPurchaseRequest} object 54 | * and returns a {@link FulfillPurchaseResponse} 55 | */ 56 | public function onFulfillPurchase(\Closure $callback) 57 | { 58 | $this->fulfillPurchaseCallback = $callback; 59 | } 60 | 61 | /** 62 | * Set the callback function for revoke purchase 63 | * 64 | * @param \Closure $callback a callable object that receives a {@link RevokePurchaseRequest} object 65 | * and returns a {@link RevokePurchaseResponse} 66 | */ 67 | public function onRevokePurchase(\Closure $callback) 68 | { 69 | $this->revokePurchaseCallback = $callback; 70 | } 71 | 72 | /** 73 | * Set the callback function for subscription activate 74 | * 75 | * @param \Closure $callback a callable object that receives a {@link SubscriptionActivateRequest} object 76 | * and returns a {@link SubscriptionActivateResponse} 77 | */ 78 | public function onSubscriptionActivate(\Closure $callback) 79 | { 80 | $this->subscriptionActivateCallback = $callback; 81 | } 82 | 83 | /** 84 | * Set the callback function for subscription deactivate 85 | * 86 | * @param \Closure $callback a callable object that receives a {@link SubscriptionDeactivateRequest} object 87 | * and returns a {@link SubscriptionDeactivateResponse} 88 | */ 89 | public function onSubscriptionDeactivate(\Closure $callback) 90 | { 91 | $this->subscriptionDeactivateCallback = $callback; 92 | } 93 | 94 | protected function processOperation($operation) 95 | { 96 | switch ($operation) { 97 | case InstantAccessOperationValue::PURCHASE: 98 | $callback = $this->fulfillPurchaseCallback; 99 | $request = FulfillPurchaseRequest::createFromJson($this->request->getBody()); 100 | break; 101 | case InstantAccessOperationValue::REVOKE: 102 | $callback = $this->revokePurchaseCallback; 103 | $request = RevokePurchaseRequest::createFromJson($this->request->getBody()); 104 | break; 105 | case InstantAccessOperationValue::SUBSCRIPTION_ACTIVATE: 106 | $callback = $this->subscriptionActivateCallback; 107 | $request = SubscriptionActivateRequest::createFromJson($this->request->getBody()); 108 | break; 109 | case InstantAccessOperationValue::SUBSCRIPTION_DEACTIVATE: 110 | $callback = $this->subscriptionDeactivateCallback; 111 | $request = SubscriptionDeactivateRequest::createFromJson($this->request->getBody()); 112 | break; 113 | default: 114 | throw new \InvalidArgumentException( 115 | sprintf('Operation %s is not supported by %s', $operation, get_class($this)) 116 | ); 117 | } 118 | 119 | if (!$callback) { 120 | throw new \UnexpectedValueException(sprintf('Callback not set for %s', $operation)); 121 | } 122 | 123 | $iaResponse = $callback($request); 124 | 125 | return $iaResponse; 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Log/Logger.php: -------------------------------------------------------------------------------- 1 | getConstants(); 27 | } 28 | 29 | /** 30 | * Check if $name is a value supported by the enum 31 | * 32 | * @param string $name a string 33 | * @return boolean true if $name is a valid value in this enum, false otherwise 34 | */ 35 | public static function isValid($name) 36 | { 37 | return in_array($name, self::getConstants()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/Enums/FulfillPurchaseReasonValue.php: -------------------------------------------------------------------------------- 1 | setPurchaseToken($jsonObject->purchaseToken); 48 | $newObject->setUserId($jsonObject->userId); 49 | $newObject->setProductId($jsonObject->productId); 50 | $newObject->setReason($jsonObject->reason); 51 | }; 52 | 53 | $object = parent::createFromJson($jsonString, $callback); 54 | 55 | return $object; 56 | } 57 | 58 | public function getPurchaseToken() 59 | { 60 | return $this->purchaseToken; 61 | } 62 | 63 | public function setPurchaseToken($purchaseToken) 64 | { 65 | $this->purchaseToken = $purchaseToken; 66 | return $this; 67 | } 68 | 69 | public function getUserId() 70 | { 71 | return $this->userId; 72 | } 73 | 74 | public function setUserId($userId) 75 | { 76 | $this->userId = $userId; 77 | return $this; 78 | } 79 | 80 | public function getProductId() 81 | { 82 | return $this->productId; 83 | } 84 | 85 | public function setProductId($productId) 86 | { 87 | $this->productId = $productId; 88 | return $this; 89 | } 90 | 91 | public function getReason() 92 | { 93 | return $this->reason; 94 | } 95 | 96 | /** 97 | * Set the request reason. 98 | * 99 | * @param string a string representation of the reason 100 | * 101 | * @see Amazon\InstantAccess\Serialization\Enums\FulfillPurchaseReasonValue For reason values 102 | */ 103 | public function setReason($reason) 104 | { 105 | if (!FulfillPurchaseReasonValue::isValid($reason)) { 106 | throw new \InvalidArgumentException(sprintf('Invalid reason value: %s', $reason)); 107 | } 108 | 109 | $this->reason = $reason; 110 | return $this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/FulfillPurchaseResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/GetUserIdRequest.php: -------------------------------------------------------------------------------- 1 | setInfoField1($jsonObject->infoField1); 42 | 43 | // optional field 44 | if (isset($jsonObject->infoField2)) { 45 | $newObject->setInfoField2($jsonObject->infoField2); 46 | } 47 | 48 | // optional field 49 | if (isset($jsonObject->infoField3)) { 50 | $newObject->setInfoField3($jsonObject->infoField3); 51 | } 52 | }; 53 | 54 | $object = parent::createFromJson($jsonString, $callback); 55 | 56 | return $object; 57 | } 58 | 59 | public function getInfoField1() 60 | { 61 | return $this->infoField1; 62 | } 63 | 64 | public function setInfoField1($infoField1) 65 | { 66 | $this->infoField1 = $infoField1; 67 | return $this; 68 | } 69 | 70 | public function getInfoField2() 71 | { 72 | return $this->infoField2; 73 | } 74 | 75 | public function setInfoField2($infoField2) 76 | { 77 | $this->infoField2 = $infoField2; 78 | return $this; 79 | } 80 | 81 | public function getInfoField3() 82 | { 83 | return $this->infoField3; 84 | } 85 | 86 | public function setInfoField3($infoField3) 87 | { 88 | $this->infoField3 = $infoField3; 89 | return $this; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/GetUserIdResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 44 | return $this; 45 | } 46 | 47 | public function getUserId() 48 | { 49 | return $this->userId; 50 | } 51 | 52 | public function setUserId($userId) 53 | { 54 | $this->userId = $userId; 55 | return $this; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/InstantAccessRequest.php: -------------------------------------------------------------------------------- 1 | setOperation($jsonObject->operation); 55 | // and the specific fields 56 | if ($callback && is_callable($callback)) { 57 | $callback($newObject, $jsonObject); 58 | } 59 | } catch (\Exception $e) { 60 | throw new \InvalidArgumentException( 61 | sprintf('Unable to deserialized object: %s. %s', $type, $e->getMessage()) 62 | ); 63 | } 64 | 65 | return $newObject; 66 | } 67 | 68 | public function getOperation() 69 | { 70 | return $this->operation; 71 | } 72 | 73 | /** 74 | * Set the request operation 75 | * 76 | * @param string a string representation of the operation 77 | * 78 | * @see Amazon\InstantAccess\Serialization\Enums\InstantAccessOperationValue For operation values 79 | */ 80 | public function setOperation($operation) 81 | { 82 | if (!InstantAccessOperationValue::isValid($operation)) { 83 | throw new \InvalidArgumentException(sprintf('Invalid operation value: %s', $reason)); 84 | } 85 | 86 | $this->operation = $operation; 87 | return $this; 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/InstantAccessResponse.php: -------------------------------------------------------------------------------- 1 | response; 40 | } 41 | 42 | public function setResponse($response) 43 | { 44 | $this->response = $response; 45 | return $this; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/RevokePurchaseRequest.php: -------------------------------------------------------------------------------- 1 | setPurchaseToken($jsonObject->purchaseToken); 48 | $newObject->setUserId($jsonObject->userId); 49 | $newObject->setProductId($jsonObject->productId); 50 | $newObject->setReason($jsonObject->reason); 51 | }; 52 | 53 | $object = parent::createFromJson($jsonString, $callback); 54 | 55 | return $object; 56 | } 57 | 58 | public function getPurchaseToken() 59 | { 60 | return $this->purchaseToken; 61 | } 62 | 63 | public function setPurchaseToken($purchaseToken) 64 | { 65 | $this->purchaseToken = $purchaseToken; 66 | return $this; 67 | } 68 | 69 | public function getUserId() 70 | { 71 | return $this->userId; 72 | } 73 | 74 | public function setUserId($userId) 75 | { 76 | $this->userId = $userId; 77 | return $this; 78 | } 79 | 80 | public function getProductId() 81 | { 82 | return $this->productId; 83 | } 84 | 85 | public function setProductId($productId) 86 | { 87 | $this->productId = $productId; 88 | return $this; 89 | } 90 | 91 | public function getReason() 92 | { 93 | return $this->reason; 94 | } 95 | 96 | /** 97 | * Set the request reason 98 | * 99 | * @param string a string representation of the reason 100 | * 101 | * @see Amazon\InstantAccess\Serialization\Enums\RevokePurchaseReasonValue For reason values 102 | */ 103 | public function setReason($reason) 104 | { 105 | if (!RevokePurchaseReasonValue::isValid($reason)) { 106 | throw new \InvalidArgumentException(sprintf('Invalid reason value: %s', $reason)); 107 | } 108 | 109 | $this->reason = $reason; 110 | return $this; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/RevokePurchaseResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/SubscriptionActivateRequest.php: -------------------------------------------------------------------------------- 1 | setSubscriptionId($jsonObject->subscriptionId); 46 | $newObject->setProductId($jsonObject->productId); 47 | $newObject->setUserId($jsonObject->userId); 48 | if (isset($jsonObject->subscriptionGroupId)) { 49 | $newObject->setSubscriptionGroupId($jsonObject->subscriptionGroupId); 50 | } 51 | if (isset($jsonObject->numberOfSubscriptionsInGroup)) { 52 | $newObject->setNumberOfSubscriptionsInGroup($jsonObject->numberOfSubscriptionsInGroup); 53 | } 54 | }; 55 | 56 | $object = parent::createFromJson($jsonString, $callback); 57 | 58 | return $object; 59 | } 60 | 61 | public function getSubscriptionId() 62 | { 63 | return $this->subscriptionId; 64 | } 65 | 66 | public function setSubscriptionId($subscriptionId) 67 | { 68 | $this->subscriptionId = $subscriptionId; 69 | return $this; 70 | } 71 | 72 | public function getProductId() 73 | { 74 | return $this->productId; 75 | } 76 | 77 | public function setProductId($productId) 78 | { 79 | $this->productId = $productId; 80 | return $this; 81 | } 82 | 83 | public function getUserId() 84 | { 85 | return $this->userId; 86 | } 87 | 88 | public function setUserId($userId) 89 | { 90 | $this->userId = $userId; 91 | return $this; 92 | } 93 | 94 | public function getSubscriptionGroupId() 95 | { 96 | return $this->subscriptionGroupId; 97 | } 98 | 99 | public function setSubscriptionGroupId($subscriptionGroupId) 100 | { 101 | $this->subscriptionGroupId = $subscriptionGroupId; 102 | return $this; 103 | } 104 | 105 | public function getNumberOfSubscriptionsInGroup() 106 | { 107 | return $this->numberOfSubscriptionsInGroup; 108 | } 109 | 110 | public function setNumberOfSubscriptionsInGroup($numberOfSubscriptionsInGroup) 111 | { 112 | $this->numberOfSubscriptionsInGroup = $numberOfSubscriptionsInGroup; 113 | return $this; 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/SubscriptionActivateResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/SubscriptionDeactivateRequest.php: -------------------------------------------------------------------------------- 1 | setSubscriptionId($jsonObject->subscriptionId); 48 | $newObject->setReason($jsonObject->reason); 49 | $newObject->setPeriod($jsonObject->period); 50 | }; 51 | 52 | $object = parent::createFromJson($jsonString, $callback); 53 | 54 | return $object; 55 | } 56 | 57 | public function getSubscriptionId() 58 | { 59 | return $this->subscriptionId; 60 | } 61 | 62 | public function setSubscriptionId($subscriptionId) 63 | { 64 | $this->subscriptionId = $subscriptionId; 65 | return $this; 66 | } 67 | 68 | public function getReason() 69 | { 70 | return $this->reason; 71 | } 72 | 73 | /** 74 | * Set the request reason 75 | * 76 | * @param string a string representation of the reason 77 | * 78 | * @see Amazon\InstantAccess\Serialization\Enums\SubscriptionDeactivateReasonValue For reason values 79 | */ 80 | public function setReason($reason) 81 | { 82 | if (!SubscriptionDeactivateReasonValue::isValid($reason)) { 83 | throw new \InvalidArgumentException(sprintf('Invalid reason value: %s', $reason)); 84 | } 85 | 86 | $this->reason = $reason; 87 | return $this; 88 | } 89 | 90 | public function getPeriod() 91 | { 92 | return $this->period; 93 | } 94 | 95 | /** 96 | * Set the request period 97 | * 98 | * @param string a string representation of the period 99 | * 100 | * @see Amazon\InstantAccess\Serialization\Enums\SubscriptionDeactivatePeriodValue For period values 101 | */ 102 | public function setPeriod($period) 103 | { 104 | if (!SubscriptionDeactivatePeriodValue::isValid($period)) { 105 | throw new \InvalidArgumentException(sprintf('Invalid period value: %s', $reason)); 106 | } 107 | 108 | $this->period = $period; 109 | return $this; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Serialization/SubscriptionDeactivateResponse.php: -------------------------------------------------------------------------------- 1 | response = $response; 41 | return $this; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Signature/AuthorizationHeader.php: -------------------------------------------------------------------------------- 1 | 24 | * Authorization: ALGORITHM Credential=CREDENTIAL, SignedHeaders=SIGNED_HEADERS, Signature=SIGNATURE 25 | * 26 | * 27 | * Where: 28 | * ALGORITHM := The signing algorithm used for the credential, ex. DTAv1-SHA-256 29 | * CREDENTIAL := KEYID/DATE. 30 | * SIGNED_HEADERS := lower cased header names sorted by byte order joined with semicolons. 31 | * SIGNATURE := The signature calculated by the signing algorithm. 32 | * KEYID := The public id for the sceret key used to calculate the signature. 33 | * DATE := The date the message was signed in YYMMDD format. This is used to generate the daily key. 34 | */ 35 | class AuthorizationHeader 36 | { 37 | /** @var string */ 38 | private $algorithm; 39 | /** @var string */ 40 | private $credential; 41 | /** @var string */ 42 | private $signedHeaders; 43 | /** @var string */ 44 | private $signature; 45 | 46 | const AUTHORIZATION_HEADER_PATTERN = '/(\S+) SignedHeaders=(\S+), Credential=(\S+), Signature=([\S]+)$/'; 47 | 48 | /** 49 | * Parses header value string an returns a new object. 50 | * 51 | * @param string $headerString the string representation of the Authentication header 52 | * 53 | * @return AuthorizationHeader a new {@link AuthorizationHeader} object if header is valid 54 | * 55 | * @throws InvalidArgumentException if unable to parse header 56 | */ 57 | public static function parse($headerString) 58 | { 59 | if (empty($headerString)) { 60 | throw new \InvalidArgumentException('Invalid authorization header.'); 61 | } 62 | 63 | preg_match(self::AUTHORIZATION_HEADER_PATTERN, $headerString, $matches); 64 | 65 | if (!$matches) { 66 | throw new \InvalidArgumentException('Unable to parse authorization header.'); 67 | } 68 | 69 | // split headers by ';' and convert them to lower case 70 | $signedHeaders = array_map('strtolower', explode(';', $matches[2])); 71 | 72 | // the credential should follow this pattern: PUBLIC_KEY_ID/DATE 73 | $credential = explode('/', $matches[3]); 74 | 75 | if (count($credential) < 2) { 76 | throw new \InvalidArgumentException('Invalid credential format.'); 77 | } 78 | 79 | $credential = array('key' => $credential[0], 80 | 'date' => $credential[1]); 81 | 82 | // the algorithm used to generate the signature 83 | $algorithm = $matches[1]; 84 | 85 | // the signature of the request 86 | $signature = $matches[4]; 87 | 88 | $header = new AuthorizationHeader( 89 | $algorithm, 90 | $signedHeaders, 91 | $credential, 92 | $signature 93 | ); 94 | 95 | return $header; 96 | } 97 | 98 | public function __construct($algorithm, $signedHeaders, $credential, $signature) 99 | { 100 | if (empty($algorithm)) { 101 | throw new \InvalidArgumentException('Empty algorithm information.'); 102 | } 103 | 104 | if (!is_array($signedHeaders)) { 105 | throw new \InvalidArgumentException('Invalid signed headers array.'); 106 | } 107 | 108 | if (!is_array($credential) || !array_key_exists('key', $credential) || !array_key_exists('date', $credential)) { 109 | throw new \InvalidArgumentException('Invalid credential information.'); 110 | } 111 | 112 | if (empty($signature)) { 113 | throw new \InvalidArgumentException('Empty signature information.'); 114 | } 115 | 116 | $this->algorithm = $algorithm; 117 | $this->signedHeaders = $signedHeaders; 118 | $this->credential = $credential; 119 | $this->signature = $signature; 120 | } 121 | 122 | public function getAlgorithm() 123 | { 124 | return $this->algorithm; 125 | } 126 | 127 | public function getCredential() 128 | { 129 | return $this->credential; 130 | } 131 | 132 | public function getSignedHeaders() 133 | { 134 | return $this->signedHeaders; 135 | } 136 | 137 | public function getSignature() 138 | { 139 | return $this->signature; 140 | } 141 | 142 | public function __tostring() 143 | { 144 | $str = $this->algorithm . ' '; 145 | $str .= 'SignedHeaders=' . implode(';', $this->signedHeaders) . ', '; 146 | $str .= 'Credential=' . $this->credential['key'] . '/'. $this->credential['date'] .', '; 147 | $str .= 'Signature=' . $this->signature; 148 | 149 | return $str; 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Signature/Credential.php: -------------------------------------------------------------------------------- 1 | secretKey = $secretKey; 35 | $this->publicKey = $publicKey; 36 | } 37 | 38 | /** 39 | * Gets the secret key. 40 | * 41 | * @return string the secret key 42 | */ 43 | public function getSecretKey() 44 | { 45 | return $this->secretKey; 46 | } 47 | 48 | /** 49 | * Gets the public key. 50 | * 51 | * @return string the public key 52 | */ 53 | public function getPublicKey() 54 | { 55 | return $this->publicKey; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Signature/CredentialStore.php: -------------------------------------------------------------------------------- 1 | store)) { 46 | return null; 47 | } 48 | 49 | return $this->store[$publicKey]; 50 | } 51 | 52 | /** 53 | * Gets the credentials stored in this store. 54 | * 55 | * @return array an array with all the credentials 56 | */ 57 | public function getAll() 58 | { 59 | return $this->store; 60 | } 61 | 62 | /** 63 | * Adds the new credential to the store. If the store already contains the public key, the credential is replaced. 64 | * 65 | * @param Credential credential the credential object to be added 66 | */ 67 | public function add(Credential $credential) 68 | { 69 | $this->store[$credential->getPublicKey()] = $credential; 70 | } 71 | 72 | /** 73 | * Removes the credential from the store. 74 | * 75 | * @param string $publicKey the public key of the credential to be removed 76 | */ 77 | public function remove($publicKey) 78 | { 79 | unset($this->store[$publicKey]); 80 | } 81 | 82 | /** 83 | * Loads keys from a file and populates the store. 84 | * 85 | * Each line of the file must contain a secret key and a public key separated by an empty space. 86 | * 87 | * @param string $filePath the path of the file that contains the keys 88 | * 89 | * @throws InvalidArgumentException if the file does not exist 90 | */ 91 | public function loadFromFile($filePath) 92 | { 93 | if (empty($filePath) || !file_exists($filePath)) { 94 | $message = 'Invalid keys file path'; 95 | throw new \InvalidArgumentException($message); 96 | } 97 | 98 | $contents = file_get_contents($filePath); 99 | 100 | $this->load($contents); 101 | } 102 | 103 | /** 104 | * Loads keys from a string and populates the store. 105 | * 106 | * Each line of the file must contain a secret key and a public key separated by an empty space. 107 | * 108 | * @param string $contents the string object that contains the keys 109 | * 110 | * @throws InvalidArgumentException if the content is empty or malformed 111 | */ 112 | public function load($contents) 113 | { 114 | if (empty($contents)) { 115 | $message = 'Empty key container'; 116 | throw new \InvalidArgumentException($message); 117 | } 118 | 119 | $lines = explode(PHP_EOL, $contents); 120 | 121 | foreach ($lines as $i => $line) { 122 | // Ignore blank lines in between credentials 123 | if (!$line) { 124 | continue; 125 | } 126 | 127 | // credentials should be separate by an empty space 128 | $keys = preg_split('/\s+/', $line); 129 | 130 | // Invalid format 131 | if (count($keys) < 2) { 132 | $message = 'Invalid credentials format found on line ' . $i; 133 | throw new \InvalidArgumentException($message); 134 | } 135 | 136 | $secretKey = $keys[0]; 137 | $publicKey = $keys[1]; 138 | 139 | $this->store[$publicKey] = new Credential($secretKey, $publicKey); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Signature/Request.php: -------------------------------------------------------------------------------- 1 | url = HttpUtils::parseFullURL($server); 49 | $this->method = $server['REQUEST_METHOD']; 50 | $this->headers = HttpUtils::parseRequestHeaders($server); 51 | $this->body = $requestBody; 52 | } 53 | 54 | public function getUrl() 55 | { 56 | return $this->url; 57 | } 58 | 59 | public function getMethod() 60 | { 61 | return $this->method; 62 | } 63 | 64 | public function getBody() 65 | { 66 | return $this->body; 67 | } 68 | 69 | public function &getHeaders() 70 | { 71 | return $this->headers; 72 | } 73 | 74 | /** 75 | * Remove headers that are present in $filter 76 | * 77 | * @param array $filter array of headers to be removed from this request 78 | */ 79 | public function filterHeaders(array $filter) 80 | { 81 | $this->headers = array_diff_key($this->headers, array_flip($filter)); 82 | } 83 | 84 | /** 85 | * @return string a human-friendly string representation of the Request 86 | */ 87 | public function __tostring() 88 | { 89 | $headersStr = implode( 90 | ', ', 91 | array_map( 92 | function ($v, $k) { 93 | return sprintf("%s:'%s'", $k, $v); 94 | }, 95 | $this->getHeaders(), 96 | array_keys($this->getHeaders()) 97 | ) 98 | ); 99 | 100 | $bodyStr = trim(preg_replace('/\s+/', ' ', $this->getBody())); 101 | 102 | return sprintf( 103 | 'Method: %s, Url: %s, Headers: %s, Body: %s', 104 | $this->getMethod(), 105 | $this->getMethod(), 106 | $headersStr, 107 | $bodyStr 108 | ); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Signature/Signer.php: -------------------------------------------------------------------------------- 1 | format(DateUtils::DATE_FORMAT_SHORT); 53 | $isoDate = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 54 | 55 | $headers = &$request->getHeaders(); 56 | 57 | $headers[HttpUtils::X_AMZ_DATE_HEADER] = $isoDate; 58 | 59 | // Remove the Authorization header from the request, since it could have been set if sign() was previously 60 | // called on this request. 61 | unset($headers[HttpUtils::AUTHORIZATION_HEADER]); 62 | 63 | $authorizationHeader = $this->getAuthorizationHeader($request, $credential, $shortDate, $isoDate); 64 | 65 | Logger::getLogger()->debug(sprintf('Signing request with header: %s', $authorizationHeader)); 66 | 67 | $headers[HttpUtils::AUTHORIZATION_HEADER] = $authorizationHeader; 68 | } 69 | 70 | /** 71 | * Verifies the request signature against a credential store. 72 | * 73 | * @param Request $request the request to verify. 74 | * @param CredentialStore $credentialStore the credential store used to verify the request. 75 | * 76 | * @return boolean returns true if the request validates. 77 | */ 78 | public function verify(Request $request, CredentialStore $credentialStore) 79 | { 80 | if (count($credentialStore->getAll()) == 0) { 81 | throw new \InvalidArgumentException('Empty credential store.'); 82 | } 83 | 84 | $dateNow = new \DateTime('@' . time()); 85 | 86 | $shortDate = $dateNow->format(DateUtils::DATE_FORMAT_SHORT); 87 | 88 | $headers = $request->getHeaders(); 89 | 90 | if (!isset($headers[HttpUtils::X_AMZ_DATE_HEADER])) { 91 | Logger::getLogger()->warning( 92 | sprintf('Header %s not found, aborting request verification.', HttpUtils::X_AMZ_DATE_HEADER) 93 | ); 94 | return false; 95 | } 96 | $requestIsoDate = $headers[HttpUtils::X_AMZ_DATE_HEADER]; 97 | 98 | if (!isset($headers[HttpUtils::AUTHORIZATION_HEADER])) { 99 | Logger::getLogger()->warning( 100 | sprintf('Header %s not found, aborting request verification.', HttpUtils::AUTHORIZATION_HEADER) 101 | ); 102 | return false; 103 | } 104 | $actualAuthorization = $headers[HttpUtils::AUTHORIZATION_HEADER]; 105 | 106 | $authorizationHeader = null; 107 | try { 108 | $authorizationHeader = AuthorizationHeader::parse($actualAuthorization); 109 | } catch (\InvalidArgumentException $e) { 110 | Logger::getLogger()->warning( 111 | sprintf('Unable to parse Authorization header, aborting request verification.') 112 | ); 113 | return false; 114 | } 115 | 116 | $signedHeaders = $authorizationHeader->getSignedHeaders(); 117 | 118 | // remove unsigned headers 119 | $unsignedHeaders = array_diff_key(($request->getHeaders()), array_flip($signedHeaders)); 120 | $request->filterHeaders(array_keys($unsignedHeaders)); 121 | 122 | $dateOfRequest = \DateTime::createFromFormat(DateUtils::DATE_FORMAT_ISO8601, $requestIsoDate); 123 | $delta = $dateOfRequest->getTimestamp() - $dateNow->getTimestamp(); 124 | if (abs($delta) > self::TIME_TOLERANCE_IN_MS) { 125 | Logger::getLogger()->warning(sprintf('Time tolerance exceeded, aborting request verification.')); 126 | return false; 127 | } 128 | 129 | $credentialInfo = $authorizationHeader->getCredential(); 130 | 131 | $credential = $credentialStore->get($credentialInfo['key']); 132 | if (!$credential) { 133 | Logger::getLogger()->warning( 134 | sprintf('Public key not found: %s, aborting request verification.', $credentialInfo['key']) 135 | ); 136 | return false; 137 | } 138 | 139 | if ($dateOfRequest->format(DateUtils::DATE_FORMAT_SHORT) != $credentialInfo['date']) { 140 | Logger::getLogger()->warning( 141 | sprintf('Request date and credential date don`t match aborting request verification.') 142 | ); 143 | return false; 144 | } 145 | 146 | $authorizationHeader = $this->getAuthorizationHeader($request, $credential, $shortDate, $requestIsoDate); 147 | 148 | Logger::getLogger()->debug( 149 | sprintf( 150 | 'Verifying request with header: %s, against expected header: %s', 151 | $actualAuthorization, 152 | $authorizationHeader 153 | ) 154 | ); 155 | 156 | if ($authorizationHeader == $actualAuthorization) { 157 | return true; 158 | } else { 159 | Logger::getLogger()->warning(sprintf('Authorization signature doesn`t match, verification failed.')); 160 | return false; 161 | } 162 | } 163 | 164 | /** 165 | * Returns the autorization header based on the parameters 166 | * 167 | * @param Request $request the request to generate the signature from 168 | * @param Credential $credential the credential to use when signing 169 | * @param string $shortDate the date to use to sign in short format 170 | * @param string $isoDate the date to use to sign in iso format 171 | * 172 | * @return string a string representation of the authorization header 173 | */ 174 | public function getAuthorizationHeader(Request $request, Credential $credential, $shortDate, $isoDate) 175 | { 176 | if (!$shortDate || !$isoDate) { 177 | throw new \InvalidArgumentException('Invalid dates.'); 178 | } 179 | 180 | $timedKey = hash_hmac('sha256', $shortDate, $credential->getSecretKey(), true); 181 | 182 | $canonicalRequest = $this->getCanonicalRequest($request); 183 | 184 | // We don't use scope in this algorithm 185 | $scope = ''; 186 | 187 | $stringToSign = self::ALGORITHM_ID . "\n" . $isoDate . "\n" . $scope . "\n" . hash('sha256', $canonicalRequest); 188 | 189 | Logger::getLogger()->debug(sprintf('String to sign: %s', $stringToSign)); 190 | 191 | $signature = hash_hmac('sha256', $stringToSign, $timedKey); 192 | 193 | $headers = $request->getHeaders(); 194 | ksort($headers); 195 | 196 | $authorizationHeader = new AuthorizationHeader( 197 | self::ALGORITHM_ID, 198 | array_keys($headers), 199 | array('key' => $credential->getPublicKey(), 'date' => $shortDate), 200 | $signature 201 | ); 202 | 203 | return $authorizationHeader->__tostring(); 204 | } 205 | 206 | /** 207 | * Returns the canonical representation of the request. The canonical request is of the form: 208 | * 209 | *
210 |      * METHOD
211 |      * CANONICAL_PATH
212 |      * CANONICAL_QUERY_STRING
213 |      * CANONICAL_HEADER_STRING
214 |      * SIGNED_HEADERS
215 |      * CONTENT_HASH
216 |      * 
217 | * 218 | * Which for a get request to http://amazon.com/ would be: 219 | * 220 | *
221 |      * GET
222 |      * /
223 |      *
224 |      * x-amz-date:20110909T233600Z
225 |      * 230d8358dc8e8890b4c58deeb62912ee2f20357ae92a5cc861b98e68fe31acb5
226 |      * 
227 | * 228 | * @param Request $request the request to canonicalize. 229 | * @return string the canonical request. 230 | */ 231 | private function getCanonicalRequest(Request $request) 232 | { 233 | // Method 234 | $canonicalRequest = $request->getMethod() . "\n"; 235 | 236 | // Path 237 | $url = parse_url($request->getUrl()); 238 | $canonicalRequest .= HttpUtils::normalizePath($url['path']) . "\n"; 239 | 240 | // Query string 241 | // TODO: the Java SDK does not add the query string to the canonical form 242 | $canonicalRequest .= "\n"; 243 | 244 | // Headers 245 | $headers = array(); 246 | foreach ($request->getHeaders() as $key => $value) { 247 | $headers[$key] = preg_replace('/\s+/', ' ', trim($value)); 248 | } 249 | 250 | ksort($headers); 251 | 252 | foreach ($headers as $key => $value) { 253 | $canonicalRequest .= $key . ':' . $value . "\n"; 254 | } 255 | 256 | // TODO: the Java SDK adds a empty line after the headers 257 | $canonicalRequest .= "\n"; 258 | 259 | // Signed headers 260 | $headers = $request->getHeaders(); 261 | ksort($headers); 262 | 263 | $canonicalRequest .= implode(';', array_keys($headers)) . "\n"; 264 | 265 | // Content hash 266 | $canonicalRequest .= hash('sha256', $request->getBody()); 267 | 268 | Logger::getLogger()->debug(sprintf('Canonical request: %s', $canonicalRequest)); 269 | 270 | return $canonicalRequest; 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Utils/DateUtils.php: -------------------------------------------------------------------------------- 1 | $value) { 98 | if (substr($key, 0, 5) == 'HTTP_') { 99 | $key = substr($key, 5); 100 | } elseif ($key != 'CONTENT_TYPE' && $key != 'CONTENT_LENGTH') { 101 | continue; 102 | } 103 | 104 | $header = str_replace('_', '-', strtolower($key)); 105 | $headers[$header] = $value; 106 | } 107 | 108 | return $headers; 109 | } 110 | 111 | /** 112 | * Normalize the URL according to RFC 3986. 113 | * 114 | * This function replaces multiple slashses with a single one and eliminates '.' (current directory) and 115 | * '..' (parent directory). 116 | * 117 | * @param string a path 118 | * 119 | * @return string a normalized path 120 | */ 121 | public static function normalizePath($path) 122 | { 123 | if (!$path) { 124 | return '/'; 125 | } 126 | 127 | $parts = explode('/', $path); 128 | 129 | $normalizedParts = array(); 130 | foreach ($parts as $part) { 131 | if ($part == '..') { 132 | array_pop($normalizedParts); 133 | } elseif ($part != '.' && $part != '') { 134 | $normalizedParts[] = $part; 135 | } 136 | } 137 | 138 | $newPath = ''; 139 | 140 | if ($path[0] == '/') { 141 | $newPath .= '/'; 142 | } 143 | 144 | $newPath .= implode('/', $normalizedParts); 145 | 146 | if ($path != '/' && end($parts) == '') { 147 | $newPath .= '/'; 148 | } 149 | 150 | return $newPath; 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/Amazon/InstantAccess/Utils/IOUtils.php: -------------------------------------------------------------------------------- 1 | pushHandler(new Monolog\Handler\StreamHandler('php://stdout')); 16 | Logger::setLogger($monolog); 17 | 18 | $server = array(); 19 | //$server['HTTP_USER_AGENT'] = 'Mozilla/5.0 (Windows NT 6.2; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/32.0.1667.0 Safari/537.36'; 20 | $server['HTTP_HOST'] = 'localhost'; 21 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 22 | $server['SERVER_PORT'] = '8000'; 23 | $server['REQUEST_URI'] = '/service/linking'; 24 | $server['REQUEST_METHOD'] = 'POST'; 25 | $server['CONTENT_TYPE'] = 'application/json'; 26 | 27 | $content = '{ 28 | "operation": "GetUserId", 29 | "infoField1": "nobody@amazon.com", 30 | "infoField2": "nobody" 31 | }'; 32 | 33 | $content = trim(preg_replace('/\s+/', ' ', $content)); 34 | 35 | $request = new Request($server, $content); 36 | 37 | $credential = new Credential('e2c4905c-83ba-41e7-9c1b-af8014a334cb', '367caa91-cde5-48f2-91fe-bb95f546e9f0'); 38 | 39 | $signer = new Signer(); 40 | 41 | $signer->sign($request, $credential); 42 | 43 | //echo $request; 44 | 45 | $cmd = 'curl -v'; 46 | $cmd .= ' --data \'' . $request->getBody() . '\''; 47 | foreach ($request->getHeaders() as $key => $value) { 48 | $cmd .= ' -H "' . $key . ':' . $value . '"'; 49 | } 50 | $cmd .= ' ' . $request->getUrl(); 51 | 52 | echo $cmd; 53 | 54 | system($cmd); 55 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Controllers/AccountLinkingControllerTest.php: -------------------------------------------------------------------------------- 1 | add($credential); 42 | 43 | self::$credential = $credential; 44 | self::$credentialStore = $credentialStore; 45 | } 46 | 47 | private function generateSignedRequest(&$server, $content) 48 | { 49 | $dateNow = new \DateTime('@' . time()); 50 | 51 | $shortDate = $dateNow->format(DateUtils::DATE_FORMAT_SHORT); 52 | $isoDate = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 53 | 54 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $isoDate; 55 | 56 | $request = new Request($server, $content); 57 | 58 | $signer = new Signer(); 59 | $signer->sign($request, self::$credential); 60 | 61 | $headers = $request->getHeaders(); 62 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = $headers[HttpUtils::AUTHORIZATION_HEADER]; 63 | 64 | return $request; 65 | } 66 | 67 | public function testGetUserId() 68 | { 69 | $server = array(); 70 | $server['HTTP_HOST'] = 'amazon.com'; 71 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 72 | $server['SERVER_PORT'] = '80'; 73 | $server['REQUEST_URI'] = '/'; 74 | $server['REQUEST_METHOD'] = 'POST'; 75 | $server['CONTENT_TYPE'] = 'application/json'; 76 | 77 | $content = '{ 78 | "operation": "GetUserId", 79 | "infoField1": "nobody@amazon.com", 80 | "infoField2": "amazon", 81 | "infoField3": "nobody" 82 | }'; 83 | 84 | $request = $this->generateSignedRequest($server, $content); 85 | 86 | $body = tmpfile(); 87 | fwrite($body, $content); 88 | fseek($body, 0); 89 | 90 | $controller = new AccountLinkingController(self::$credentialStore); 91 | 92 | $controller->onGetUserId(function ($req) { 93 | $res = new GetUserIdResponse(); 94 | $res->setResponse(GetUserIdResponseValue::OK); 95 | $res->setUserId('1234'); 96 | return $res; 97 | }); 98 | 99 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 100 | 101 | $this->assertNotNull($response); 102 | $this->assertEquals('{"userId":"1234","response":"OK"}', $response); 103 | } 104 | 105 | public function testGetUserIdNoCallback() 106 | { 107 | $server = array(); 108 | $server['HTTP_HOST'] = 'amazon.com'; 109 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 110 | $server['SERVER_PORT'] = '80'; 111 | $server['REQUEST_URI'] = '/'; 112 | $server['REQUEST_METHOD'] = 'POST'; 113 | $server['CONTENT_TYPE'] = 'application/json'; 114 | 115 | $content = '{ 116 | "operation": "GetUserId", 117 | "infoField1": "nobody@amazon.com", 118 | "infoField2": "amazon", 119 | "infoField3": "nobody" 120 | }'; 121 | 122 | $request = $this->generateSignedRequest($server, $content); 123 | 124 | $body = tmpfile(); 125 | fwrite($body, $content); 126 | fseek($body, 0); 127 | 128 | $controller = new AccountLinkingController(self::$credentialStore); 129 | 130 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 131 | 132 | $this->assertEmpty($response); 133 | 134 | // TODO : assert headers for status 500 135 | } 136 | 137 | public function testProcessOperationInvalidOperation() 138 | { 139 | $server = array(); 140 | $server['HTTP_HOST'] = 'amazon.com'; 141 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 142 | $server['SERVER_PORT'] = '80'; 143 | $server['REQUEST_URI'] = '/'; 144 | $server['REQUEST_METHOD'] = 'POST'; 145 | $server['CONTENT_TYPE'] = 'application/json'; 146 | 147 | $content = '{ 148 | "operation": "Purchase", 149 | "reason": "FULFILL", 150 | "productId": "GamePack1", 151 | "userId": "123456", 152 | "purchaseToken": "6f3092e5-0326-42b7-a107-416234d548d8" 153 | }'; 154 | 155 | $request = $this->generateSignedRequest($server, $content); 156 | 157 | $body = tmpfile(); 158 | fwrite($body, $content); 159 | fseek($body, 0); 160 | 161 | $controller = new AccountLinkingController(self::$credentialStore); 162 | 163 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 164 | 165 | $this->assertEmpty($response); 166 | 167 | // TODO : assert headers for status 500 168 | } 169 | 170 | public function testGetUserIdInvalidSignature() 171 | { 172 | $server = array(); 173 | $server['HTTP_HOST'] = 'amazon.com'; 174 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 175 | $server['SERVER_PORT'] = '80'; 176 | $server['REQUEST_URI'] = '/'; 177 | $server['REQUEST_METHOD'] = 'POST'; 178 | $server['CONTENT_TYPE'] = 'application/json'; 179 | 180 | $content = '{ 181 | "operation": "GetUserId", 182 | "infoField1": "nobody@amazon.com", 183 | "infoField2": "amazon", 184 | "infoField3": "nobody" 185 | }'; 186 | 187 | $request = $this->generateSignedRequest($server, $content); 188 | 189 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] .= "foobar"; 190 | 191 | $body = tmpfile(); 192 | fwrite($body, $content); 193 | fseek($body, 0); 194 | 195 | $controller = new AccountLinkingController(self::$credentialStore); 196 | 197 | $controller->onGetUserId(function ($req) { 198 | $res = new GetUserIdResponse(); 199 | $res->setResponse(GetUserIdResponseValue::OK); 200 | $res->setUserId('1234'); 201 | return $res; 202 | }); 203 | 204 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 205 | 206 | $this->assertEmpty($response); 207 | 208 | // TODO : assert headers for status 500 209 | } 210 | 211 | public function testGetUserIdInvalidCallbackReturn() 212 | { 213 | $server = array(); 214 | $server['HTTP_HOST'] = 'amazon.com'; 215 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 216 | $server['SERVER_PORT'] = '80'; 217 | $server['REQUEST_URI'] = '/'; 218 | $server['REQUEST_METHOD'] = 'POST'; 219 | $server['CONTENT_TYPE'] = 'application/json'; 220 | 221 | $content = '{ 222 | "operation": "GetUserId", 223 | "infoField1": "nobody@amazon.com", 224 | "infoField2": "amazon", 225 | "infoField3": "nobody" 226 | }'; 227 | 228 | $request = $this->generateSignedRequest($server, $content); 229 | 230 | $body = tmpfile(); 231 | fwrite($body, $content); 232 | fseek($body, 0); 233 | 234 | $controller = new AccountLinkingController(self::$credentialStore); 235 | 236 | $controller->onGetUserId(function ($req) { 237 | return "respose"; 238 | }); 239 | 240 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 241 | 242 | $this->assertEmpty($response); 243 | 244 | // TODO : assert headers for status 500 245 | } 246 | } 247 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Controllers/PurchaseControllerTest.php: -------------------------------------------------------------------------------- 1 | add($credential); 47 | 48 | self::$credential = $credential; 49 | self::$credentialStore = $credentialStore; 50 | } 51 | 52 | private function generateSignedRequest(&$server, $content) 53 | { 54 | $dateNow = new \DateTime('@' . time()); 55 | 56 | $shortDate = $dateNow->format(DateUtils::DATE_FORMAT_SHORT); 57 | $isoDate = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 58 | 59 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $isoDate; 60 | 61 | $request = new Request($server, $content); 62 | 63 | $signer = new Signer(); 64 | $signer->sign($request, self::$credential); 65 | 66 | $headers = $request->getHeaders(); 67 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = $headers[HttpUtils::AUTHORIZATION_HEADER]; 68 | 69 | return $request; 70 | } 71 | 72 | public function testFulfillPurchase() 73 | { 74 | $server = array(); 75 | $server['HTTP_HOST'] = 'amazon.com'; 76 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 77 | $server['SERVER_PORT'] = '80'; 78 | $server['REQUEST_URI'] = '/'; 79 | $server['REQUEST_METHOD'] = 'POST'; 80 | $server['CONTENT_TYPE'] = 'application/json'; 81 | 82 | $content = '{ 83 | "operation": "Purchase", 84 | "reason": "FULFILL", 85 | "productId": "GamePack1", 86 | "userId": "123456", 87 | "purchaseToken": "6f3092e5-0326-42b7-a107-416234d548d8" 88 | }'; 89 | 90 | $request = $this->generateSignedRequest($server, $content); 91 | 92 | $body = tmpfile(); 93 | fwrite($body, $content); 94 | fseek($body, 0); 95 | 96 | $controller = new PurchaseController(self::$credentialStore); 97 | 98 | $controller->onFulfillPurchase(function ($req) { 99 | $res = new FulfillPurchaseResponse(); 100 | $res->setResponse(FulfillPurchaseResponseValue::FAIL_USER_NOT_ELIGIBLE); 101 | return $res; 102 | }); 103 | 104 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 105 | 106 | $this->assertNotNull($response); 107 | $this->assertEquals('{"response":"FAIL_USER_NOT_ELIGIBLE"}', $response); 108 | } 109 | 110 | public function testRevokePurchase() 111 | { 112 | $server = array(); 113 | $server['HTTP_HOST'] = 'amazon.com'; 114 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 115 | $server['SERVER_PORT'] = '80'; 116 | $server['REQUEST_URI'] = '/'; 117 | $server['REQUEST_METHOD'] = 'POST'; 118 | $server['CONTENT_TYPE'] = 'application/json'; 119 | 120 | $content = '{ 121 | "operation": "Revoke", 122 | "reason": "CUSTOMER_SERVICE_REQUEST", 123 | "productId": "GamePack1", 124 | "userId": "123456", 125 | "purchaseToken": "6f3092e5-0326-42b7-a107-416234d548d8" 126 | }'; 127 | 128 | $request = $this->generateSignedRequest($server, $content); 129 | 130 | $body = tmpfile(); 131 | fwrite($body, $content); 132 | fseek($body, 0); 133 | 134 | $controller = new PurchaseController(self::$credentialStore); 135 | 136 | $controller->onRevokePurchase(function ($req) { 137 | $res = new RevokePurchaseResponse(); 138 | $res->setResponse(RevokePurchaseResponseValue::FAIL_INVALID_PURCHASE_TOKEN); 139 | return $res; 140 | }); 141 | 142 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 143 | 144 | $this->assertNotNull($response); 145 | $this->assertEquals('{"response":"FAIL_INVALID_PURCHASE_TOKEN"}', $response); 146 | } 147 | 148 | public function testSubscriptionActivate() 149 | { 150 | $server = array(); 151 | $server['HTTP_HOST'] = 'amazon.com'; 152 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 153 | $server['SERVER_PORT'] = '80'; 154 | $server['REQUEST_URI'] = '/'; 155 | $server['REQUEST_METHOD'] = 'POST'; 156 | $server['CONTENT_TYPE'] = 'application/json'; 157 | 158 | $content = '{ 159 | "operation": "SubscriptionActivate", 160 | "subscriptionId": "subscriptionId", 161 | "productId": "GamePack1", 162 | "userId": "1234" 163 | }'; 164 | 165 | $request = $this->generateSignedRequest($server, $content); 166 | 167 | $body = tmpfile(); 168 | fwrite($body, $content); 169 | fseek($body, 0); 170 | 171 | $controller = new PurchaseController(self::$credentialStore); 172 | 173 | $controller->onSubscriptionActivate(function ($req) { 174 | $res = new SubscriptionActivateResponse(); 175 | $res->setResponse(SubscriptionActivateResponseValue::FAIL_USER_INVALID); 176 | return $res; 177 | }); 178 | 179 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 180 | 181 | $this->assertNotNull($response); 182 | $this->assertEquals('{"response":"FAIL_USER_INVALID"}', $response); 183 | } 184 | 185 | public function testSubscriptionActivateTeamSubsV1() 186 | { 187 | $server = array(); 188 | $server['HTTP_HOST'] = 'amazon.com'; 189 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 190 | $server['SERVER_PORT'] = '80'; 191 | $server['REQUEST_URI'] = '/'; 192 | $server['REQUEST_METHOD'] = 'POST'; 193 | $server['CONTENT_TYPE'] = 'application/json'; 194 | 195 | $content = '{ 196 | "operation": "SubscriptionActivate", 197 | "subscriptionId": "subscriptionId", 198 | "productId": "GamePack1", 199 | "userId": "1234", 200 | "subscriptionGroupId": "group1234", 201 | "numberOfSubscriptionsInGroup": "4" 202 | }'; 203 | 204 | $request = $this->generateSignedRequest($server, $content); 205 | 206 | $body = tmpfile(); 207 | fwrite($body, $content); 208 | fseek($body, 0); 209 | 210 | $controller = new PurchaseController(self::$credentialStore); 211 | 212 | $controller->onSubscriptionActivate(function ($req) { 213 | $res = new SubscriptionActivateResponse(); 214 | $res->setResponse(SubscriptionActivateResponseValue::FAIL_INVALID_SUBSCRIPTION); 215 | return $res; 216 | }); 217 | 218 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 219 | 220 | $this->assertNotNull($response); 221 | $this->assertEquals('{"response":"FAIL_INVALID_SUBSCRIPTION"}', $response); 222 | } 223 | 224 | public function testSubscriptionDeactivate() 225 | { 226 | $server = array(); 227 | $server['HTTP_HOST'] = 'amazon.com'; 228 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 229 | $server['SERVER_PORT'] = '80'; 230 | $server['REQUEST_URI'] = '/'; 231 | $server['REQUEST_METHOD'] = 'POST'; 232 | $server['CONTENT_TYPE'] = 'application/json'; 233 | 234 | $content = '{ 235 | "operation": "SubscriptionDeactivate", 236 | "subscriptionId": "subscriptionId", 237 | "reason": "PAYMENT_PROBLEM", 238 | "period": "REGULAR" 239 | }'; 240 | 241 | $request = $this->generateSignedRequest($server, $content); 242 | 243 | $body = tmpfile(); 244 | fwrite($body, $content); 245 | fseek($body, 0); 246 | 247 | $controller = new PurchaseController(self::$credentialStore); 248 | 249 | $controller->onSubscriptionDeactivate(function ($req) { 250 | $res = new SubscriptionDeactivateResponse(); 251 | $res->setResponse(SubscriptionDeactivateResponseValue::OK); 252 | return $res; 253 | }); 254 | 255 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 256 | 257 | $this->assertNotNull($response); 258 | $this->assertEquals('{"response":"OK"}', $response); 259 | } 260 | 261 | public function testProcessOperationInvalidOperation() 262 | { 263 | $server = array(); 264 | $server['HTTP_HOST'] = 'amazon.com'; 265 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 266 | $server['SERVER_PORT'] = '80'; 267 | $server['REQUEST_URI'] = '/'; 268 | $server['REQUEST_METHOD'] = 'POST'; 269 | $server['CONTENT_TYPE'] = 'application/json'; 270 | 271 | $content = '{ 272 | "operation": "GetUserId", 273 | "infoField1": "nobody@amazon.com", 274 | "infoField2": "amazon", 275 | "infoField3": "nobody" 276 | }'; 277 | 278 | $request = $this->generateSignedRequest($server, $content); 279 | 280 | $body = tmpfile(); 281 | fwrite($body, $content); 282 | fseek($body, 0); 283 | 284 | $controller = new PurchaseController(self::$credentialStore); 285 | 286 | $controller->onSubscriptionDeactivate(function ($req) { 287 | $res = new SubscriptionDeactivateResponse(); 288 | $res->setResponse(SubscriptionDeactivateResponseValue::OK); 289 | return $res; 290 | }); 291 | 292 | $response = $controller->process($server, IOUtils::getFilePathFromHandle($body)); 293 | $this->assertEmpty($response); 294 | 295 | // TODO : assert headers for status 500 296 | } 297 | } 298 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/Enums/InstantAccessOperationValueTest.php: -------------------------------------------------------------------------------- 1 | assertTrue(InstantAccessOperationValue::isValid(InstantAccessOperationValue::PURCHASE)); 23 | $this->assertTrue(InstantAccessOperationValue::isValid(InstantAccessOperationValue::REVOKE)); 24 | $this->assertTrue(InstantAccessOperationValue::isValid(InstantAccessOperationValue::GET_USER_ID)); 25 | $this->assertTrue(InstantAccessOperationValue::isValid(InstantAccessOperationValue::SUBSCRIPTION_ACTIVATE)); 26 | $this->assertTrue(InstantAccessOperationValue::isValid(InstantAccessOperationValue::SUBSCRIPTION_DEACTIVATE)); 27 | } 28 | 29 | public function testInvalidValues() 30 | { 31 | $this->assertFalse(InstantAccessOperationValue::isValid("test")); 32 | $this->assertFalse(InstantAccessOperationValue::isValid("")); 33 | $this->assertFalse(InstantAccessOperationValue::isValid(null)); 34 | $this->assertFalse(InstantAccessOperationValue::isValid(array())); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/FulfillPurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($fulfillRequest); 36 | $this->assertEquals(InstantAccessOperationValue::PURCHASE, $fulfillRequest->getOperation()); 37 | $this->assertEquals(FulfillPurchaseReasonValue::FULFILL, $fulfillRequest->getReason()); 38 | $this->assertEquals('GamePack1', $fulfillRequest->getProductId()); 39 | $this->assertEquals('123456', $fulfillRequest->getUserId()); 40 | $this->assertEquals('6f3092e5-0326-42b7-a107-416234d548d8', $fulfillRequest->getPurchaseToken()); 41 | } 42 | 43 | public function testCreateFromJsonInvalidReason() 44 | { 45 | $this->setExpectedException('InvalidArgumentException'); 46 | 47 | $json = '{ 48 | "operation": "Purchase", 49 | "reason": "PURCHASE", 50 | "productId": "GamePack1", 51 | "userId": "123456", 52 | "purchaseToken": "6f3092e5-0326-42b7-a107-416234d548d8" 53 | }'; 54 | 55 | $fulfillRequest = FulfillPurchaseRequest::createFromJson($json); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/FulfillPurchaseResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse(FulfillPurchaseResponseValue::OK); 26 | 27 | $json = $fulfillResponse->toJson(); 28 | 29 | $this->assertNotEmpty($fulfillResponse); 30 | $this->assertEquals('{"response":"OK"}', $json); 31 | } 32 | 33 | public function testToJsonInvalidResponse() 34 | { 35 | $this->setExpectedException('InvalidArgumentException'); 36 | 37 | $fulfillResponse = new FulfillPurchaseResponse(); 38 | $fulfillResponse->setResponse('foobar'); 39 | 40 | $json = $fulfillResponse->toJson(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/GetUserIdRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($getUserIdRequest); 34 | $this->assertEquals(InstantAccessOperationValue::GET_USER_ID, $getUserIdRequest->getOperation()); 35 | $this->assertEquals('nobody@amazon.com', $getUserIdRequest->getInfoField1()); 36 | $this->assertEquals('amazon', $getUserIdRequest->getInfoField2()); 37 | $this->assertEquals('nobody', $getUserIdRequest->getInfoField3()); 38 | } 39 | 40 | public function testCreateFromJsonWithOneInfoField() 41 | { 42 | $json = '{ 43 | "operation": "GetUserId", 44 | "infoField1": "nobody@amazon.com" 45 | }'; 46 | 47 | $getUserIdRequest = GetUserIdRequest::createFromJson($json); 48 | 49 | $this->assertNotNull($getUserIdRequest); 50 | $this->assertEquals(InstantAccessOperationValue::GET_USER_ID, $getUserIdRequest->getOperation()); 51 | $this->assertEquals('nobody@amazon.com', $getUserIdRequest->getInfoField1()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/GetUserIdResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse(GetUserIdResponseValue::OK); 26 | $getUserIdResponse->setUserId('1234'); 27 | 28 | $json = $getUserIdResponse->toJson(); 29 | 30 | $this->assertNotEmpty($getUserIdResponse); 31 | $this->assertEquals('{"userId":"1234","response":"OK"}', $json); 32 | } 33 | 34 | public function testToJsonInvalidResponse() 35 | { 36 | $this->setExpectedException('InvalidArgumentException'); 37 | 38 | $getUserIdResponse = new GetUserIdResponse(); 39 | $getUserIdResponse->setResponse('foobar'); 40 | 41 | $json = $getUserIdResponse->toJson(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/InstantAccessRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($iaRequest); 31 | $this->assertEquals(InstantAccessOperationValue::PURCHASE, $iaRequest->getOperation()); 32 | } 33 | 34 | public function testCreateFromJsonInvalidValue() 35 | { 36 | $this->setExpectedException('InvalidArgumentException'); 37 | 38 | $json = '{ 39 | "operation" : "FooBar" 40 | }'; 41 | 42 | $iaRequest = InstantAccessRequest::createFromJson($json); 43 | } 44 | 45 | public function testCreateFromJsonInvalidJson() 46 | { 47 | $this->setExpectedException('InvalidArgumentException'); 48 | 49 | $json = '{ 50 | "operation" : "Purchase" 51 | '; 52 | 53 | $iaRequest = InstantAccessRequest::createFromJson($json); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/InstantAccessResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse("OK"); 24 | 25 | $json = $iaResponse->toJson(); 26 | 27 | $this->assertNotEmpty($iaResponse); 28 | $this->assertEquals('{"response":"OK"}', $json); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/RevokePurchaseRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($revokeRequest); 36 | $this->assertEquals(InstantAccessOperationValue::REVOKE, $revokeRequest->getOperation()); 37 | $this->assertEquals(RevokePurchaseReasonValue::CUSTOMER_SERVICE_REQUEST, $revokeRequest->getReason()); 38 | $this->assertEquals('GamePack1', $revokeRequest->getProductId()); 39 | $this->assertEquals('123456', $revokeRequest->getUserId()); 40 | $this->assertEquals('6f3092e5-0326-42b7-a107-416234d548d8', $revokeRequest->getPurchaseToken()); 41 | } 42 | 43 | public function testCreateFromJsonInvalidReason() 44 | { 45 | $this->setExpectedException('InvalidArgumentException'); 46 | 47 | $json = '{ 48 | "operation": "Revoke", 49 | "reason": "USER_MISCLICK", 50 | "productId": "GamePack1", 51 | "userId": "123456", 52 | "purchaseToken": "6f3092e5-0326-42b7-a107-416234d548d8" 53 | }'; 54 | 55 | $revokeRequest = RevokePurchaseRequest::createFromJson($json); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/RevokePurchaseResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse(RevokePurchaseResponseValue::FAIL_INVALID_PURCHASE_TOKEN); 26 | 27 | $json = $revokeResponse->toJson(); 28 | 29 | $this->assertNotEmpty($revokeResponse); 30 | $this->assertEquals('{"response":"FAIL_INVALID_PURCHASE_TOKEN"}', $json); 31 | } 32 | 33 | public function testToJsonInvalidResponse() 34 | { 35 | $this->setExpectedException('InvalidArgumentException'); 36 | 37 | $revokeResponse = new RevokePurchaseResponse(); 38 | $revokeResponse->setResponse('foobar'); 39 | 40 | $json = $revokeResponse->toJson(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/SubscriptionActivateRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($subRequest); 34 | $this->assertEquals(InstantAccessOperationValue::SUBSCRIPTION_ACTIVATE, $subRequest->getOperation()); 35 | $this->assertEquals('subscriptionId', $subRequest->getSubscriptionId()); 36 | $this->assertEquals('GamePack1', $subRequest->getProductId()); 37 | $this->assertEquals('1234', $subRequest->getUserId()); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/SubscriptionActivateResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse(SubscriptionActivateResponseValue::FAIL_USER_INVALID); 26 | 27 | $json = $subResponse->toJson(); 28 | 29 | $this->assertNotEmpty($subResponse); 30 | $this->assertEquals('{"response":"FAIL_USER_INVALID"}', $json); 31 | } 32 | 33 | public function testToJsonInvalidResponse() 34 | { 35 | $this->setExpectedException('InvalidArgumentException'); 36 | 37 | $subResponse = new SubscriptionActivateResponse(); 38 | $subResponse->setResponse('foobar'); 39 | 40 | $json = $subResponse->toJson(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/SubscriptionDeactivateRequestTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($subRequest); 36 | $this->assertEquals(InstantAccessOperationValue::SUBSCRIPTION_DEACTIVATE, $subRequest->getOperation()); 37 | $this->assertEquals('subscriptionId', $subRequest->getSubscriptionId()); 38 | $this->assertEquals(SubscriptionDeactivateReasonValue::USER_REQUEST, $subRequest->getReason()); 39 | $this->assertEquals(SubscriptionDeactivatePeriodValue::REGULAR, $subRequest->getPeriod()); 40 | } 41 | 42 | public function testCreateFromJsonInvalidReason() 43 | { 44 | $this->setExpectedException('InvalidArgumentException'); 45 | 46 | $json = '{ 47 | "operation": "SubscriptionDeactivate", 48 | "subscriptionId": "subscriptionId", 49 | "reason": "UNKNOWN", 50 | "period": "REGULAR" 51 | }'; 52 | 53 | $subRequest = SubscriptionDeactivateRequest::createFromJson($json); 54 | } 55 | 56 | public function testCreateFromJsonInvalidPeriod() 57 | { 58 | $this->setExpectedException('InvalidArgumentException'); 59 | 60 | $json = '{ 61 | "operation": "SubscriptionDeactivate", 62 | "subscriptionId": "subscriptionId", 63 | "reason": "USER_REQUEST", 64 | "period": "NEGATIVE" 65 | }'; 66 | 67 | $subRequest = SubscriptionDeactivateRequest::createFromJson($json); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Serialization/SubscriptionDeactivateResponseTest.php: -------------------------------------------------------------------------------- 1 | setResponse(SubscriptionDeactivateResponseValue::OK); 26 | 27 | $json = $subResponse->toJson(); 28 | 29 | $this->assertNotEmpty($subResponse); 30 | $this->assertEquals('{"response":"OK"}', $json); 31 | } 32 | 33 | public function testToJsonInvalidResponse() 34 | { 35 | $this->setExpectedException('InvalidArgumentException'); 36 | 37 | $subResponse = new SubscriptionDeactivateResponse(); 38 | $subResponse->setResponse('foobar'); 39 | 40 | $json = $subResponse->toJson(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Signature/AuthorizationHeaderTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($header); 30 | $this->assertEquals('DTA1-HMAC-SHA256', $header->getAlgorithm()); 31 | $this->assertEquals(array('key' => 'KEYID', 'date' =>'20110909'), $header->getCredential()); 32 | $this->assertEquals(array('aaa', 'content-type', 'x-amz-date', 'zzz'), $header->getSignedHeaders()); 33 | $this->assertEquals('87729cb3475859a18b5d9cead0bba82f0f56a85c2a13bed3bc229c6c35e06628', $header->getSignature()); 34 | } 35 | 36 | public function testParseValidHeader2() 37 | { 38 | $value = 'DTA1-HMAC-SHA256 ' . 39 | 'SignedHeaders=Content-Type;X-Amz-Date;X-Amz-Dta-Version;X-AMZ-REQUEST-ID, ' . 40 | 'Credential=367caa91-cde5-48f2-91fe-bb95f546e9f0/20131207, ' . 41 | 'Signature=6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe'; 42 | 43 | $header = AuthorizationHeader::parse($value); 44 | 45 | $this->assertNotNull($header); 46 | $this->assertEquals('DTA1-HMAC-SHA256', $header->getAlgorithm()); 47 | $this->assertEquals(array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0', 'date' =>'20131207'), $header->getCredential()); 48 | $this->assertEquals(array('content-type', 'x-amz-date', 'x-amz-dta-version', 'x-amz-request-id'), $header->getSignedHeaders()); 49 | $this->assertEquals('6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe', $header->getSignature()); 50 | } 51 | 52 | public function testParseInvalidHeader() 53 | { 54 | $this->setExpectedException('\InvalidArgumentException'); 55 | 56 | $value = ''; 57 | 58 | $header = AuthorizationHeader::parse($value); 59 | } 60 | 61 | public function testParseInvalidHeaderFormat() 62 | { 63 | $this->setExpectedException('\InvalidArgumentException'); 64 | 65 | $value = 'SignedHeaders=content-type;x-amz-date;x-amz-dta-version;x-amz-request-id, ' . 66 | 'Credential=367caa91-cde5-48f2-91fe-bb95f546e9f0, ' . 67 | 'Signature=6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe'; 68 | 69 | $header = AuthorizationHeader::parse($value); 70 | } 71 | 72 | public function testParseInvalidHeaderinvalidCredential() 73 | { 74 | $this->setExpectedException('\InvalidArgumentException'); 75 | 76 | $value = 'DTA1-HMAC-SHA256 ' . 77 | 'SignedHeaders=content-type;x-amz-date;x-amz-dta-version;x-amz-request-id, ' . 78 | 'Credential=367caa91-cde5-48f2-91fe-bb95f546e9f0, ' . 79 | 'Signature=6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe'; 80 | 81 | $header = AuthorizationHeader::parse($value); 82 | } 83 | 84 | public function testToString() 85 | { 86 | $header = new AuthorizationHeader( 87 | 'DTA1-HMAC-SHA256', 88 | array('content-type', 'x-amz-date', 'x-amz-dta-version', 'x-amz-request-id'), 89 | array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0', 'date' => '20140101'), 90 | '6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe' 91 | ); 92 | 93 | $str = 'DTA1-HMAC-SHA256 ' . 94 | 'SignedHeaders=content-type;x-amz-date;x-amz-dta-version;x-amz-request-id, ' . 95 | 'Credential=367caa91-cde5-48f2-91fe-bb95f546e9f0/20140101, ' . 96 | 'Signature=6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe'; 97 | 98 | $this->assertEquals($str, (string)$header); 99 | } 100 | 101 | public function testCreateAuthorixationHeaderWithInvalidAlgorithm() 102 | { 103 | $this->setExpectedException('\InvalidArgumentException'); 104 | 105 | $header = new AuthorizationHeader( 106 | '', 107 | array('content-type', 'x-amz-date', 'x-amz-dta-version', 'x-amz-request-id'), 108 | array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0', 'date' => '20140101'), 109 | '6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe' 110 | ); 111 | } 112 | 113 | public function testCreateAuthorixationHeaderWithInvalidHeaders() 114 | { 115 | $this->setExpectedException('\InvalidArgumentException'); 116 | 117 | $header = new AuthorizationHeader( 118 | 'DTA1-HMAC-SHA256', 119 | 'test', 120 | array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0', 'date' => '20140101'), 121 | '6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe' 122 | ); 123 | } 124 | 125 | public function testCreateAuthorixationHeaderWithInvalidCredential() 126 | { 127 | $this->setExpectedException('\InvalidArgumentException'); 128 | 129 | $header = new AuthorizationHeader( 130 | 'DTA1-HMAC-SHA256', 131 | array('content-type', 'x-amz-date', 'x-amz-dta-version', 'x-amz-request-id'), 132 | array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0'), 133 | '6fe5d5bbf4acda9b0f47f66db3ad8f23a33117ee52b45ae69983bec0b50550fe' 134 | ); 135 | } 136 | 137 | public function testCreateAuthorixationHeaderWithInvalidSignature() 138 | { 139 | $this->setExpectedException('\InvalidArgumentException'); 140 | 141 | $header = new AuthorizationHeader( 142 | 'DTA1-HMAC-SHA256', 143 | array('content-type', 'x-amz-date', 'x-amz-dta-version', 'x-amz-request-id'), 144 | array('key' => '367caa91-cde5-48f2-91fe-bb95f546e9f0', 'date' => '20140101'), 145 | '' 146 | ); 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Signature/CredentialStoreTest.php: -------------------------------------------------------------------------------- 1 | loadFromFile(IOUtils::getFilePathFromHandle(self::$VALID_FILE)); 52 | 53 | $this->assertCorrectCredentials($store); 54 | } 55 | 56 | public function testLoad() 57 | { 58 | $store = new CredentialStore(); 59 | 60 | $contents = file_get_contents(IOUtils::getFilePathFromHandle(self::$VALID_FILE)); 61 | $store->load($contents); 62 | 63 | $this->assertCorrectCredentials($store); 64 | } 65 | 66 | public function testLoadFromEmptyString() 67 | { 68 | $this->setExpectedException('InvalidArgumentException'); 69 | 70 | $store = new CredentialStore(); 71 | $store->load(null); 72 | } 73 | 74 | public function testLoadFromInvalidFile() 75 | { 76 | $this->setExpectedException('InvalidArgumentException'); 77 | 78 | $store = new CredentialStore(); 79 | $store->loadFromFile(IOUtils::getFilePathFromHandle(self::$INVALID_FILE)); 80 | } 81 | 82 | public function testLoadFromEmptyFile() 83 | { 84 | $this->setExpectedException('InvalidArgumentException'); 85 | 86 | $store = new CredentialStore(); 87 | $store->loadFromFile(null); 88 | } 89 | 90 | public function testGetInvalidCredential() 91 | { 92 | $store = new CredentialStore(); 93 | $store->loadFromFile(IOUtils::getFilePathFromHandle(self::$VALID_FILE)); 94 | $credential = $store->get(self::$INVALID_KEY); 95 | 96 | $this->assertNull($credential); 97 | } 98 | 99 | public function testAddCredential() 100 | { 101 | $store = new CredentialStore(); 102 | $store->add(new Credential(self::$KEYS[0], self::$KEYS[1])); 103 | 104 | $credential = $store->get(self::$KEYS[1]); 105 | 106 | $this->assertEquals(self::$KEYS[0], $credential->getSecretKey()); 107 | $this->assertEquals(self::$KEYS[1], $credential->getPublicKey()); 108 | } 109 | 110 | public function testRemoveCredential() 111 | { 112 | $store = new CredentialStore(); 113 | $store->add(new Credential(self::$KEYS[0], self::$KEYS[1])); 114 | 115 | $this->assertEquals(1, count($store->getAll())); 116 | 117 | $store->remove(self::$KEYS[1]); 118 | 119 | $this->assertEquals(0, count($store->getAll())); 120 | } 121 | 122 | private function assertCorrectCredentials($store) 123 | { 124 | $this->assertEquals(self::$KEYS[0], $store->get(self::$KEYS[1])->getSecretKey()); 125 | $this->assertEquals(self::$KEYS[1], $store->get(self::$KEYS[1])->getPublicKey()); 126 | 127 | $this->assertEquals(self::$KEYS[2], $store->get(self::$KEYS[3])->getSecretKey()); 128 | $this->assertEquals(self::$KEYS[3], $store->get(self::$KEYS[3])->getPublicKey()); 129 | 130 | $this->assertEquals(self::$KEYS[4], $store->get(self::$KEYS[5])->getSecretKey()); 131 | $this->assertEquals(self::$KEYS[5], $store->get(self::$KEYS[5])->getPublicKey()); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Signature/CredentialTest.php: -------------------------------------------------------------------------------- 1 | setExpectedException('InvalidArgumentException'); 23 | 24 | $credential = new Credential(null, 'PUBLIC'); 25 | } 26 | 27 | public function testCreateCredentialWithInvalidArguments2() 28 | { 29 | $this->setExpectedException('InvalidArgumentException'); 30 | 31 | $credential = new Credential('SECRET', ''); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Signature/RequestTest.php: -------------------------------------------------------------------------------- 1 | assertEquals('http://amazon.com/index.html?a=1', $request->getUrl()); 37 | $this->assertEquals('POST', $request->getMethod()); 38 | $this->assertEquals('{}', $request->getBody()); 39 | 40 | $this->assertEquals(array('host' => 'amazon.com', 'accept' => '*/*'), $request->getHeaders()); 41 | } 42 | 43 | public function testFilterHeaders() 44 | { 45 | $server = array(); 46 | $server['HTTP_HOST'] = 'amazon.com'; 47 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 48 | $server['SERVER_PORT'] = '80'; 49 | $server['REQUEST_URI'] = '/index.html?a=1'; 50 | $server['REQUEST_METHOD'] = 'POST'; 51 | $server['HTTP_ACCEPT'] = '*/*'; 52 | 53 | $request = new Request($server); 54 | 55 | $filter = array('host'); 56 | 57 | $request->filterHeaders($filter); 58 | 59 | $this->assertEquals(1, count($request->getHeaders())); 60 | $this->assertEquals(array('accept' => '*/*'), $request->getHeaders()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Signature/SignerTest.php: -------------------------------------------------------------------------------- 1 | format(DateUtils::DATE_FORMAT_SHORT); 29 | $isoDate = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 30 | 31 | $server = array(); 32 | $server['HTTP_HOST'] = 'amazon.com'; 33 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 34 | $server['SERVER_PORT'] = '80'; 35 | $server['REQUEST_URI'] = '/'; 36 | $server['REQUEST_METHOD'] = 'GET'; 37 | $server['CONTENT_TYPE'] = 'application/json'; 38 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $isoDate; 39 | 40 | $content = 'body'; 41 | 42 | $request = new Request($server, $content); 43 | 44 | $credential = new Credential('SECRETKEY', 'KEYID'); 45 | $store = new CredentialStore(); 46 | $store->add($credential); 47 | 48 | $signer = new Signer(); 49 | 50 | // remove the host header because this signature was generated by the Java SDK 51 | // and it does not automatically adds this header 52 | $headers = &$request->getHeaders(); 53 | unset($headers['host']); 54 | 55 | $header = $signer->getAuthorizationHeader($request, $credential, $shortDate, $isoDate); 56 | 57 | $this->assertEquals('DTA1-HMAC-SHA256 SignedHeaders=content-type;x-amz-date, Credential=KEYID/20110909, Signature=4d2f81ea2cf8d6963f8176a22eec4c65ae95c63502326a7c148686da7d50f47e', $header); 58 | } 59 | 60 | public function testSignAndVerify() 61 | { 62 | $server = array(); 63 | $server['HTTP_HOST'] = 'amazon.com'; 64 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 65 | $server['SERVER_PORT'] = '80'; 66 | $server['REQUEST_URI'] = '/service/foo.php'; 67 | $server['REQUEST_METHOD'] = 'POST'; 68 | $server['HTTP_ACCEPT'] = '*/*'; 69 | $server['CONTENT_TYPE'] = 'application/json'; 70 | 71 | $content = '{"operation" : "bar"}'; 72 | 73 | $request = new Request($server, $content); 74 | 75 | $credential = new Credential('SECRET', 'PUBLIC'); 76 | $store = new CredentialStore(); 77 | $store->add($credential); 78 | 79 | $signer = new Signer(); 80 | 81 | $signer->sign($request, $credential); 82 | 83 | $verified = $signer->verify($request, $store); 84 | 85 | $this->assertTrue($verified); 86 | } 87 | 88 | public function testVerifyEmptyCredentialStore() 89 | { 90 | $this->setExpectedException('InvalidArgumentException'); 91 | 92 | $server = array(); 93 | $server['HTTP_HOST'] = 'amazon.com'; 94 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 95 | $server['SERVER_PORT'] = '80'; 96 | $server['REQUEST_URI'] = '/service/foo.php'; 97 | $server['REQUEST_METHOD'] = 'POST'; 98 | $server['HTTP_ACCEPT'] = '*/*'; 99 | $server['CONTENT_TYPE'] = 'application/json'; 100 | 101 | $content = '{"operation" : "bar"}'; 102 | 103 | $request = new Request($server, $content); 104 | 105 | $signer = new Signer(); 106 | $signer->verify($request, new CredentialStore()); 107 | } 108 | 109 | public function testVerifyRequestNoDate() 110 | { 111 | $server = array(); 112 | $server['HTTP_HOST'] = 'amazon.com'; 113 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 114 | $server['SERVER_PORT'] = '80'; 115 | $server['REQUEST_URI'] = '/'; 116 | $server['REQUEST_METHOD'] = 'GET'; 117 | $server['CONTENT_TYPE'] = 'application/json'; 118 | 119 | $content = 'body'; 120 | 121 | $request = new Request($server, $content); 122 | 123 | $credential = new Credential('SECRETKEY', 'KEYID'); 124 | $store = new CredentialStore(); 125 | $store->add($credential); 126 | 127 | $signer = new Signer(); 128 | 129 | $this->assertFalse($signer->verify($request, $store)); 130 | } 131 | 132 | public function testVerifyRequestNoAuthorization() 133 | { 134 | $server = array(); 135 | $server['HTTP_HOST'] = 'amazon.com'; 136 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 137 | $server['SERVER_PORT'] = '80'; 138 | $server['REQUEST_URI'] = '/'; 139 | $server['REQUEST_METHOD'] = 'GET'; 140 | $server['CONTENT_TYPE'] = 'application/json'; 141 | 142 | $dateNow = new \DateTime('@' . time()); 143 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 144 | 145 | $content = 'body'; 146 | 147 | $request = new Request($server, $content); 148 | 149 | $credential = new Credential('SECRETKEY', 'KEYID'); 150 | $store = new CredentialStore(); 151 | $store->add($credential); 152 | 153 | $signer = new Signer(); 154 | 155 | $this->assertFalse($signer->verify($request, $store)); 156 | } 157 | 158 | public function testVerifyRequestWithInvalidAuthorization() 159 | { 160 | $server = array(); 161 | $server['HTTP_HOST'] = 'amazon.com'; 162 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 163 | $server['SERVER_PORT'] = '80'; 164 | $server['REQUEST_URI'] = '/'; 165 | $server['REQUEST_METHOD'] = 'GET'; 166 | $server['CONTENT_TYPE'] = 'application/json'; 167 | 168 | $dateNow = new \DateTime('@' . time()); 169 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 170 | 171 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = 'foo'; 172 | 173 | $content = 'body'; 174 | 175 | $request = new Request($server, $content); 176 | 177 | $credential = new Credential('SECRETKEY', 'KEYID'); 178 | $store = new CredentialStore(); 179 | $store->add($credential); 180 | 181 | $signer = new Signer(); 182 | 183 | $this->assertFalse($signer->verify($request, $store)); 184 | } 185 | 186 | public function testVerifyRequestWithLateMessage() 187 | { 188 | $server = array(); 189 | $server['HTTP_HOST'] = 'amazon.com'; 190 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 191 | $server['SERVER_PORT'] = '80'; 192 | $server['REQUEST_URI'] = '/'; 193 | $server['REQUEST_METHOD'] = 'GET'; 194 | $server['CONTENT_TYPE'] = 'application/json'; 195 | 196 | $dateOld = \DateTime::createFromFormat(DateUtils::DATE_FORMAT_ISO8601, '20110909T233600Z'); 197 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateOld->format(DateUtils::DATE_FORMAT_ISO8601); 198 | 199 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = 'DTA1-HMAC-SHA256 SignedHeaders=xxx, Credential=KEYID/20110909, Signature=xxx'; 200 | 201 | $content = 'body'; 202 | 203 | $request = new Request($server, $content); 204 | 205 | $credential = new Credential('SECRETKEY', 'KEYID'); 206 | $store = new CredentialStore(); 207 | $store->add($credential); 208 | 209 | $signer = new Signer(); 210 | 211 | $this->assertFalse($signer->verify($request, $store)); 212 | } 213 | 214 | public function testVerifyRequestWithInvalidCredential() 215 | { 216 | $server = array(); 217 | $server['HTTP_HOST'] = 'amazon.com'; 218 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 219 | $server['SERVER_PORT'] = '80'; 220 | $server['REQUEST_URI'] = '/'; 221 | $server['REQUEST_METHOD'] = 'GET'; 222 | $server['CONTENT_TYPE'] = 'application/json'; 223 | 224 | $dateNow = new \DateTime('@' . time()); 225 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 226 | 227 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = 'DTA1-HMAC-SHA256 SignedHeaders=xxx, Credential=NOTKEYID/' . $dateNow->format(DateUtils::DATE_FORMAT_SHORT) . ', Signature=xxx'; 228 | 229 | $content = 'body'; 230 | 231 | $request = new Request($server, $content); 232 | 233 | $credential = new Credential('SECRETKEY', 'KEYID'); 234 | $store = new CredentialStore(); 235 | $store->add($credential); 236 | 237 | $signer = new Signer(); 238 | 239 | $this->assertFalse($signer->verify($request, $store)); 240 | } 241 | 242 | public function testVerifyRequestWithInvalidCredentialDate() 243 | { 244 | $server = array(); 245 | $server['HTTP_HOST'] = 'amazon.com'; 246 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 247 | $server['SERVER_PORT'] = '80'; 248 | $server['REQUEST_URI'] = '/'; 249 | $server['REQUEST_METHOD'] = 'GET'; 250 | $server['CONTENT_TYPE'] = 'application/json'; 251 | 252 | $dateNow = new \DateTime('@' . time()); 253 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 254 | 255 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = 'DTA1-HMAC-SHA256 SignedHeaders=xxx, Credential=KEYID/20110909, Signature=xxx'; 256 | 257 | $content = 'body'; 258 | 259 | $request = new Request($server, $content); 260 | 261 | $credential = new Credential('SECRETKEY', 'KEYID'); 262 | $store = new CredentialStore(); 263 | $store->add($credential); 264 | 265 | $signer = new Signer(); 266 | 267 | $this->assertFalse($signer->verify($request, $store)); 268 | } 269 | 270 | public function testVerifyRequestWithInvalidSignature() 271 | { 272 | $server = array(); 273 | $server['HTTP_HOST'] = 'amazon.com'; 274 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 275 | $server['SERVER_PORT'] = '80'; 276 | $server['REQUEST_URI'] = '/'; 277 | $server['REQUEST_METHOD'] = 'GET'; 278 | $server['CONTENT_TYPE'] = 'application/json'; 279 | 280 | $dateNow = new \DateTime('@' . time()); 281 | $server['HTTP_' . HttpUtils::X_AMZ_DATE_HEADER] = $dateNow->format(DateUtils::DATE_FORMAT_ISO8601); 282 | 283 | $server['HTTP_' . HttpUtils::AUTHORIZATION_HEADER] = 'DTA1-HMAC-SHA256 SignedHeaders=xxx, Credential=KEYID/' . $dateNow->format(DateUtils::DATE_FORMAT_SHORT) . ', Signature=xxx'; 284 | 285 | $content = 'body'; 286 | 287 | $request = new Request($server, $content); 288 | 289 | $credential = new Credential('SECRETKEY', 'KEYID'); 290 | $store = new CredentialStore(); 291 | $store->add($credential); 292 | 293 | $signer = new Signer(); 294 | 295 | $this->assertFalse($signer->verify($request, $store)); 296 | } 297 | 298 | public function testGetAuthorizationHeaderInvalidDates() 299 | { 300 | $this->setExpectedException('InvalidArgumentException'); 301 | 302 | $server = array(); 303 | $server['HTTP_HOST'] = 'amazon.com'; 304 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 305 | $server['SERVER_PORT'] = '80'; 306 | $server['REQUEST_URI'] = '/service/foo.php'; 307 | $server['REQUEST_METHOD'] = 'POST'; 308 | $server['HTTP_ACCEPT'] = '*/*'; 309 | $server['CONTENT_TYPE'] = 'application/json'; 310 | 311 | $content = '{"operation" : "bar"}'; 312 | 313 | $request = new Request($server, $content); 314 | 315 | $signer = new Signer(); 316 | $header = $signer->getAuthorizationHeader($request, new Credential('SECRET', 'PUBLIC'), null, null); 317 | } 318 | } 319 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Utils/HttpUtilsTest.php: -------------------------------------------------------------------------------- 1 | assertNotNull($url); 32 | $this->assertEquals('http://amazon.com/index.html?a=1', $url); 33 | } 34 | 35 | public function testParseFullURLWithCustomPort() 36 | { 37 | $server = array(); 38 | 39 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 40 | $server['SERVER_PORT'] = '1234'; 41 | $server['HTTP_HOST'] = 'amazon.com'; 42 | $server['REQUEST_URI'] = '/index.html?a=1'; 43 | 44 | $url = HttpUtils::parseFullURL($server); 45 | 46 | $this->assertNotNull($url); 47 | $this->assertEquals('http://amazon.com:1234/index.html?a=1', $url); 48 | } 49 | 50 | public function testParseFullURLWithSSL() 51 | { 52 | $server = array(); 53 | 54 | $server['HTTPS'] = 'on'; 55 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 56 | $server['SERVER_PORT'] = '443'; 57 | $server['HTTP_HOST'] = 'amazon.com'; 58 | $server['REQUEST_URI'] = '/index.html?a=1'; 59 | 60 | $url = HttpUtils::parseFullURL($server); 61 | 62 | $this->assertNotNull($url); 63 | $this->assertEquals('https://amazon.com/index.html?a=1', $url); 64 | } 65 | 66 | public function testParseFullURLWithSSLAndCustomPort() 67 | { 68 | $server = array(); 69 | 70 | $server['HTTPS'] = 'on'; 71 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 72 | $server['SERVER_PORT'] = '1234'; 73 | $server['HTTP_HOST'] = 'amazon.com'; 74 | $server['REQUEST_URI'] = '/index.html?a=1'; 75 | 76 | $url = HttpUtils::parseFullURL($server); 77 | 78 | $this->assertNotNull($url); 79 | $this->assertEquals('https://amazon.com:1234/index.html?a=1', $url); 80 | } 81 | 82 | public function testParseFullURLWithForwardedHost() 83 | { 84 | $server = array(); 85 | 86 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 87 | $server['SERVER_PORT'] = '1234'; 88 | $server['HTTP_HOST'] = 'amazon.com'; 89 | $server['HTTP_X_FORWARDED_HOST'] = 'amazon2.com'; 90 | $server['REQUEST_URI'] = '/index.html?a=1'; 91 | 92 | $url = HttpUtils::parseFullURL($server); 93 | 94 | $this->assertNotNull($url); 95 | $this->assertEquals('http://amazon2.com:1234/index.html?a=1', $url); 96 | } 97 | 98 | public function testParseFullURLWithServerName() 99 | { 100 | $server = array(); 101 | 102 | $server['SERVER_PROTOCOL'] = 'HTTP/1.1'; 103 | $server['SERVER_PORT'] = '1234'; 104 | $server['SERVER_NAME'] = 'amazon.com'; 105 | $server['REQUEST_URI'] = '/index.html?a=1'; 106 | 107 | $url = HttpUtils::parseFullURL($server); 108 | 109 | $this->assertNotNull($url); 110 | $this->assertEquals('http://amazon.com:1234/index.html?a=1', $url); 111 | } 112 | 113 | public function testParseRequestHeaders() 114 | { 115 | $server = array(); 116 | 117 | $server['HTTP_HOST'] = 'amazon.com'; 118 | $server['HTTP_ACCEPT'] = 'text/html,application/json;q=1'; 119 | $server['HTTP_CONNECTION'] = 'keep-alive'; 120 | $server['HTTP_ACCEPT_ENCODING'] = 'gzip,deflate,sdch'; 121 | $server['SERVER_PORT'] = '80'; 122 | 123 | $server['CONTENT_TYPE'] = 'application/json'; 124 | $server['CONTENT_LENGTH'] = '32'; 125 | 126 | $headers = HttpUtils::parseRequestHeaders($server); 127 | 128 | $this->assertNotNull($headers); 129 | $this->assertEquals(6, count($headers)); 130 | $this->assertEquals('amazon.com', $headers['host']); 131 | $this->assertEquals('text/html,application/json;q=1', $headers['accept']); 132 | $this->assertEquals('keep-alive', $headers['connection']); 133 | $this->assertEquals('gzip,deflate,sdch', $headers['accept-encoding']); 134 | $this->assertEquals('application/json', $headers['content-type']); 135 | $this->assertEquals('32', $headers['content-length']); 136 | } 137 | 138 | public function testNormalizePath() 139 | { 140 | $this->assertEquals('a/c', HttpUtils::normalizePath('a/c')); 141 | $this->assertEquals('a/c', HttpUtils::normalizePath('a//c')); 142 | $this->assertEquals('a/c', HttpUtils::normalizePath('a/c/.')); 143 | $this->assertEquals('a/c', HttpUtils::normalizePath('a/c/b/..')); 144 | $this->assertEquals('a/c/', HttpUtils::normalizePath('a/c/')); 145 | $this->assertEquals('/a/c', HttpUtils::normalizePath('/../a/c')); 146 | $this->assertEquals('/a/c', HttpUtils::normalizePath('/../a/b/../././/c')); 147 | } 148 | 149 | public function testNormalizeEmptyPath() 150 | { 151 | $this->assertEquals('/', HttpUtils::normalizePath('')); 152 | $this->assertEquals('/', HttpUtils::normalizePath(null)); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /tst/Amazon/InstantAccess/Utils/IOUtilsTest.php: -------------------------------------------------------------------------------- 1 | setExpectedException('InvalidArgumentException'); 23 | 24 | $url = IOUtils::getFilePathFromHandle(null); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tst/bootstrap-phar.php: -------------------------------------------------------------------------------- 1 | pushHandler(new Monolog\Handler\StreamHandler('build/log/test-phar-output.log', Monolog\Logger::DEBUG)); 24 | Amazon\InstantAccess\Log\Logger::setLogger($monolog); 25 | date_default_timezone_set('UTC'); 26 | -------------------------------------------------------------------------------- /tst/bootstrap.php: -------------------------------------------------------------------------------- 1 | pushHandler(new Monolog\Handler\StreamHandler('./build/log/test-output.log', Monolog\Logger::DEBUG)); 24 | Amazon\InstantAccess\Log\Logger::setLogger($monolog); 25 | date_default_timezone_set('UTC'); 26 | --------------------------------------------------------------------------------