├── .gitignore ├── tests ├── bootstrap.php ├── common │ └── TestCase.php └── functional │ └── SilexApplicationTest.php ├── .travis.yml ├── phpunit.xml.dist ├── LICENSE ├── composer.json ├── README.md ├── src └── Dflydev │ └── Stack │ └── Hawk.php └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | add('common', __DIR__); 5 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.4 5 | - 5.5 6 | 7 | before_script: 8 | - composer install 9 | 10 | script: vendor/bin/phpunit --coverage-text --verbose 11 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | ./tests/unit/ 10 | 11 | 12 | 13 | 14 | 15 | ./tests/integration/ 16 | 17 | 18 | 19 | 20 | 21 | ./tests/functional/ 22 | 23 | 24 | 25 | 26 | 27 | ./src/ 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/common/TestCase.php: -------------------------------------------------------------------------------- 1 | credentials = new Credentials('key1234', 'sha256', 'id1234'); 16 | } 17 | 18 | protected function hawkify(HttpKernelInterface $app, array $config = []) 19 | { 20 | $config = array_merge([ 21 | 'credentials_provider' => function ($id) { 22 | if ($this->credentials->id() === $id) { 23 | return $this->credentials; 24 | } 25 | } 26 | ], $config); 27 | 28 | return new Hawk($app, $config); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Dragonfly Development Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflydev/stack-hawk", 3 | "description": "Hawk Stack middleware", 4 | "keywords": ["stack", "stack-2", "hawk"], 5 | "license": "MIT", 6 | "authors": [ 7 | { 8 | "name": "Dragonfly Development Inc.", 9 | "email": "info@dflydev.com", 10 | "homepage": "http://dflydev.com" 11 | }, 12 | { 13 | "name": "Beau Simensen", 14 | "email": "beau@dflydev.com", 15 | "homepage": "http://beausimensen.com" 16 | } 17 | ], 18 | "autoload": { 19 | "psr-0": { 20 | "Dflydev\\Stack": "src" 21 | } 22 | }, 23 | "require": { 24 | "php": ">=5.4.0", 25 | "dflydev/hawk": "1.0.*@dev", 26 | "dflydev/stack-authentication": "1.0.*@dev", 27 | "dflydev/stack-firewall": "1.0.*@dev", 28 | "symfony/http-foundation": "~2.1", 29 | "symfony/http-kernel": "~2.1" 30 | }, 31 | "require-dev": { 32 | "phpunit/phpunit": "3.7.21", 33 | "silex/silex": "1.1.*@dev", 34 | "stack/builder": "~1.0@dev", 35 | "stack/callable-http-kernel": "~1.0@dev", 36 | "stack/inline": "~1.0@dev", 37 | "symfony/browser-kit": "~2.1" 38 | }, 39 | "extra": { 40 | "branch-alias": { 41 | "dev-master": "1.0-dev" 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Hawk Stack Middleware 2 | ===================== 3 | 4 | A [Stack][0] middleware to enable [Hawk][1] authentication following the 5 | [STACK-2 Authentication][2] conventions. 6 | 7 | 8 | Installation 9 | ------------ 10 | 11 | Through [Composer][3] as [dflydev/stack-hawk][4]. 12 | 13 | 14 | Usage 15 | ----- 16 | 17 | The Hawk middleware accepts the following options: 18 | 19 | * **credentials_provider**: *(required)* Either an instance of 20 | `Dflydev\Hawk\Credentials\CredentialsProviderInterface` or a callable that 21 | receives an ID as its only argument and is expected to return a 22 | `Dflydev\Hawk\Credentials\CredentialsInterface` or null. 23 | * **sign_response**: Should responses be signed? Boolean. Default **true**. 24 | * **validate_payload_response**: Should payload responses be validated? 25 | Boolean. Default **true**. 26 | * **validate_payload_request**: Should payload requests be validated? Boolean. 27 | Default **true**. 28 | * **crypto**: An instance of `Dflydev\Hawk\Crypto\Crypto` or a callable that 29 | will return an instance of `Dflydev\Hawk\Crypto\Crypto`. 30 | * **server**: An instance of `Dflydev\Hawk\Server\ServerInterface` or a 31 | callable that will return an instance of 32 | `Dflydev\Hawk\Server\ServerInterface`. 33 | * **time_provider**: An instance of `Dflydev\Hawk\Time\TimeProviderInterface` 34 | or a callable that will return an instance of 35 | `Dflydev\Hawk\Time\TimeProviderInterface`. 36 | * **token_translator**: A callable that receives a 37 | `Dflydev\Hawk\Credentials\CredentialsInterface` as its only argument and is 38 | expected to return a token. Default implementation returns 39 | `$credentials->id()` as the token. 40 | * **firewall**: A firewall configuration compatible with 41 | [dflydev/stack-firewall][5]. 42 | 43 | ```php 44 | id(); 63 | }; 64 | 65 | $app = new Dflydev\Stack\Hawk($app, [ 66 | 'firewall' => [ 67 | ['path' => '/api'], // Only /api requests will be protected by Hawk! 68 | ], 69 | 'credentials_provider' => $credentialsProvider, 70 | 'token_translator' => $tokenTranslator, 71 | 'sign_response' => false, // do not sign the response; default true 72 | ]); 73 | ``` 74 | 75 | 76 | License 77 | ------- 78 | 79 | MIT, see LICENSE. 80 | 81 | 82 | Community 83 | --------- 84 | 85 | If you have questions or want to help out, join us in the **#stackphp** or **#dflydev** channels on **irc.freenode.net**. 86 | 87 | 88 | [0]: http://stackphp.com/ 89 | [1]: https://github.com/hueniverse/hawk 90 | [2]: http://stackphp.com/specs/STACK-2/ 91 | [3]: http://getcomposer.org 92 | [4]: https://packagist.org/packages/dflydev/stack-hawk 93 | [5]: https://packagist.org/packages/dflydev/stack-firewall 94 | -------------------------------------------------------------------------------- /src/Dflydev/Stack/Hawk.php: -------------------------------------------------------------------------------- 1 | app = $app; 24 | $this->container = $this->setupContainer($options); 25 | } 26 | 27 | public function handle(Request $request, $type = HttpKernelInterface::MASTER_REQUEST, $catch = true) 28 | { 29 | // The challenge callback is called if a 401 response is detected that 30 | // has a "WWW-Authenticate: Stack" header. This is per the Stack 31 | // Authentication and Authorization proposals. It is passed the existing 32 | // response object. 33 | $challenge = function (Response $response) { 34 | $response->headers->set('WWW-Authenticate', 'Hawk'); 35 | 36 | return $response; 37 | }; 38 | 39 | // The authenticate callback is called if the request has no Stack 40 | // authentication token but there is an authorization header. It is 41 | // passed an app we should delegate to (assuming we do not return 42 | // beforehand) and a boolean value indicating whether or not anonymous 43 | // requests should be allowed. 44 | $authenticate = function ($app, $anonymous) use ($request, $type, $catch, $challenge) { 45 | try { 46 | $header = HeaderFactory::createFromString( 47 | 'Authorization', 48 | $request->headers->get('authorization') 49 | ); 50 | } catch (NotHawkAuthorizationException $e) { 51 | if ($anonymous) { 52 | // This is not a Hawk request but the firewall allows 53 | // anonymous requests so we should wrap the application 54 | // so that we might be able to challenge if authorization 55 | // fails. 56 | return (new WwwAuthenticateStackChallenge($app, $challenge)) 57 | ->handle($request, $type, $catch); 58 | } 59 | 60 | // Anonymous requests are not allowed so we should challenge 61 | // immediately. 62 | return call_user_func($challenge, (new Response)->setStatusCode(401)); 63 | } catch (FieldValueParserException $e) { 64 | // Something horribly wrong has happened. 65 | return (new Response)->setStatusCode(400); 66 | } 67 | 68 | try { 69 | $payload = $this->container['validate_payload_request'] 70 | ? ($request->getMethod() !== 'GET' ? $request->getContent() : null) 71 | : null; 72 | 73 | $authenticationResponse = $this->container['server']->authenticate( 74 | $request->getMethod(), 75 | $request->getHost(), 76 | $request->getPort(), 77 | $request->getRequestUri(), 78 | $request->headers->get('content-type'), 79 | $payload, 80 | $header 81 | ); 82 | } catch (UnauthorizedException $e) { 83 | $response = (new Response)->setStatusCode(401); 84 | $header = $e->getHeader(); 85 | $response->headers->set($header->fieldName(), $header->fieldValue()); 86 | 87 | return $response; 88 | } 89 | 90 | // Stack authentication compatibility. 91 | $request->attributes->set( 92 | 'stack.authn.token', 93 | $this->container['token_translator']($authenticationResponse->credentials()) 94 | ); 95 | 96 | // Hawk specific information 97 | $request->attributes->set('hawk.credentials', $authenticationResponse->credentials()); 98 | $request->attributes->set('hawk.artifacts', $authenticationResponse->artifacts()); 99 | 100 | $response = $app->handle($request, $type, $catch); 101 | 102 | if ($this->container['sign_response']) { 103 | $options = []; 104 | if ($this->container['validate_payload_response']) { 105 | $options['payload'] = $response->getContent(); 106 | $options['content_type'] = $response->headers->get('content-type'); 107 | } 108 | 109 | $header = $this->container['server']->createHeader( 110 | $authenticationResponse->credentials(), 111 | $authenticationResponse->artifacts(), 112 | $options 113 | ); 114 | 115 | $response->headers->set($header->fieldName(), $header->fieldValue()); 116 | } 117 | 118 | return $response; 119 | }; 120 | 121 | return (new Firewall($this->app, [ 122 | 'challenge' => $challenge, 123 | 'authenticate' => $authenticate, 124 | 'firewall' => $this->container['firewall'], 125 | ])) 126 | ->handle($request, $type, $catch); 127 | } 128 | 129 | private function setupContainer(array $options = array()) 130 | { 131 | if (!isset($options['credentials_provider'])) { 132 | throw new \RuntimeException("No 'credentials_provider' callback or service specified"); 133 | } 134 | 135 | if ($options['credentials_provider'] instanceof CredentialsProviderInterface || 136 | is_callable($options['credentials_provider'])) { 137 | $credentialsProvider = $options['credentials_provider']; 138 | } else { 139 | throw new \InvalidArgumentException( 140 | "The 'credentials_provider' must either be an instance of CredentialsProviderInterface " . 141 | "or it must be callable" 142 | ); 143 | } 144 | 145 | unset($options['credentials_provider']); 146 | 147 | $c = new Pimple([ 148 | 'sign_response' => true, 149 | 'validate_payload_response' => true, 150 | 'validate_payload_request' => true, 151 | 'firewall' => [], 152 | ]); 153 | 154 | $c['crypto'] = $c->share(function () { 155 | return new Crypto; 156 | }); 157 | 158 | $c['server'] = $c->share(function () use ($c, $credentialsProvider) { 159 | $builder = (new ServerBuilder($credentialsProvider)) 160 | ->setCrypto($c['crypto']); 161 | 162 | if (isset($c['time_provider'])) { 163 | $builder->setTimeProvider($c['time_provider']); 164 | } 165 | 166 | return $builder->build(); 167 | }); 168 | 169 | $c['token_translator'] = $c->protect(function (CredentialsInterface $credentials) { 170 | return $credentials->id(); 171 | }); 172 | 173 | foreach ($options as $name => $value) { 174 | if (in_array($name, ['crypto', 'server', 'time_provider', 'token_translator'])) { 175 | if (is_callable($value)) { 176 | $c[$name] = $c->share($value); 177 | } else { 178 | $c[$name] = $c->share(function () use ($value) { 179 | return $value; 180 | }); 181 | } 182 | 183 | continue; 184 | } 185 | 186 | $c[$name] = $value; 187 | } 188 | 189 | return $c; 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /tests/functional/SilexApplicationTest.php: -------------------------------------------------------------------------------- 1 | hawkify($this->createTestApp(), ['firewall' => [ 25 | ['path' => '/foo'], 26 | ]]); 27 | 28 | $client = new Client($app); 29 | 30 | $client->request('GET', '/'); 31 | $this->assertEquals('Root.', $client->getResponse()->getContent()); 32 | } 33 | 34 | /** @test */ 35 | public function shouldNotChallengeForUnprotectedResourceNoHeader() 36 | { 37 | $app = $this->hawkify($this->createTestApp(), ['firewall' => [ 38 | ['path' => '/', 'anonymous' => true], 39 | ]]); 40 | 41 | $client = new Client($app); 42 | 43 | $client->request('GET', '/'); 44 | $this->assertEquals('Root.', $client->getResponse()->getContent()); 45 | } 46 | 47 | /** @test */ 48 | public function shouldChallengeForProtectedResourceNoHeader() 49 | { 50 | $app = $this->hawkify($this->createTestApp(), ['firewall' => [ 51 | ['path' => '/', 'anonymous' => true], 52 | ]]); 53 | 54 | $client = new Client($app); 55 | 56 | $client->request('GET', '/protected/resource'); 57 | $this->assertEquals(401, $client->getResponse()->getStatusCode()); 58 | $this->assertEquals('Hawk', $client->getResponse()->headers->get('www-authenticate')); 59 | } 60 | 61 | /** 62 | * @test 63 | */ 64 | public function shouldGetExpectedToken() 65 | { 66 | $timeProvider = new MockTimeProvider(56789); 67 | $app = $this->hawkify($this->createTestApp(), ['time_provider' => $timeProvider]); 68 | 69 | $hawkClient = (new \Dflydev\Hawk\Client\ClientBuilder) 70 | ->setTimeProvider($timeProvider) 71 | ->build(); 72 | 73 | $hawkRequest = $hawkClient->createRequest( 74 | $this->credentials, 75 | 'http://localhost/protected/token', 76 | 'GET', 77 | array() 78 | ); 79 | 80 | $client = new Client($app); 81 | 82 | $client->request('GET', '/protected/token', [], [], ['HTTP_AUTHORIZATION' => $hawkRequest->header()->fieldValue()]); 83 | $this->assertEquals($this->credentials->id(), $client->getResponse()->getContent()); 84 | } 85 | 86 | /** 87 | * @test 88 | * @dataProvider protectedAndUnprotectedResources 89 | */ 90 | public function shouldChallengeForInvalidHeader($resource) 91 | { 92 | $app = $this->hawkify($this->createTestApp()); 93 | 94 | $client = new Client($app); 95 | 96 | $client->request( 97 | 'GET', 98 | $resource, 99 | [], 100 | [], 101 | [ 102 | 'HTTP_AUTHORIZATION' => 'Hawk' 103 | ] 104 | ); 105 | $this->assertEquals(401, $client->getResponse()->getStatusCode()); 106 | $this->assertEquals('Hawk error="Missing attributes"', $client->getResponse()->headers->get('www-authenticate')); 107 | } 108 | 109 | /** 110 | * @test 111 | * @dataProvider protectedAndUnprotectedResources 112 | */ 113 | public function shouldAllowAccessToResource($resource, $expectedContent) 114 | { 115 | $timeProvider = new MockTimeProvider(56789); 116 | $app = $this->hawkify($this->createTestApp(), ['time_provider' => $timeProvider]); 117 | 118 | $hawkClient = (new \Dflydev\Hawk\Client\ClientBuilder) 119 | ->setTimeProvider($timeProvider) 120 | ->build(); 121 | 122 | $hawkRequest = $hawkClient->createRequest( 123 | $this->credentials, 124 | 'http://localhost'.$resource, 125 | 'GET', 126 | array() 127 | ); 128 | 129 | $client = new Client($app); 130 | 131 | $client->request('GET', $resource, [], [], ['HTTP_AUTHORIZATION' => $hawkRequest->header()->fieldValue()]); 132 | $this->assertEquals($expectedContent, $client->getResponse()->getContent()); 133 | 134 | $authenticatedResponse = $hawkClient->authenticate( 135 | $this->credentials, 136 | $hawkRequest, 137 | $client->getResponse()->headers->get('Server-Authorization'), 138 | [ 139 | 'payload' => $client->getResponse()->getContent(), 140 | 'content_type' => $client->getResponse()->headers->get('content-type'), 141 | ] 142 | ); 143 | 144 | $this->assertTrue($authenticatedResponse); 145 | } 146 | 147 | /** 148 | * @test 149 | * @dataProvider protectedAndUnprotectedResources 150 | */ 151 | public function shouldAllowAccessToResourceNoSignedResponse($resource, $expectedContent) 152 | { 153 | $timeProvider = new MockTimeProvider(56789); 154 | $app = $this->hawkify($this->createTestApp(), [ 155 | 'time_provider' => $timeProvider, 156 | 'sign_response' => false, 157 | ]); 158 | 159 | $hawkClient = (new \Dflydev\Hawk\Client\ClientBuilder) 160 | ->setTimeProvider($timeProvider) 161 | ->build(); 162 | 163 | $hawkRequest = $hawkClient->createRequest( 164 | $this->credentials, 165 | 'http://localhost'.$resource, 166 | 'GET', 167 | array() 168 | ); 169 | 170 | $client = new Client($app); 171 | 172 | $client->request('GET', $resource, [], [], ['HTTP_AUTHORIZATION' => $hawkRequest->header()->fieldValue()]); 173 | $this->assertEquals($expectedContent, $client->getResponse()->getContent()); 174 | $this->assertFalse($client->getResponse()->headers->has('Server-Authorization')); 175 | } 176 | 177 | /** 178 | * @test 179 | */ 180 | public function shouldConvertWwwAuthenticateStackToHawk() 181 | { 182 | $authz = function( 183 | HttpKernelInterface $app, 184 | Request $request, 185 | $type = HttpKernelInterface::MASTER_REQUEST, 186 | $catch = true 187 | ) { 188 | // Simulate Authorization failure by returning 401 status 189 | // code with WWW-Authenticate: Stack. 190 | $response = (new Response)->setStatusCode(401); 191 | $response->headers->set('WWW-Authenticate', 'Stack'); 192 | return $response; 193 | }; 194 | 195 | $app = $this->hawkify(new Inline($this->createTestApp(), $authz)); 196 | 197 | $client = new Client($app); 198 | 199 | $client->request('GET', '/'); 200 | $this->assertEquals('Hawk', $client->getResponse()->headers->get('WWW-Authenticate')); 201 | } 202 | 203 | /** 204 | * @test 205 | */ 206 | public function shouldNotClobberExistingToken() 207 | { 208 | $authnMiddleware = function( 209 | HttpKernelInterface $app, 210 | Request $request, 211 | $type = HttpKernelInterface::MASTER_REQUEST, 212 | $catch = true 213 | ) { 214 | // We are going to claim that we authenticated... 215 | $request->attributes->set('stack.authn.token', 'foo'); 216 | 217 | // Hawk should actually capture the WWW-Authenticate: Stack response 218 | // and challenge on its own. 219 | return $app->handle($request, $type, $catch); 220 | }; 221 | 222 | $app = new Inline($this->hawkify($this->createTestApp()), $authnMiddleware); 223 | 224 | $client = new Client($app); 225 | 226 | $client->request('GET', '/protected/token'); 227 | $this->assertEquals('foo', $client->getResponse()->getContent()); 228 | } 229 | 230 | /** 231 | * @test 232 | */ 233 | public function shouldChallengeOnAuthorizationEvenIfOtherMiddlewareAuthenticated() 234 | { 235 | $authnMiddleware = function( 236 | HttpKernelInterface $app, 237 | Request $request, 238 | $type = HttpKernelInterface::MASTER_REQUEST, 239 | $catch = true 240 | ) { 241 | // We are going to claim that we authenticated... 242 | $request->attributes->set('stack.authn.token', 'foo'); 243 | 244 | // Hawk should actually capture the WWW-Authenticate: Stack response 245 | // and challenge on its own. 246 | return $app->handle($request, $type, $catch); 247 | }; 248 | 249 | $authzMiddleware = function( 250 | HttpKernelInterface $app, 251 | Request $request, 252 | $type = HttpKernelInterface::MASTER_REQUEST, 253 | $catch = true 254 | ) { 255 | // Simulate Authorization failure by returning 401 status 256 | // code with WWW-Authenticate: Stack. 257 | $response = (new Response)->setStatusCode(401); 258 | $response->headers->set('WWW-Authenticate', 'Stack'); 259 | return $response; 260 | }; 261 | 262 | $app = new Inline($this->hawkify(new Inline($this->createTestApp(), $authzMiddleware)), $authnMiddleware); 263 | 264 | $client = new Client($app); 265 | 266 | $client->request('GET', '/protected/token'); 267 | $this->assertEquals(401, $client->getResponse()->getStatusCode()); 268 | $this->assertEquals('Hawk', $client->getResponse()->headers->get('www-authenticate')); 269 | } 270 | 271 | /** 272 | * @test 273 | */ 274 | public function shouldPassTentTestVectorsAppRequest() 275 | { 276 | $timeProvider = new MockTimeProvider(1368996800); 277 | $nonceProvider = new MockNonceProvider('3yuYCD4Z'); 278 | $credentials = new Credentials('HX9QcbD-r3ItFEnRcAuOSg', 'sha256', 'exqbZWtykFZIh2D7cXi9dA'); 279 | 280 | $app = $this->hawkify($this->createTestApp(), [ 281 | 'validate_payload_response' => false, // this test actually has no payload validaton 282 | 'time_provider' => $timeProvider, 283 | 'credentials_provider' => function ($id) use ($credentials) { 284 | if ($credentials->id() === $id) { 285 | return $credentials; 286 | } 287 | } 288 | ]); 289 | 290 | $hawkClient = (new \Dflydev\Hawk\Client\ClientBuilder) 291 | ->setTimeProvider($timeProvider) 292 | ->setNonceProvider($nonceProvider) 293 | ->build(); 294 | 295 | $hawkRequest = $hawkClient->createRequest( 296 | $credentials, 297 | 'https://example.com/posts', 298 | 'POST', 299 | [ 300 | 'payload' => '{"type":"https://tent.io/types/status/v0#"}', 301 | 'content_type' => 'application/vnd.tent.post.v0+json', 302 | 'app' => 'wn6yzHGe5TLaT-fvOPbAyQ', 303 | ] 304 | ); 305 | 306 | $expectedHeader = HeaderFactory::createFromString( 307 | 'Authorization', 308 | 'Hawk id="exqbZWtykFZIh2D7cXi9dA", mac="2sttHCQJG9ejj1x7eCi35FP23Miu9VtlaUgwk68DTpM=", ts="1368996800", nonce="3yuYCD4Z", hash="neQFHgYKl/jFqDINrC21uLS0gkFglTz789rzcSr7HYU=", app="wn6yzHGe5TLaT-fvOPbAyQ"' 309 | ); 310 | 311 | $this->assertEquals($expectedHeader->fieldName(), $hawkRequest->header()->fieldName()); 312 | $this->assertEquals($expectedHeader->attributes(), $hawkRequest->header()->attributes()); 313 | 314 | $client = new Client($app); 315 | 316 | $client->request( 317 | 'POST', 318 | 'https://example.com/posts', 319 | [], 320 | [], 321 | [ 322 | 'HTTP_AUTHORIZATION' => $hawkRequest->header()->fieldValue(), 323 | 'CONTENT_TYPE' => 'application/vnd.tent.post.v0+json', 324 | ], 325 | '{"type":"https://tent.io/types/status/v0#"}' 326 | ); 327 | 328 | $this->assertEquals('{"type":"https://tent.io/types/status/v0#"}', $client->getResponse()->getContent()); 329 | 330 | $this->assertEquals( 331 | 'Hawk mac="lTG3kTBr33Y97Q4KQSSamu9WY/mOUKnZzq/ho9x+yxw="', 332 | $client->getResponse()->headers->get('Server-Authorization') 333 | ); 334 | 335 | $authenticatedResponse = $hawkClient->authenticate( 336 | $credentials, 337 | $hawkRequest, 338 | $client->getResponse()->headers->get('Server-Authorization'), 339 | [ 340 | //'payload' => $client->getResponse()->getContent(), 341 | //'content_type' => $client->getResponse()->headers->get('content-type'), 342 | ] 343 | ); 344 | 345 | $this->assertTrue($authenticatedResponse); 346 | } 347 | 348 | 349 | /** 350 | * @test 351 | */ 352 | public function shouldPassTentTestVectorsRelationshipRequest() 353 | { 354 | $timeProvider = new MockTimeProvider(1368996800); 355 | $nonceProvider = new MockNonceProvider('3yuYCD4Z'); 356 | $credentials = new Credentials('HX9QcbD-r3ItFEnRcAuOSg', 'sha256', 'exqbZWtykFZIh2D7cXi9dA'); 357 | 358 | $app = $this->hawkify($this->createTestApp(), [ 359 | 'validate_payload_request' => false, 360 | 'time_provider' => $timeProvider, 361 | 'credentials_provider' => function ($id) use ($credentials) { 362 | if ($credentials->id() === $id) { 363 | return $credentials; 364 | } 365 | } 366 | ]); 367 | 368 | $hawkClient = (new \Dflydev\Hawk\Client\ClientBuilder) 369 | ->setTimeProvider($timeProvider) 370 | ->setNonceProvider($nonceProvider) 371 | ->build(); 372 | 373 | $hawkRequest = $hawkClient->createRequest( 374 | $credentials, 375 | 'https://example.com/posts', 376 | 'POST', 377 | [ 378 | ] 379 | ); 380 | 381 | $expectedHeader = HeaderFactory::createFromString( 382 | 'Authorization', 383 | 'Hawk id="exqbZWtykFZIh2D7cXi9dA", mac="OO2ldBDSw8KmNHlEdTC4BciIl8+uiuCRvCnJ9KkcR3Y=", ts="1368996800", nonce="3yuYCD4Z"' 384 | ); 385 | 386 | $this->assertEquals($expectedHeader->fieldName(), $hawkRequest->header()->fieldName()); 387 | $this->assertEquals($expectedHeader->attributes(), $hawkRequest->header()->attributes()); 388 | 389 | $client = new Client($app); 390 | 391 | $client->request( 392 | 'POST', 393 | 'https://example.com/posts', 394 | [], 395 | [], 396 | [ 397 | 'HTTP_AUTHORIZATION' => $hawkRequest->header()->fieldValue(), 398 | 'CONTENT_TYPE' => 'application/vnd.tent.post.v0+json', 399 | ], 400 | '{"type":"https://tent.io/types/status/v0#"}' 401 | ); 402 | 403 | $this->assertEquals( 404 | 'Hawk mac="LvxASIZ2gop5cwE2mNervvz6WXkPmVslwm11MDgEZ5E=", hash="neQFHgYKl/jFqDINrC21uLS0gkFglTz789rzcSr7HYU="', 405 | $client->getResponse()->headers->get('Server-Authorization') 406 | ); 407 | 408 | $authenticatedResponse = $hawkClient->authenticate( 409 | $credentials, 410 | $hawkRequest, 411 | $client->getResponse()->headers->get('Server-Authorization'), 412 | [ 413 | 'payload' => $client->getResponse()->getContent(), 414 | 'content_type' => $client->getResponse()->headers->get('content-type'), 415 | ] 416 | ); 417 | 418 | $this->assertTrue($authenticatedResponse); 419 | } 420 | 421 | protected function createTestApp() 422 | { 423 | $app = new Application; 424 | $app['exception_handler']->disable(); 425 | 426 | $app->get('/', function () { 427 | return 'Root.'; 428 | }); 429 | 430 | $app->get('/protected/resource', function () { 431 | return 'Protected Resource.'; 432 | }); 433 | 434 | $app->get('/protected/token', function (Request $request) { 435 | return $request->attributes->get('stack.authn.token'); 436 | }); 437 | 438 | $app->post('/posts', function () { 439 | $response = (new Response) 440 | ->setStatusCode(200) 441 | ->setContent('{"type":"https://tent.io/types/status/v0#"}'); 442 | 443 | $response->headers->set('Content-Type', 'application/vnd.tent.post.v0+json'); 444 | 445 | return $response; 446 | }); 447 | 448 | // Simple Silex middleware to always let certain requests go through 449 | // and to always throw 401 responses in all other cases *unless* 450 | // stack.authn.token has been set correctly. 451 | $app->before(function (Request $request) { 452 | if (in_array($request->getRequestUri(), array('/'))) { 453 | return; 454 | } 455 | 456 | if (!$request->attributes->has('stack.authn.token')) { 457 | $response = (new Response)->setStatusCode(401); 458 | $response->headers->set('WWW-Authenticate', 'Stack'); 459 | 460 | return $response; 461 | } 462 | }); 463 | 464 | return $app; 465 | } 466 | 467 | public function protectedAndUnprotectedResources() 468 | { 469 | return [ 470 | ['/', 'Root.'], 471 | ['/protected/resource', 'Protected Resource.'], 472 | ]; 473 | } 474 | } 475 | 476 | class MockNonceProvider implements NonceProviderInterface 477 | { 478 | private $nonce; 479 | 480 | public function __construct($nonce) 481 | { 482 | $this->nonce = $nonce; 483 | } 484 | 485 | public function createNonce() 486 | { 487 | return $this->nonce; 488 | } 489 | } 490 | 491 | class MockTimeProvider implements TimeProviderInterface 492 | { 493 | private $timestamp; 494 | 495 | public function __construct($timestamp) 496 | { 497 | $this->timestamp = $timestamp; 498 | } 499 | 500 | public function createTimestamp() 501 | { 502 | return $this->timestamp; 503 | } 504 | } 505 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file" 5 | ], 6 | "hash": "daa896c1e1b1c59f7333752d828d587b", 7 | "packages": [ 8 | { 9 | "name": "dflydev/hawk", 10 | "version": "dev-master", 11 | "source": { 12 | "type": "git", 13 | "url": "https://github.com/dflydev/dflydev-hawk.git", 14 | "reference": "7b23fd8312bed3bcc63365b97461b7ecc2a64266" 15 | }, 16 | "dist": { 17 | "type": "zip", 18 | "url": "https://api.github.com/repos/dflydev/dflydev-hawk/zipball/7b23fd8312bed3bcc63365b97461b7ecc2a64266", 19 | "reference": "7b23fd8312bed3bcc63365b97461b7ecc2a64266", 20 | "shasum": "" 21 | }, 22 | "require": { 23 | "ircmaxell/random-lib": "1.0.*@dev" 24 | }, 25 | "require-dev": { 26 | "phpunit/phpunit": "3.7.21" 27 | }, 28 | "type": "library", 29 | "extra": { 30 | "branch-alias": { 31 | "dev-master": "1.0.x-dev" 32 | } 33 | }, 34 | "autoload": { 35 | "psr-0": { 36 | "Dflydev\\Hawk": "src" 37 | } 38 | }, 39 | "notification-url": "https://packagist.org/downloads/", 40 | "license": [ 41 | "MIT" 42 | ], 43 | "authors": [ 44 | { 45 | "name": "Dragonfly Development Inc.", 46 | "email": "info@dflydev.com", 47 | "homepage": "http://dflydev.com" 48 | }, 49 | { 50 | "name": "Beau Simensen", 51 | "email": "beau@dflydev.com", 52 | "homepage": "http://beausimensen.com" 53 | } 54 | ], 55 | "description": "Hawk", 56 | "keywords": [ 57 | "Authentication", 58 | "bewit", 59 | "hawk" 60 | ], 61 | "time": "2013-08-02 02:14:11" 62 | }, 63 | { 64 | "name": "dflydev/stack-authentication", 65 | "version": "dev-master", 66 | "source": { 67 | "type": "git", 68 | "url": "https://github.com/dflydev/dflydev-stack-authentication.git", 69 | "reference": "62b092ac20517a03c41e847a15468f018f0d62e7" 70 | }, 71 | "dist": { 72 | "type": "zip", 73 | "url": "https://api.github.com/repos/dflydev/dflydev-stack-authentication/zipball/62b092ac20517a03c41e847a15468f018f0d62e7", 74 | "reference": "62b092ac20517a03c41e847a15468f018f0d62e7", 75 | "shasum": "" 76 | }, 77 | "require": { 78 | "php": ">=5.4.0", 79 | "symfony/http-foundation": "~2.1", 80 | "symfony/http-kernel": "~2.1" 81 | }, 82 | "require-dev": { 83 | "phpunit/phpunit": "3.7.21", 84 | "silex/silex": "1.1.*@dev", 85 | "stack/builder": "~1.0@dev", 86 | "stack/callable-http-kernel": "~1.0@dev", 87 | "stack/inline": "~1.0@dev", 88 | "symfony/browser-kit": "~2.1" 89 | }, 90 | "type": "library", 91 | "extra": { 92 | "branch-alias": { 93 | "dev-master": "1.0-dev" 94 | } 95 | }, 96 | "autoload": { 97 | "psr-0": { 98 | "Dflydev\\Stack": "src" 99 | } 100 | }, 101 | "notification-url": "https://packagist.org/downloads/", 102 | "license": [ 103 | "MIT" 104 | ], 105 | "authors": [ 106 | { 107 | "name": "Dragonfly Development Inc.", 108 | "email": "info@dflydev.com", 109 | "homepage": "http://dflydev.com" 110 | }, 111 | { 112 | "name": "Beau Simensen", 113 | "email": "beau@dflydev.com", 114 | "homepage": "http://beausimensen.com" 115 | } 116 | ], 117 | "description": "STACK-2 Authentication Middlewares", 118 | "keywords": [ 119 | "stack", 120 | "stack-2" 121 | ], 122 | "time": "2013-08-02 03:57:47" 123 | }, 124 | { 125 | "name": "dflydev/stack-firewall", 126 | "version": "dev-master", 127 | "source": { 128 | "type": "git", 129 | "url": "https://github.com/dflydev/dflydev-stack-firewall.git", 130 | "reference": "e4800b85283ad91aac2f1218c0697825653584b2" 131 | }, 132 | "dist": { 133 | "type": "zip", 134 | "url": "https://api.github.com/repos/dflydev/dflydev-stack-firewall/zipball/e4800b85283ad91aac2f1218c0697825653584b2", 135 | "reference": "e4800b85283ad91aac2f1218c0697825653584b2", 136 | "shasum": "" 137 | }, 138 | "require": { 139 | "dflydev/stack-authentication": "1.0.*@dev", 140 | "php": ">=5.4.0", 141 | "symfony/http-foundation": "~2.1", 142 | "symfony/http-kernel": "~2.1" 143 | }, 144 | "require-dev": { 145 | "phpunit/phpunit": "3.7.21", 146 | "silex/silex": "1.1.*@dev", 147 | "stack/builder": "~1.0@dev", 148 | "stack/callable-http-kernel": "~1.0@dev", 149 | "stack/inline": "~1.0@dev", 150 | "symfony/browser-kit": "~2.1" 151 | }, 152 | "type": "library", 153 | "extra": { 154 | "branch-alias": { 155 | "dev-master": "1.0-dev" 156 | } 157 | }, 158 | "autoload": { 159 | "psr-0": { 160 | "Dflydev\\Stack": "src" 161 | } 162 | }, 163 | "notification-url": "https://packagist.org/downloads/", 164 | "license": [ 165 | "MIT" 166 | ], 167 | "authors": [ 168 | { 169 | "name": "Dragonfly Development Inc.", 170 | "email": "info@dflydev.com", 171 | "homepage": "http://dflydev.com" 172 | }, 173 | { 174 | "name": "Beau Simensen", 175 | "email": "beau@dflydev.com", 176 | "homepage": "http://beausimensen.com" 177 | } 178 | ], 179 | "description": "Firewall Stack middleware", 180 | "keywords": [ 181 | "stack", 182 | "stack-2" 183 | ], 184 | "time": "2013-08-02 03:57:05" 185 | }, 186 | { 187 | "name": "ircmaxell/random-lib", 188 | "version": "v1.0.0", 189 | "source": { 190 | "type": "git", 191 | "url": "https://github.com/ircmaxell/RandomLib.git", 192 | "reference": "v1.0.0" 193 | }, 194 | "dist": { 195 | "type": "zip", 196 | "url": "https://api.github.com/repos/ircmaxell/RandomLib/zipball/v1.0.0", 197 | "reference": "v1.0.0", 198 | "shasum": "" 199 | }, 200 | "require": { 201 | "ircmaxell/security-lib": "1.0.*@dev", 202 | "php": ">=5.3.2" 203 | }, 204 | "require-dev": { 205 | "mikey179/vfsstream": "1.1.*" 206 | }, 207 | "type": "library", 208 | "extra": { 209 | "branch-alias": { 210 | "dev-master": "1.0.x-dev" 211 | } 212 | }, 213 | "autoload": { 214 | "psr-0": { 215 | "RandomLib": "lib" 216 | } 217 | }, 218 | "notification-url": "https://packagist.org/downloads/", 219 | "license": [ 220 | "MIT" 221 | ], 222 | "authors": [ 223 | { 224 | "name": "Anthony Ferrara", 225 | "email": "ircmaxell@ircmaxell.com", 226 | "homepage": "http://blog.ircmaxell.com" 227 | } 228 | ], 229 | "description": "A Library For Generating Secure Random Numbers", 230 | "homepage": "https://github.com/ircmaxell/PHP-RandomLib", 231 | "keywords": [ 232 | "cryptography", 233 | "random", 234 | "random-numbers", 235 | "random-strings" 236 | ], 237 | "time": "2013-07-30 17:40:57" 238 | }, 239 | { 240 | "name": "ircmaxell/security-lib", 241 | "version": "1.0.0", 242 | "source": { 243 | "type": "git", 244 | "url": "https://github.com/ircmaxell/SecurityLib.git", 245 | "reference": "v1.0.0" 246 | }, 247 | "dist": { 248 | "type": "zip", 249 | "url": "https://api.github.com/repos/ircmaxell/SecurityLib/zipball/v1.0.0", 250 | "reference": "v1.0.0", 251 | "shasum": "" 252 | }, 253 | "require": { 254 | "php": ">=5.3.2" 255 | }, 256 | "require-dev": { 257 | "mikey179/vfsstream": "1.1.*" 258 | }, 259 | "type": "library", 260 | "autoload": { 261 | "psr-0": { 262 | "SecurityLib": "lib" 263 | } 264 | }, 265 | "notification-url": "https://packagist.org/downloads/", 266 | "license": [ 267 | "MIT" 268 | ], 269 | "authors": [ 270 | { 271 | "name": "Anthony Ferrara", 272 | "email": "ircmaxell@ircmaxell.com", 273 | "homepage": "http://blog.ircmaxell.com" 274 | } 275 | ], 276 | "description": "A Base Security Library", 277 | "homepage": "https://github.com/ircmaxell/PHP-SecurityLib", 278 | "time": "2013-04-30 18:00:34" 279 | }, 280 | { 281 | "name": "psr/log", 282 | "version": "1.0.0", 283 | "source": { 284 | "type": "git", 285 | "url": "https://github.com/php-fig/log", 286 | "reference": "1.0.0" 287 | }, 288 | "dist": { 289 | "type": "zip", 290 | "url": "https://github.com/php-fig/log/archive/1.0.0.zip", 291 | "reference": "1.0.0", 292 | "shasum": "" 293 | }, 294 | "type": "library", 295 | "autoload": { 296 | "psr-0": { 297 | "Psr\\Log\\": "" 298 | } 299 | }, 300 | "notification-url": "https://packagist.org/downloads/", 301 | "license": [ 302 | "MIT" 303 | ], 304 | "authors": [ 305 | { 306 | "name": "PHP-FIG", 307 | "homepage": "http://www.php-fig.org/" 308 | } 309 | ], 310 | "description": "Common interface for logging libraries", 311 | "keywords": [ 312 | "log", 313 | "psr", 314 | "psr-3" 315 | ], 316 | "time": "2012-12-21 11:40:51" 317 | }, 318 | { 319 | "name": "symfony/debug", 320 | "version": "v2.3.2", 321 | "target-dir": "Symfony/Component/Debug", 322 | "source": { 323 | "type": "git", 324 | "url": "https://github.com/symfony/Debug.git", 325 | "reference": "v2.3.2" 326 | }, 327 | "dist": { 328 | "type": "zip", 329 | "url": "https://api.github.com/repos/symfony/Debug/zipball/v2.3.2", 330 | "reference": "v2.3.2", 331 | "shasum": "" 332 | }, 333 | "require": { 334 | "php": ">=5.3.3" 335 | }, 336 | "require-dev": { 337 | "symfony/http-foundation": "~2.1", 338 | "symfony/http-kernel": "~2.1" 339 | }, 340 | "suggest": { 341 | "symfony/class-loader": "", 342 | "symfony/http-foundation": "", 343 | "symfony/http-kernel": "" 344 | }, 345 | "type": "library", 346 | "extra": { 347 | "branch-alias": { 348 | "dev-master": "2.3-dev" 349 | } 350 | }, 351 | "autoload": { 352 | "psr-0": { 353 | "Symfony\\Component\\Debug\\": "" 354 | } 355 | }, 356 | "notification-url": "https://packagist.org/downloads/", 357 | "license": [ 358 | "MIT" 359 | ], 360 | "authors": [ 361 | { 362 | "name": "Fabien Potencier", 363 | "email": "fabien@symfony.com" 364 | }, 365 | { 366 | "name": "Symfony Community", 367 | "homepage": "http://symfony.com/contributors" 368 | } 369 | ], 370 | "description": "Symfony Debug Component", 371 | "homepage": "http://symfony.com", 372 | "time": "2013-07-01 12:24:43" 373 | }, 374 | { 375 | "name": "symfony/event-dispatcher", 376 | "version": "v2.3.2", 377 | "target-dir": "Symfony/Component/EventDispatcher", 378 | "source": { 379 | "type": "git", 380 | "url": "https://github.com/symfony/EventDispatcher.git", 381 | "reference": "v2.3.2" 382 | }, 383 | "dist": { 384 | "type": "zip", 385 | "url": "https://api.github.com/repos/symfony/EventDispatcher/zipball/v2.3.2", 386 | "reference": "v2.3.2", 387 | "shasum": "" 388 | }, 389 | "require": { 390 | "php": ">=5.3.3" 391 | }, 392 | "require-dev": { 393 | "symfony/dependency-injection": "~2.0" 394 | }, 395 | "suggest": { 396 | "symfony/dependency-injection": "", 397 | "symfony/http-kernel": "" 398 | }, 399 | "type": "library", 400 | "extra": { 401 | "branch-alias": { 402 | "dev-master": "2.3-dev" 403 | } 404 | }, 405 | "autoload": { 406 | "psr-0": { 407 | "Symfony\\Component\\EventDispatcher\\": "" 408 | } 409 | }, 410 | "notification-url": "https://packagist.org/downloads/", 411 | "license": [ 412 | "MIT" 413 | ], 414 | "authors": [ 415 | { 416 | "name": "Fabien Potencier", 417 | "email": "fabien@symfony.com" 418 | }, 419 | { 420 | "name": "Symfony Community", 421 | "homepage": "http://symfony.com/contributors" 422 | } 423 | ], 424 | "description": "Symfony EventDispatcher Component", 425 | "homepage": "http://symfony.com", 426 | "time": "2013-05-13 14:36:40" 427 | }, 428 | { 429 | "name": "symfony/http-foundation", 430 | "version": "v2.3.2", 431 | "target-dir": "Symfony/Component/HttpFoundation", 432 | "source": { 433 | "type": "git", 434 | "url": "https://github.com/symfony/HttpFoundation.git", 435 | "reference": "v2.3.2" 436 | }, 437 | "dist": { 438 | "type": "zip", 439 | "url": "https://api.github.com/repos/symfony/HttpFoundation/zipball/v2.3.2", 440 | "reference": "v2.3.2", 441 | "shasum": "" 442 | }, 443 | "require": { 444 | "php": ">=5.3.3" 445 | }, 446 | "type": "library", 447 | "extra": { 448 | "branch-alias": { 449 | "dev-master": "2.3-dev" 450 | } 451 | }, 452 | "autoload": { 453 | "psr-0": { 454 | "Symfony\\Component\\HttpFoundation\\": "" 455 | }, 456 | "classmap": [ 457 | "Symfony/Component/HttpFoundation/Resources/stubs" 458 | ] 459 | }, 460 | "notification-url": "https://packagist.org/downloads/", 461 | "license": [ 462 | "MIT" 463 | ], 464 | "authors": [ 465 | { 466 | "name": "Fabien Potencier", 467 | "email": "fabien@symfony.com" 468 | }, 469 | { 470 | "name": "Symfony Community", 471 | "homepage": "http://symfony.com/contributors" 472 | } 473 | ], 474 | "description": "Symfony HttpFoundation Component", 475 | "homepage": "http://symfony.com", 476 | "time": "2013-07-17 05:57:53" 477 | }, 478 | { 479 | "name": "symfony/http-kernel", 480 | "version": "v2.3.2", 481 | "target-dir": "Symfony/Component/HttpKernel", 482 | "source": { 483 | "type": "git", 484 | "url": "https://github.com/symfony/HttpKernel.git", 485 | "reference": "v2.3.2" 486 | }, 487 | "dist": { 488 | "type": "zip", 489 | "url": "https://api.github.com/repos/symfony/HttpKernel/zipball/v2.3.2", 490 | "reference": "v2.3.2", 491 | "shasum": "" 492 | }, 493 | "require": { 494 | "php": ">=5.3.3", 495 | "psr/log": "~1.0", 496 | "symfony/debug": "~2.3", 497 | "symfony/event-dispatcher": "~2.1", 498 | "symfony/http-foundation": "~2.2" 499 | }, 500 | "require-dev": { 501 | "symfony/browser-kit": "2.2.*", 502 | "symfony/class-loader": "~2.1", 503 | "symfony/config": "~2.0", 504 | "symfony/console": "2.2.*", 505 | "symfony/dependency-injection": "~2.0", 506 | "symfony/finder": "~2.0", 507 | "symfony/process": "~2.0", 508 | "symfony/routing": "~2.2", 509 | "symfony/stopwatch": "~2.2" 510 | }, 511 | "suggest": { 512 | "symfony/browser-kit": "", 513 | "symfony/class-loader": "", 514 | "symfony/config": "", 515 | "symfony/console": "", 516 | "symfony/dependency-injection": "", 517 | "symfony/finder": "" 518 | }, 519 | "type": "library", 520 | "extra": { 521 | "branch-alias": { 522 | "dev-master": "2.3-dev" 523 | } 524 | }, 525 | "autoload": { 526 | "psr-0": { 527 | "Symfony\\Component\\HttpKernel\\": "" 528 | } 529 | }, 530 | "notification-url": "https://packagist.org/downloads/", 531 | "license": [ 532 | "MIT" 533 | ], 534 | "authors": [ 535 | { 536 | "name": "Fabien Potencier", 537 | "email": "fabien@symfony.com" 538 | }, 539 | { 540 | "name": "Symfony Community", 541 | "homepage": "http://symfony.com/contributors" 542 | } 543 | ], 544 | "description": "Symfony HttpKernel Component", 545 | "homepage": "http://symfony.com", 546 | "time": "2013-07-17 06:22:21" 547 | } 548 | ], 549 | "packages-dev": [ 550 | { 551 | "name": "phpunit/php-code-coverage", 552 | "version": "1.2.12", 553 | "source": { 554 | "type": "git", 555 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 556 | "reference": "1.2.12" 557 | }, 558 | "dist": { 559 | "type": "zip", 560 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1.2.12", 561 | "reference": "1.2.12", 562 | "shasum": "" 563 | }, 564 | "require": { 565 | "php": ">=5.3.3", 566 | "phpunit/php-file-iterator": ">=1.3.0@stable", 567 | "phpunit/php-text-template": ">=1.1.1@stable", 568 | "phpunit/php-token-stream": ">=1.1.3@stable" 569 | }, 570 | "require-dev": { 571 | "phpunit/phpunit": "3.7.*@dev" 572 | }, 573 | "suggest": { 574 | "ext-dom": "*", 575 | "ext-xdebug": ">=2.0.5" 576 | }, 577 | "type": "library", 578 | "extra": { 579 | "branch-alias": { 580 | "dev-master": "1.2.x-dev" 581 | } 582 | }, 583 | "autoload": { 584 | "classmap": [ 585 | "PHP/" 586 | ] 587 | }, 588 | "notification-url": "https://packagist.org/downloads/", 589 | "include-path": [ 590 | "" 591 | ], 592 | "license": [ 593 | "BSD-3-Clause" 594 | ], 595 | "authors": [ 596 | { 597 | "name": "Sebastian Bergmann", 598 | "email": "sb@sebastian-bergmann.de", 599 | "role": "lead" 600 | } 601 | ], 602 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 603 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 604 | "keywords": [ 605 | "coverage", 606 | "testing", 607 | "xunit" 608 | ], 609 | "time": "2013-07-06 06:26:16" 610 | }, 611 | { 612 | "name": "phpunit/php-file-iterator", 613 | "version": "1.3.3", 614 | "source": { 615 | "type": "git", 616 | "url": "git://github.com/sebastianbergmann/php-file-iterator.git", 617 | "reference": "1.3.3" 618 | }, 619 | "dist": { 620 | "type": "zip", 621 | "url": "https://github.com/sebastianbergmann/php-file-iterator/zipball/1.3.3", 622 | "reference": "1.3.3", 623 | "shasum": "" 624 | }, 625 | "require": { 626 | "php": ">=5.3.3" 627 | }, 628 | "type": "library", 629 | "autoload": { 630 | "classmap": [ 631 | "File/" 632 | ] 633 | }, 634 | "notification-url": "https://packagist.org/downloads/", 635 | "include-path": [ 636 | "" 637 | ], 638 | "license": [ 639 | "BSD-3-Clause" 640 | ], 641 | "authors": [ 642 | { 643 | "name": "Sebastian Bergmann", 644 | "email": "sb@sebastian-bergmann.de", 645 | "role": "lead" 646 | } 647 | ], 648 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 649 | "homepage": "http://www.phpunit.de/", 650 | "keywords": [ 651 | "filesystem", 652 | "iterator" 653 | ], 654 | "time": "2012-10-11 04:44:38" 655 | }, 656 | { 657 | "name": "phpunit/php-text-template", 658 | "version": "1.1.4", 659 | "source": { 660 | "type": "git", 661 | "url": "git://github.com/sebastianbergmann/php-text-template.git", 662 | "reference": "1.1.4" 663 | }, 664 | "dist": { 665 | "type": "zip", 666 | "url": "https://github.com/sebastianbergmann/php-text-template/zipball/1.1.4", 667 | "reference": "1.1.4", 668 | "shasum": "" 669 | }, 670 | "require": { 671 | "php": ">=5.3.3" 672 | }, 673 | "type": "library", 674 | "autoload": { 675 | "classmap": [ 676 | "Text/" 677 | ] 678 | }, 679 | "notification-url": "https://packagist.org/downloads/", 680 | "include-path": [ 681 | "" 682 | ], 683 | "license": [ 684 | "BSD-3-Clause" 685 | ], 686 | "authors": [ 687 | { 688 | "name": "Sebastian Bergmann", 689 | "email": "sb@sebastian-bergmann.de", 690 | "role": "lead" 691 | } 692 | ], 693 | "description": "Simple template engine.", 694 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 695 | "keywords": [ 696 | "template" 697 | ], 698 | "time": "2012-10-31 11:15:28" 699 | }, 700 | { 701 | "name": "phpunit/php-timer", 702 | "version": "1.0.4", 703 | "source": { 704 | "type": "git", 705 | "url": "git://github.com/sebastianbergmann/php-timer.git", 706 | "reference": "1.0.4" 707 | }, 708 | "dist": { 709 | "type": "zip", 710 | "url": "https://github.com/sebastianbergmann/php-timer/zipball/1.0.4", 711 | "reference": "1.0.4", 712 | "shasum": "" 713 | }, 714 | "require": { 715 | "php": ">=5.3.3" 716 | }, 717 | "type": "library", 718 | "autoload": { 719 | "classmap": [ 720 | "PHP/" 721 | ] 722 | }, 723 | "notification-url": "https://packagist.org/downloads/", 724 | "include-path": [ 725 | "" 726 | ], 727 | "license": [ 728 | "BSD-3-Clause" 729 | ], 730 | "authors": [ 731 | { 732 | "name": "Sebastian Bergmann", 733 | "email": "sb@sebastian-bergmann.de", 734 | "role": "lead" 735 | } 736 | ], 737 | "description": "Utility class for timing", 738 | "homepage": "http://www.phpunit.de/", 739 | "keywords": [ 740 | "timer" 741 | ], 742 | "time": "2012-10-11 04:45:58" 743 | }, 744 | { 745 | "name": "phpunit/php-token-stream", 746 | "version": "1.1.7", 747 | "source": { 748 | "type": "git", 749 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 750 | "reference": "1.1.7" 751 | }, 752 | "dist": { 753 | "type": "zip", 754 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/1.1.7", 755 | "reference": "1.1.7", 756 | "shasum": "" 757 | }, 758 | "require": { 759 | "ext-tokenizer": "*", 760 | "php": ">=5.3.3" 761 | }, 762 | "type": "library", 763 | "autoload": { 764 | "classmap": [ 765 | "PHP/" 766 | ] 767 | }, 768 | "notification-url": "https://packagist.org/downloads/", 769 | "include-path": [ 770 | "" 771 | ], 772 | "license": [ 773 | "BSD-3-Clause" 774 | ], 775 | "authors": [ 776 | { 777 | "name": "Sebastian Bergmann", 778 | "email": "sb@sebastian-bergmann.de", 779 | "role": "lead" 780 | } 781 | ], 782 | "description": "Wrapper around PHP's tokenizer extension.", 783 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 784 | "keywords": [ 785 | "tokenizer" 786 | ], 787 | "time": "2013-07-29 14:27:06" 788 | }, 789 | { 790 | "name": "phpunit/phpunit", 791 | "version": "3.7.21", 792 | "source": { 793 | "type": "git", 794 | "url": "https://github.com/sebastianbergmann/phpunit.git", 795 | "reference": "3.7.21" 796 | }, 797 | "dist": { 798 | "type": "zip", 799 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/3.7.21", 800 | "reference": "3.7.21", 801 | "shasum": "" 802 | }, 803 | "require": { 804 | "ext-dom": "*", 805 | "ext-pcre": "*", 806 | "ext-reflection": "*", 807 | "ext-spl": "*", 808 | "php": ">=5.3.3", 809 | "phpunit/php-code-coverage": ">=1.2.1,<1.3.0", 810 | "phpunit/php-file-iterator": ">=1.3.1", 811 | "phpunit/php-text-template": ">=1.1.1", 812 | "phpunit/php-timer": ">=1.0.2,<1.1.0", 813 | "phpunit/phpunit-mock-objects": ">=1.2.0,<1.3.0", 814 | "symfony/yaml": ">=2.0,<3.0" 815 | }, 816 | "require-dev": { 817 | "pear-pear/pear": "1.9.4" 818 | }, 819 | "suggest": { 820 | "ext-json": "*", 821 | "ext-simplexml": "*", 822 | "ext-tokenizer": "*", 823 | "phpunit/php-invoker": ">=1.1.0,<1.2.0" 824 | }, 825 | "bin": [ 826 | "composer/bin/phpunit" 827 | ], 828 | "type": "library", 829 | "extra": { 830 | "branch-alias": { 831 | "dev-master": "3.7.x-dev" 832 | } 833 | }, 834 | "autoload": { 835 | "classmap": [ 836 | "PHPUnit/" 837 | ] 838 | }, 839 | "notification-url": "https://packagist.org/downloads/", 840 | "include-path": [ 841 | "", 842 | "../../symfony/yaml/" 843 | ], 844 | "license": [ 845 | "BSD-3-Clause" 846 | ], 847 | "authors": [ 848 | { 849 | "name": "Sebastian Bergmann", 850 | "email": "sebastian@phpunit.de", 851 | "role": "lead" 852 | } 853 | ], 854 | "description": "The PHP Unit Testing framework.", 855 | "homepage": "http://www.phpunit.de/", 856 | "keywords": [ 857 | "phpunit", 858 | "testing", 859 | "xunit" 860 | ], 861 | "time": "2013-05-23 18:54:29" 862 | }, 863 | { 864 | "name": "phpunit/phpunit-mock-objects", 865 | "version": "1.2.3", 866 | "source": { 867 | "type": "git", 868 | "url": "git://github.com/sebastianbergmann/phpunit-mock-objects.git", 869 | "reference": "1.2.3" 870 | }, 871 | "dist": { 872 | "type": "zip", 873 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects/archive/1.2.3.zip", 874 | "reference": "1.2.3", 875 | "shasum": "" 876 | }, 877 | "require": { 878 | "php": ">=5.3.3", 879 | "phpunit/php-text-template": ">=1.1.1@stable" 880 | }, 881 | "suggest": { 882 | "ext-soap": "*" 883 | }, 884 | "type": "library", 885 | "autoload": { 886 | "classmap": [ 887 | "PHPUnit/" 888 | ] 889 | }, 890 | "notification-url": "https://packagist.org/downloads/", 891 | "include-path": [ 892 | "" 893 | ], 894 | "license": [ 895 | "BSD-3-Clause" 896 | ], 897 | "authors": [ 898 | { 899 | "name": "Sebastian Bergmann", 900 | "email": "sb@sebastian-bergmann.de", 901 | "role": "lead" 902 | } 903 | ], 904 | "description": "Mock Object library for PHPUnit", 905 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 906 | "keywords": [ 907 | "mock", 908 | "xunit" 909 | ], 910 | "time": "2013-01-13 10:24:48" 911 | }, 912 | { 913 | "name": "pimple/pimple", 914 | "version": "v1.0.2", 915 | "source": { 916 | "type": "git", 917 | "url": "https://github.com/fabpot/Pimple.git", 918 | "reference": "v1.0.2" 919 | }, 920 | "dist": { 921 | "type": "zip", 922 | "url": "https://api.github.com/repos/fabpot/Pimple/zipball/v1.0.2", 923 | "reference": "v1.0.2", 924 | "shasum": "" 925 | }, 926 | "require": { 927 | "php": ">=5.3.0" 928 | }, 929 | "type": "library", 930 | "extra": { 931 | "branch-alias": { 932 | "dev-master": "1.0.x-dev" 933 | } 934 | }, 935 | "autoload": { 936 | "psr-0": { 937 | "Pimple": "lib/" 938 | } 939 | }, 940 | "notification-url": "https://packagist.org/downloads/", 941 | "license": [ 942 | "MIT" 943 | ], 944 | "authors": [ 945 | { 946 | "name": "Fabien Potencier", 947 | "email": "fabien@symfony.com" 948 | } 949 | ], 950 | "description": "Pimple is a simple Dependency Injection Container for PHP 5.3", 951 | "homepage": "http://pimple.sensiolabs.org", 952 | "keywords": [ 953 | "container", 954 | "dependency injection" 955 | ], 956 | "time": "2013-03-08 08:21:40" 957 | }, 958 | { 959 | "name": "silex/silex", 960 | "version": "dev-master", 961 | "source": { 962 | "type": "git", 963 | "url": "https://github.com/fabpot/Silex.git", 964 | "reference": "e9bedd65873d6e20e2dd9eee9cde4723f10bcdb0" 965 | }, 966 | "dist": { 967 | "type": "zip", 968 | "url": "https://api.github.com/repos/fabpot/Silex/zipball/e9bedd65873d6e20e2dd9eee9cde4723f10bcdb0", 969 | "reference": "e9bedd65873d6e20e2dd9eee9cde4723f10bcdb0", 970 | "shasum": "" 971 | }, 972 | "require": { 973 | "php": ">=5.3.3", 974 | "pimple/pimple": "1.*", 975 | "symfony/event-dispatcher": ">=2.3,<2.4-dev", 976 | "symfony/http-foundation": ">=2.3,<2.4-dev", 977 | "symfony/http-kernel": ">=2.3,<2.4-dev", 978 | "symfony/routing": ">=2.3,<2.4-dev" 979 | }, 980 | "require-dev": { 981 | "doctrine/dbal": ">=2.2.0,<2.4.0-dev", 982 | "monolog/monolog": "~1.4,>=1.4.1", 983 | "phpunit/phpunit": "~3.7", 984 | "swiftmailer/swiftmailer": "5.*", 985 | "symfony/browser-kit": ">=2.3,<2.4-dev", 986 | "symfony/config": ">=2.3,<2.4-dev", 987 | "symfony/css-selector": ">=2.3,<2.4-dev", 988 | "symfony/dom-crawler": ">=2.3,<2.4-dev", 989 | "symfony/finder": ">=2.3,<2.4-dev", 990 | "symfony/form": ">=2.3,<2.4-dev", 991 | "symfony/locale": ">=2.3,<2.4-dev", 992 | "symfony/monolog-bridge": ">=2.3,<2.4-dev", 993 | "symfony/options-resolver": ">=2.3,<2.4-dev", 994 | "symfony/process": ">=2.3,<2.4-dev", 995 | "symfony/security": ">=2.3,<2.4-dev", 996 | "symfony/serializer": ">=2.3,<2.4-dev", 997 | "symfony/translation": ">=2.3,<2.4-dev", 998 | "symfony/twig-bridge": ">=2.3,<2.4-dev", 999 | "symfony/validator": ">=2.3,<2.4-dev", 1000 | "twig/twig": ">=1.8.0,<2.0-dev" 1001 | }, 1002 | "suggest": { 1003 | "symfony/browser-kit": ">=2.3,<2.4-dev", 1004 | "symfony/css-selector": ">=2.3,<2.4-dev", 1005 | "symfony/dom-crawler": ">=2.3,<2.4-dev", 1006 | "symfony/form": ">=2.3,<2.4-dev" 1007 | }, 1008 | "type": "library", 1009 | "extra": { 1010 | "branch-alias": { 1011 | "dev-master": "1.1.x-dev" 1012 | } 1013 | }, 1014 | "autoload": { 1015 | "psr-0": { 1016 | "Silex": "src/" 1017 | } 1018 | }, 1019 | "notification-url": "https://packagist.org/downloads/", 1020 | "license": [ 1021 | "MIT" 1022 | ], 1023 | "authors": [ 1024 | { 1025 | "name": "Fabien Potencier", 1026 | "email": "fabien@symfony.com" 1027 | }, 1028 | { 1029 | "name": "Igor Wiedler", 1030 | "email": "igor@wiedler.ch", 1031 | "homepage": "http://wiedler.ch/igor/" 1032 | } 1033 | ], 1034 | "description": "The PHP micro-framework based on the Symfony2 Components", 1035 | "homepage": "http://silex.sensiolabs.org", 1036 | "keywords": [ 1037 | "microframework" 1038 | ], 1039 | "time": "2013-07-27 05:29:38" 1040 | }, 1041 | { 1042 | "name": "stack/builder", 1043 | "version": "dev-master", 1044 | "source": { 1045 | "type": "git", 1046 | "url": "https://github.com/stackphp/builder.git", 1047 | "reference": "d438b2aeb1f3dfa2c16497c68b7016ab7998748d" 1048 | }, 1049 | "dist": { 1050 | "type": "zip", 1051 | "url": "https://api.github.com/repos/stackphp/builder/zipball/d438b2aeb1f3dfa2c16497c68b7016ab7998748d", 1052 | "reference": "d438b2aeb1f3dfa2c16497c68b7016ab7998748d", 1053 | "shasum": "" 1054 | }, 1055 | "require": { 1056 | "php": ">=5.4.0", 1057 | "symfony/http-foundation": "~2.1", 1058 | "symfony/http-kernel": "~2.1" 1059 | }, 1060 | "require-dev": { 1061 | "silex/silex": "1.0.*@dev" 1062 | }, 1063 | "type": "library", 1064 | "extra": { 1065 | "branch-alias": { 1066 | "dev-master": "1.0-dev" 1067 | } 1068 | }, 1069 | "autoload": { 1070 | "psr-0": { 1071 | "Stack": "src" 1072 | } 1073 | }, 1074 | "notification-url": "https://packagist.org/downloads/", 1075 | "license": [ 1076 | "MIT" 1077 | ], 1078 | "authors": [ 1079 | { 1080 | "name": "Igor Wiedler", 1081 | "email": "igor@wiedler.ch", 1082 | "homepage": "http://wiedler.ch/igor/" 1083 | } 1084 | ], 1085 | "description": "Builder for stack middlewares based on HttpKernelInterface.", 1086 | "keywords": [ 1087 | "stack" 1088 | ], 1089 | "time": "2013-04-29 12:20:08" 1090 | }, 1091 | { 1092 | "name": "stack/callable-http-kernel", 1093 | "version": "dev-master", 1094 | "source": { 1095 | "type": "git", 1096 | "url": "https://github.com/stackphp/CallableHttpKernel.git", 1097 | "reference": "2cea2eab2c3a618bd378f1a2fa05917cf934b518" 1098 | }, 1099 | "dist": { 1100 | "type": "zip", 1101 | "url": "https://api.github.com/repos/stackphp/CallableHttpKernel/zipball/2cea2eab2c3a618bd378f1a2fa05917cf934b518", 1102 | "reference": "2cea2eab2c3a618bd378f1a2fa05917cf934b518", 1103 | "shasum": "" 1104 | }, 1105 | "require": { 1106 | "php": ">=5.4.0", 1107 | "symfony/http-foundation": "~2.1", 1108 | "symfony/http-kernel": "~2.1" 1109 | }, 1110 | "type": "library", 1111 | "extra": { 1112 | "branch-alias": { 1113 | "dev-master": "1.0-dev" 1114 | } 1115 | }, 1116 | "autoload": { 1117 | "psr-0": { 1118 | "Stack": "src" 1119 | } 1120 | }, 1121 | "notification-url": "https://packagist.org/downloads/", 1122 | "license": [ 1123 | "MIT" 1124 | ], 1125 | "authors": [ 1126 | { 1127 | "name": "Igor Wiedler", 1128 | "email": "igor@wiedler.ch", 1129 | "homepage": "http://wiedler.ch/igor/" 1130 | } 1131 | ], 1132 | "description": "HttpKernelInterface implementation based on callables.", 1133 | "keywords": [ 1134 | "stack" 1135 | ], 1136 | "time": "2013-05-17 16:07:54" 1137 | }, 1138 | { 1139 | "name": "stack/inline", 1140 | "version": "dev-master", 1141 | "source": { 1142 | "type": "git", 1143 | "url": "https://github.com/stackphp/inline.git", 1144 | "reference": "31d8ca1efaa8680c20bc3229e441c8733ce84899" 1145 | }, 1146 | "dist": { 1147 | "type": "zip", 1148 | "url": "https://api.github.com/repos/stackphp/inline/zipball/31d8ca1efaa8680c20bc3229e441c8733ce84899", 1149 | "reference": "31d8ca1efaa8680c20bc3229e441c8733ce84899", 1150 | "shasum": "" 1151 | }, 1152 | "require": { 1153 | "php": ">=5.4.0", 1154 | "symfony/http-foundation": "~2.1", 1155 | "symfony/http-kernel": "~2.1" 1156 | }, 1157 | "require-dev": { 1158 | "silex/silex": "~1.0@dev", 1159 | "stack/builder": "~1.0@dev", 1160 | "stack/callable-http-kernel": "~1.0@dev" 1161 | }, 1162 | "type": "library", 1163 | "extra": { 1164 | "branch-alias": { 1165 | "dev-master": "1.0-dev" 1166 | } 1167 | }, 1168 | "autoload": { 1169 | "psr-0": { 1170 | "Stack": "src" 1171 | } 1172 | }, 1173 | "notification-url": "https://packagist.org/downloads/", 1174 | "license": [ 1175 | "MIT" 1176 | ], 1177 | "authors": [ 1178 | { 1179 | "name": "Igor Wiedler", 1180 | "email": "igor@wiedler.ch", 1181 | "homepage": "http://wiedler.ch/igor/" 1182 | } 1183 | ], 1184 | "description": "Inline stack middleware.", 1185 | "keywords": [ 1186 | "callable", 1187 | "inline", 1188 | "stack" 1189 | ], 1190 | "time": "2013-04-09 15:57:37" 1191 | }, 1192 | { 1193 | "name": "symfony/browser-kit", 1194 | "version": "v2.3.2", 1195 | "target-dir": "Symfony/Component/BrowserKit", 1196 | "source": { 1197 | "type": "git", 1198 | "url": "https://github.com/symfony/BrowserKit.git", 1199 | "reference": "v2.3.2" 1200 | }, 1201 | "dist": { 1202 | "type": "zip", 1203 | "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/v2.3.2", 1204 | "reference": "v2.3.2", 1205 | "shasum": "" 1206 | }, 1207 | "require": { 1208 | "php": ">=5.3.3", 1209 | "symfony/dom-crawler": "~2.0" 1210 | }, 1211 | "require-dev": { 1212 | "symfony/css-selector": "~2.0", 1213 | "symfony/process": "~2.0" 1214 | }, 1215 | "suggest": { 1216 | "symfony/process": "" 1217 | }, 1218 | "type": "library", 1219 | "extra": { 1220 | "branch-alias": { 1221 | "dev-master": "2.3-dev" 1222 | } 1223 | }, 1224 | "autoload": { 1225 | "psr-0": { 1226 | "Symfony\\Component\\BrowserKit\\": "" 1227 | } 1228 | }, 1229 | "notification-url": "https://packagist.org/downloads/", 1230 | "license": [ 1231 | "MIT" 1232 | ], 1233 | "authors": [ 1234 | { 1235 | "name": "Fabien Potencier", 1236 | "email": "fabien@symfony.com" 1237 | }, 1238 | { 1239 | "name": "Symfony Community", 1240 | "homepage": "http://symfony.com/contributors" 1241 | } 1242 | ], 1243 | "description": "Symfony BrowserKit Component", 1244 | "homepage": "http://symfony.com", 1245 | "time": "2013-07-07 15:48:29" 1246 | }, 1247 | { 1248 | "name": "symfony/dom-crawler", 1249 | "version": "v2.3.2", 1250 | "target-dir": "Symfony/Component/DomCrawler", 1251 | "source": { 1252 | "type": "git", 1253 | "url": "https://github.com/symfony/DomCrawler.git", 1254 | "reference": "v2.3.2" 1255 | }, 1256 | "dist": { 1257 | "type": "zip", 1258 | "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/v2.3.2", 1259 | "reference": "v2.3.2", 1260 | "shasum": "" 1261 | }, 1262 | "require": { 1263 | "php": ">=5.3.3" 1264 | }, 1265 | "require-dev": { 1266 | "symfony/css-selector": "~2.0" 1267 | }, 1268 | "suggest": { 1269 | "symfony/css-selector": "" 1270 | }, 1271 | "type": "library", 1272 | "extra": { 1273 | "branch-alias": { 1274 | "dev-master": "2.3-dev" 1275 | } 1276 | }, 1277 | "autoload": { 1278 | "psr-0": { 1279 | "Symfony\\Component\\DomCrawler\\": "" 1280 | } 1281 | }, 1282 | "notification-url": "https://packagist.org/downloads/", 1283 | "license": [ 1284 | "MIT" 1285 | ], 1286 | "authors": [ 1287 | { 1288 | "name": "Fabien Potencier", 1289 | "email": "fabien@symfony.com" 1290 | }, 1291 | { 1292 | "name": "Symfony Community", 1293 | "homepage": "http://symfony.com/contributors" 1294 | } 1295 | ], 1296 | "description": "Symfony DomCrawler Component", 1297 | "homepage": "http://symfony.com", 1298 | "time": "2013-07-01 12:24:43" 1299 | }, 1300 | { 1301 | "name": "symfony/routing", 1302 | "version": "v2.3.2", 1303 | "target-dir": "Symfony/Component/Routing", 1304 | "source": { 1305 | "type": "git", 1306 | "url": "https://github.com/symfony/Routing.git", 1307 | "reference": "v2.3.2" 1308 | }, 1309 | "dist": { 1310 | "type": "zip", 1311 | "url": "https://api.github.com/repos/symfony/Routing/zipball/v2.3.2", 1312 | "reference": "v2.3.2", 1313 | "shasum": "" 1314 | }, 1315 | "require": { 1316 | "php": ">=5.3.3" 1317 | }, 1318 | "require-dev": { 1319 | "doctrine/common": "~2.2", 1320 | "psr/log": "~1.0", 1321 | "symfony/config": "~2.2", 1322 | "symfony/yaml": "~2.0" 1323 | }, 1324 | "suggest": { 1325 | "doctrine/common": "", 1326 | "symfony/config": "", 1327 | "symfony/yaml": "" 1328 | }, 1329 | "type": "library", 1330 | "extra": { 1331 | "branch-alias": { 1332 | "dev-master": "2.3-dev" 1333 | } 1334 | }, 1335 | "autoload": { 1336 | "psr-0": { 1337 | "Symfony\\Component\\Routing\\": "" 1338 | } 1339 | }, 1340 | "notification-url": "https://packagist.org/downloads/", 1341 | "license": [ 1342 | "MIT" 1343 | ], 1344 | "authors": [ 1345 | { 1346 | "name": "Fabien Potencier", 1347 | "email": "fabien@symfony.com" 1348 | }, 1349 | { 1350 | "name": "Symfony Community", 1351 | "homepage": "http://symfony.com/contributors" 1352 | } 1353 | ], 1354 | "description": "Symfony Routing Component", 1355 | "homepage": "http://symfony.com", 1356 | "time": "2013-06-23 08:16:02" 1357 | }, 1358 | { 1359 | "name": "symfony/yaml", 1360 | "version": "v2.3.2", 1361 | "target-dir": "Symfony/Component/Yaml", 1362 | "source": { 1363 | "type": "git", 1364 | "url": "https://github.com/symfony/Yaml.git", 1365 | "reference": "v2.3.2" 1366 | }, 1367 | "dist": { 1368 | "type": "zip", 1369 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/v2.3.2", 1370 | "reference": "v2.3.2", 1371 | "shasum": "" 1372 | }, 1373 | "require": { 1374 | "php": ">=5.3.3" 1375 | }, 1376 | "type": "library", 1377 | "extra": { 1378 | "branch-alias": { 1379 | "dev-master": "2.3-dev" 1380 | } 1381 | }, 1382 | "autoload": { 1383 | "psr-0": { 1384 | "Symfony\\Component\\Yaml\\": "" 1385 | } 1386 | }, 1387 | "notification-url": "https://packagist.org/downloads/", 1388 | "license": [ 1389 | "MIT" 1390 | ], 1391 | "authors": [ 1392 | { 1393 | "name": "Fabien Potencier", 1394 | "email": "fabien@symfony.com" 1395 | }, 1396 | { 1397 | "name": "Symfony Community", 1398 | "homepage": "http://symfony.com/contributors" 1399 | } 1400 | ], 1401 | "description": "Symfony Yaml Component", 1402 | "homepage": "http://symfony.com", 1403 | "time": "2013-07-11 19:36:36" 1404 | } 1405 | ], 1406 | "aliases": [ 1407 | 1408 | ], 1409 | "minimum-stability": "stable", 1410 | "stability-flags": { 1411 | "dflydev/hawk": 20, 1412 | "dflydev/stack-authentication": 20, 1413 | "dflydev/stack-firewall": 20, 1414 | "silex/silex": 20, 1415 | "stack/builder": 20, 1416 | "stack/callable-http-kernel": 20, 1417 | "stack/inline": 20 1418 | }, 1419 | "platform": { 1420 | "php": ">=5.4.0" 1421 | }, 1422 | "platform-dev": [ 1423 | 1424 | ] 1425 | } 1426 | --------------------------------------------------------------------------------