From 98566b6243a468495d8661f4f99fabd5ae670ac0 Mon Sep 17 00:00:00 2001 From: leo108 Date: Mon, 24 Oct 2016 13:29:07 +0800 Subject: [PATCH 01/17] optimize code structure --- .gitignore | 4 +- composer.json | 3 +- .../AuthenticationFailureResponse.php | 19 ++ .../AuthenticationSuccessResponse.php | 36 +++ src/Contracts/Responses/BaseResponse.php | 19 ++ src/Http/Controllers/ValidateController.php | 118 +++----- .../JsonAuthenticationFailureResponse.php | 49 ++++ .../JsonAuthenticationSuccessResponse.php | 64 +++++ .../XmlAuthenticationFailureResponse.php | 68 +++++ .../XmlAuthenticationSuccessResponse.php | 101 +++++++ src/Traits/XmlResponse.php | 72 +++++ .../Controllers/ValidateControllerTest.php | 254 +++++------------- .../XmlAuthenticationSuccessResponseTest.php | 103 +++++++ tests/TestCase.php | 9 + tests/Traits/XmlResponseTest.php | 89 ++++++ tests/_support/TestXmlResponseTrait.php | 12 + 16 files changed, 741 insertions(+), 279 deletions(-) create mode 100644 src/Contracts/Responses/AuthenticationFailureResponse.php create mode 100644 src/Contracts/Responses/AuthenticationSuccessResponse.php create mode 100644 src/Contracts/Responses/BaseResponse.php create mode 100644 src/Responses/JsonAuthenticationFailureResponse.php create mode 100644 src/Responses/JsonAuthenticationSuccessResponse.php create mode 100644 src/Responses/XmlAuthenticationFailureResponse.php create mode 100644 src/Responses/XmlAuthenticationSuccessResponse.php create mode 100644 src/Traits/XmlResponse.php create mode 100644 tests/Responses/XmlAuthenticationSuccessResponseTest.php create mode 100644 tests/Traits/XmlResponseTest.php create mode 100644 tests/_support/TestXmlResponseTrait.php diff --git a/.gitignore b/.gitignore index 3d332f8..3a6443a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ /vendor/ composer.lock -.idea \ No newline at end of file +.idea +.DS_Store +coverage diff --git a/composer.json b/composer.json index 41735f5..6838710 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,8 @@ "classmap": [ "tests/TestCase.php", "tests/_support/User.php", - "tests/_support/SerializableModel.php" + "tests/_support/SerializableModel.php", + "tests/_support/TestXmlResponseTrait.php" ] } } diff --git a/src/Contracts/Responses/AuthenticationFailureResponse.php b/src/Contracts/Responses/AuthenticationFailureResponse.php new file mode 100644 index 0000000..4de3c24 --- /dev/null +++ b/src/Contracts/Responses/AuthenticationFailureResponse.php @@ -0,0 +1,19 @@ +user->getCASAttributes() : []; - return $this->successResponse($record->user->getName(), $attr, $format); + return $this->successResponse($record->user->getName(), $format, $attr); } /** - * @param string $username - * @param array $attrs - * @param string $format + * @param string $username + * @param string $format + * @param array $attributes + * @param array $proxies + * @param string|null $pgt * @return Response */ - protected function successResponse($username, $attrs, $format) + protected function successResponse($username, $format, $attributes, $proxies = [], $pgt = null) { if (strtoupper($format) === 'JSON') { - $data = [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => $username, - ], - ], - ]; - - if (!empty($attrs)) { - $data['serviceResponse']['authenticationSuccess']['attributes'] = $attrs; - } - - return new Response($data); + $resp = app(JsonAuthenticationSuccessResponse::class); } else { - $xml = simplexml_load_string(self::BASE_XML); - $childSuccess = $xml->addChild('cas:authenticationSuccess'); - $childSuccess->addChild('cas:user', $username); - - if (!empty($attrs)) { - $childAttrs = $childSuccess->addChild('cas:attributes'); - foreach ($attrs as $key => $value) { - if (is_string($value)) { - $str = $value; - } else if (is_object($value) && method_exists($value, '__toString')) { - $str = $value->__toString(); - } else if ($value instanceof \Serializable) { - $str = serialize($value); - } else { - //array or object that doesn't have __toString method - //json_encode will return false if encode failed - $str = json_encode($value); - } - - if (is_string($str)) { - $childAttrs->addChild('cas:'.$key, $str); - } - } - } + $resp = app(XmlAuthenticationSuccessResponse::class); + } + $resp->setUser($username); + if (!empty($attributes)) { + $resp->setAttributes($attributes); + } + if (!empty($proxies)) { + $resp->setProxies($proxies); + } - return $this->returnXML($xml); + if (is_string($pgt)) { + $resp->setProxyGrantingTicket($pgt); } + + return $resp->toResponse(); } /** * @param string $code - * @param string $desc + * @param string $description * @param string $format * @return Response */ - protected function failureResponse($code, $desc, $format) + protected function failureResponse($code, $description, $format) { if (strtoupper($format) === 'JSON') { - return new Response( - [ - 'serviceResponse' => [ - 'authenticationFailure' => [ - 'code' => $code, - 'description' => $desc, - ], - ], - ] - ); + $resp = app(JsonAuthenticationFailureResponse::class); } else { - $xml = simplexml_load_string(self::BASE_XML); - $childFailure = $xml->addChild('cas:authenticationFailure', $desc); - $childFailure->addAttribute('code', $code); - - return $this->returnXML($xml); + $resp = app(XmlAuthenticationFailureResponse::class); } + $resp->setFailure($code, $description); + + return $resp->toResponse(); } /** @@ -218,28 +190,4 @@ protected function unlockTicket($ticket) { return $this->ticketLocker->releaseLock($ticket); } - - /** - * remove the first line of xml string - * @param string $str - * @return string - */ - protected function removeXmlFirstLine($str) - { - $first = ''; - if (Str::startsWith($str, $first)) { - return trim(substr($str, strlen($first))); - } - - return $str; - } - - /** - * @param SimpleXMLElement $xml - * @return Response - */ - protected function returnXML(SimpleXMLElement $xml) - { - return new Response($this->removeXmlFirstLine($xml->asXML()), 200, array('Content-Type' => 'application/xml')); - } -} \ No newline at end of file +} diff --git a/src/Responses/JsonAuthenticationFailureResponse.php b/src/Responses/JsonAuthenticationFailureResponse.php new file mode 100644 index 0000000..781978a --- /dev/null +++ b/src/Responses/JsonAuthenticationFailureResponse.php @@ -0,0 +1,49 @@ +data = ['serviceResponse' => ['authenticationFailure' => []]]; + } + + /** + * @param string $code + * @param string $description + * @return $this + */ + public function setFailure($code, $description) + { + $this->data['serviceResponse']['authenticationFailure']['code'] = $code; + $this->data['serviceResponse']['authenticationFailure']['description'] = $description; + + return $this; + } + + /** + * @return Response + */ + public function toResponse() + { + return new Response($this->data); + } +} diff --git a/src/Responses/JsonAuthenticationSuccessResponse.php b/src/Responses/JsonAuthenticationSuccessResponse.php new file mode 100644 index 0000000..3fac6d1 --- /dev/null +++ b/src/Responses/JsonAuthenticationSuccessResponse.php @@ -0,0 +1,64 @@ +data = ['serviceResponse' => ['authenticationSuccess' => []]]; + } + + public function setUser($user) + { + $this->data['serviceResponse']['authenticationSuccess']['user'] = $user; + + return $this; + } + + public function setProxies($proxies) + { + $this->data['serviceResponse']['authenticationSuccess']['proxies'] = $proxies; + + return $this; + } + + public function setAttributes($attributes) + { + $this->data['serviceResponse']['authenticationSuccess']['attributes'] = $attributes; + + return $this; + } + + public function setProxyGrantingTicket($ticket) + { + $this->data['serviceResponse']['authenticationSuccess']['proxyGrantingTicket'] = $ticket; + + return $this; + } + + /** + * @return Response + */ + public function toResponse() + { + return new Response($this->data); + } +} diff --git a/src/Responses/XmlAuthenticationFailureResponse.php b/src/Responses/XmlAuthenticationFailureResponse.php new file mode 100644 index 0000000..68a4726 --- /dev/null +++ b/src/Responses/XmlAuthenticationFailureResponse.php @@ -0,0 +1,68 @@ +node = $this->getRootNode(); + } + + /** + * @param string $code + * @param string $description + * @return $this + */ + public function setFailure($code, $description) + { + $this->removeByXPath($this->node, 'cas:authenticationFailure'); + $authNode = $this->node->addChild('cas:authenticationFailure', $description); + $authNode->addAttribute('code', $code); + + return $this; + } + + /** + * @return Response + */ + public function toResponse() + { + $content = $this->removeXmlFirstLine($this->node->asXML()); + + return new Response($content, 200, array('Content-Type' => 'application/xml')); + } + + /** + * @return SimpleXMLElement + */ + protected function getAuthNode() + { + $authNodes = $this->node->xpath('cas:authenticationFailure'); + if (count($authNodes) < 1) { + return $this->node->addChild('cas:authenticationFailure'); + } + + return $authNodes[0]; + } +} \ No newline at end of file diff --git a/src/Responses/XmlAuthenticationSuccessResponse.php b/src/Responses/XmlAuthenticationSuccessResponse.php new file mode 100644 index 0000000..2302421 --- /dev/null +++ b/src/Responses/XmlAuthenticationSuccessResponse.php @@ -0,0 +1,101 @@ +node = $this->getRootNode(); + $this->node->addChild('cas:authenticationSuccess'); + } + + public function setUser($user) + { + $authNode = $this->getAuthNode(); + $this->removeByXPath($authNode, 'cas:user'); + $authNode->addChild('cas:user', $user); + + return $this; + } + + public function setProxies($proxies) + { + $authNode = $this->getAuthNode(); + $this->removeByXPath($authNode, 'cas:proxies'); + $proxiesNode = $authNode->addChild('cas:proxies'); + foreach ($proxies as $proxy) { + $proxiesNode->addChild('cas:proxy', $proxy); + } + + return $this; + } + + public function setAttributes($attributes) + { + $authNode = $this->getAuthNode(); + $this->removeByXPath($authNode, 'cas:attributes'); + $attributesNode = $authNode->addChild('cas:attributes'); + foreach ($attributes as $key => $value) { + $str = $this->stringify($value); + if (is_string($str)) { + $attributesNode->addChild('cas:'.$key, $str); + } + } + + return $this; + } + + public function setProxyGrantingTicket($ticket) + { + $authNode = $this->getAuthNode(); + $this->removeByXPath($authNode, 'cas:proxyGrantingTicket'); + $authNode->addChild('cas:proxyGrantingTicket', $ticket); + + return $this; + } + + /** + * @return Response + */ + public function toResponse() + { + $content = $this->removeXmlFirstLine($this->node->asXML()); + + return new Response($content, 200, array('Content-Type' => 'application/xml')); + } + + /** + * @return SimpleXMLElement + */ + protected function getAuthNode() + { + $authNodes = $this->node->xpath('cas:authenticationSuccess'); + if (count($authNodes) < 1) { + return $this->node->addChild('cas:authenticationSuccess'); + } + + return $authNodes[0]; + } +} diff --git a/src/Traits/XmlResponse.php b/src/Traits/XmlResponse.php new file mode 100644 index 0000000..83cc545 --- /dev/null +++ b/src/Traits/XmlResponse.php @@ -0,0 +1,72 @@ +'; + + return simplexml_load_string($str); + } + + /** + * @param SimpleXMLElement $xml + * @param string $xpath + */ + protected function removeByXPath(SimpleXMLElement $xml, $xpath) + { + $node = $xml->xpath($xpath); + unset($node[0]->{0}); + } + + /** + * remove the first line of xml string + * @param string $str + * @return string + */ + protected function removeXmlFirstLine($str) + { + $first = ''; + if (Str::startsWith($str, $first)) { + return trim(substr($str, strlen($first))); + } + + return $str; + } + + /** + * @param mixed $value + * @return false|string + */ + protected function stringify($value) + { + $str = null; + if (is_string($value)) { + $str = $value; + } else if (is_object($value) && method_exists($value, '__toString')) { + $str = $value->__toString(); + } else if ($value instanceof \Serializable) { + $str = serialize($value); + } else { + //array or object that doesn't have __toString method + //json_encode will return false if encode failed + $str = json_encode($value); + } + + return $str; + } +} diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index 0d44b13..0ff6340 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -14,7 +14,10 @@ use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Ticket; use Leo108\CAS\Repositories\TicketRepository; -use ReflectionClass; +use Leo108\CAS\Responses\JsonAuthenticationFailureResponse; +use Leo108\CAS\Responses\JsonAuthenticationSuccessResponse; +use Leo108\CAS\Responses\XmlAuthenticationFailureResponse; +use Leo108\CAS\Responses\XmlAuthenticationSuccessResponse; use SerializableModel; use SimpleXMLElement; use TestCase; @@ -301,9 +304,9 @@ function ($code, $desc, $format) { ->shouldReceive('unlockTicket') ->shouldReceive('successResponse') ->andReturnUsing( - function ($name, $attr, $format) { + function ($name, $format, $attributes) { $this->assertEquals('test_user', $name); - $this->assertEmpty($attr); + $this->assertEmpty($attributes); $this->assertEquals('JSON', $format); return 'successResponse called'; @@ -345,122 +348,47 @@ public function testSuccessResponse() $controller = Mockery::mock(ValidateController::class) ->makePartial(); $method = self::getMethod($controller, 'successResponse'); - $resp = $method->invokeArgs($controller, ['test_name', [], 'JSON']); - - $this->assertInstanceOf(Response::class, $resp); - $this->assertEquals( - [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'test_name', - ], - ], - ], - $resp->getOriginalContent() - ); - - $resp = $method->invokeArgs($controller, ['test_name', ['real_name' => 'real_name'], 'JSON']); - - $this->assertInstanceOf(Response::class, $resp); - $this->assertEquals( - [ - 'serviceResponse' => [ - 'authenticationSuccess' => [ - 'user' => 'test_name', - 'attributes' => ['real_name' => 'real_name'], - ], - ], - ], - $resp->getOriginalContent() - ); - - $controller = Mockery::mock(ValidateController::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods() - ->shouldReceive('returnXML') - ->andReturnUsing( - function ($xml) { - $this->assertInstanceOf(SimpleXMLElement::class, $xml); - /* @var SimpleXMLElement $xml */ - $children = $xml->xpath('cas:authenticationSuccess'); - $this->assertCount(1, $children); - $children = $children[0]->xpath('cas:user'); - $this->assertCount(1, $children); - $this->assertEquals('test_name', $children[0]->__toString()); - - return 'returnXML called'; - } - ) - ->getMock(); - $method = self::getMethod($controller, 'successResponse'); - $this->assertEquals('returnXML called', $method->invokeArgs($controller, ['test_name', [], 'XML'])); - - $objWithToString = Mockery::mock()->shouldReceive('__toString')->andReturn('string from __toString'); - self::$functions - ->shouldReceive('method_exists') - ->with($objWithToString, '__toString') - ->andReturn(true) - ->shouldReceive('method_exists') - ->andReturn(false); + $name = 'test_name'; $attributes = [ - 'string' => 'real_name', - 'simple_array' => [1, 2, 3], - 'kv_array' => ['key' => 'value'], - 'simple_object' => (object) ['key' => 'value'], - 'obj_with_to_string' => $objWithToString, - 'serializable' => new SerializableModel(), - 'resource' => fopen(__FILE__, 'a'), + 'real_name' => 'real_name', ]; - $controller = Mockery::mock(ValidateController::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods() - ->shouldReceive('returnXML') - ->andReturnUsing( - function ($xml) use ($attributes) { - $this->assertInstanceOf(SimpleXMLElement::class, $xml); - /* @var SimpleXMLElement $xml */ - $children = $xml->xpath('cas:authenticationSuccess'); - $this->assertCount(1, $children); - $user = $children[0]->xpath('cas:user'); - $this->assertCount(1, $user); - $this->assertEquals('test_name', $user[0]->__toString()); - $attr = $children[0]->xpath('cas:attributes'); - $this->assertCount(1, $attr); - - $str = $attr[0]->xpath('cas:string'); - $this->assertCount(1, $str); - $this->assertEquals($attributes['string'], $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:simple_array'); - $this->assertCount(1, $str); - $this->assertEquals(json_encode($attributes['simple_array']), $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:kv_array'); - $this->assertCount(1, $str); - $this->assertEquals(json_encode($attributes['kv_array']), $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:simple_object'); - $this->assertCount(1, $str); - $this->assertEquals(json_encode($attributes['simple_object']), $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:obj_with_to_string'); - $this->assertCount(1, $str); - $this->assertEquals($attributes['obj_with_to_string']->__toString(), $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:serializable'); - $this->assertCount(1, $str); - $this->assertEquals(serialize($attributes['serializable']), $str[0]->__toString()); - - $str = $attr[0]->xpath('cas:resource'); - $this->assertCount(0, $str); - - return 'returnXML called'; - } - ) + $proxies = ['http://proxy1.com']; + $pgt = 'ticket'; + $jsonResp = Mockery::mock(JsonAuthenticationSuccessResponse::class) + ->shouldReceive('setUser') + ->with($name) + ->once() + ->shouldReceive('setAttributes') + ->with($attributes) + ->once() + ->shouldReceive('setProxies') + ->with($proxies) + ->once() + ->shouldReceive('toResponse') + ->once() ->getMock(); - $method = self::getMethod($controller, 'successResponse'); - $this->assertEquals('returnXML called', $method->invokeArgs($controller, ['test_name', $attributes, 'XML',])); + app()->instance(JsonAuthenticationSuccessResponse::class, $jsonResp); + $method->invokeArgs($controller, ['test_name', 'JSON', $attributes, $proxies, []]); + + $xmlResp = Mockery::mock(XmlAuthenticationSuccessResponse::class) + ->shouldReceive('setUser') + ->with($name) + ->once() + ->shouldReceive('setAttributes') + ->with($attributes) + ->once() + ->shouldReceive('setProxies') + ->with($proxies) + ->once() + ->shouldReceive('setProxyGrantingTicket') + ->with($pgt) + ->once() + ->shouldReceive('toResponse') + ->once() + ->getMock(); + app()->instance(XmlAuthenticationSuccessResponse::class, $xmlResp); + $method->invokeArgs($controller, ['test_name', 'XML', $attributes, $proxies, $pgt]); } public function testFailureResponse() @@ -468,78 +396,27 @@ public function testFailureResponse() $controller = Mockery::mock(ValidateController::class) ->makePartial(); $method = self::getMethod($controller, 'failureResponse'); - $resp = $method->invokeArgs($controller, ['code', 'desc', 'JSON']); - - $this->assertInstanceOf(Response::class, $resp); - $this->assertEquals( - [ - 'serviceResponse' => [ - 'authenticationFailure' => [ - 'code' => 'code', - 'description' => 'desc', - ], - ], - ], - $resp->getOriginalContent() - ); - - $controller = Mockery::mock(ValidateController::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods() - ->shouldReceive('returnXML') - ->andReturnUsing( - function ($xml) { - $this->assertInstanceOf(SimpleXMLElement::class, $xml); - /* @var SimpleXMLElement $xml */ - $children = $xml->xpath('cas:authenticationFailure'); - $this->assertCount(1, $children); - $this->assertEquals('desc', $children[0]->__toString()); - $this->assertEquals('code', $children[0]['code']); - - return 'returnXML called'; - } - ) + $code = 'code'; + $desc = 'desc'; + $jsonResp = Mockery::mock(JsonAuthenticationFailureResponse::class) + ->shouldReceive('setFailure') + ->withArgs([$code, $desc]) + ->once() + ->shouldReceive('toResponse') + ->once() ->getMock(); - $method = self::getMethod($controller, 'failureResponse'); - $this->assertEquals('returnXML called', $method->invokeArgs($controller, ['code', 'desc', 'XML'])); - } - - public function testRemoveXmlFirstLine() - { - $xml = new SimpleXMLElement(ValidateController::BASE_XML); - $controller = Mockery::mock(ValidateController::class); - $method = self::getMethod($controller, 'removeXmlFirstLine'); - $this->assertNotContains('', $method->invoke($controller, $xml->asXML())); - - $normalStr = 'some string'; - $this->assertEquals($normalStr, $method->invoke($controller, $normalStr)); - } - - public function testReturnXML() - { - $xml = new SimpleXMLElement(ValidateController::BASE_XML); - $controller = Mockery::mock(ValidateController::class) - ->makePartial() - ->shouldAllowMockingProtectedMethods() - ->shouldReceive('removeXmlFirstLine') - ->andReturn('parsed string') + app()->instance(JsonAuthenticationFailureResponse::class, $jsonResp); + $method->invokeArgs($controller, [$code, $desc, 'JSON']); + + $xmlResp = Mockery::mock(XmlAuthenticationFailureResponse::class) + ->shouldReceive('setFailure') + ->withArgs([$code, $desc]) + ->once() + ->shouldReceive('toResponse') + ->once() ->getMock(); - - $method = self::getMethod($controller, 'returnXML'); - $resp = $method->invoke($controller, $xml); - $this->assertInstanceOf(Response::class, $resp); - $this->assertEquals(200, $resp->getStatusCode()); - $this->assertTrue($resp->headers->has('Content-Type')); - $this->assertEquals('application/xml', $resp->headers->get('Content-Type')); - } - - protected static function getMethod($obj, $name) - { - $class = new ReflectionClass($obj); - $method = $class->getMethod($name); - $method->setAccessible(true); - - return $method; + app()->instance(XmlAuthenticationFailureResponse::class, $xmlResp); + $method->invokeArgs($controller, [$code, $desc, 'XML']); } protected function getValidRequest() @@ -556,11 +433,4 @@ protected function getValidRequest() ->andReturn('JSON') ->getMock(); } - - protected function prepareCASXml(SimpleXMLElement $xml) - { - $xml->registerXPathNamespace('cas', 'http://www.yale.edu/tp/cas'); - - return $xml; - } } diff --git a/tests/Responses/XmlAuthenticationSuccessResponseTest.php b/tests/Responses/XmlAuthenticationSuccessResponseTest.php new file mode 100644 index 0000000..64c98b0 --- /dev/null +++ b/tests/Responses/XmlAuthenticationSuccessResponseTest.php @@ -0,0 +1,103 @@ +toResponse()->getContent(); + $this->assertNotContains('cas:user', $content); + $resp->setUser('test'); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:user', $content); + $this->assertContains('test', $content); + + $resp->setUser('username_2'); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:user', $content); + $this->assertNotContains('test', $content); + $this->assertContains('username_2', $content); + } + + public function testSetProxies() + { + $resp = new XmlAuthenticationSuccessResponse(); + $content = $resp->toResponse()->getContent(); + $this->assertNotContains('cas:proxies', $content); + $this->assertNotContains('cas:proxy', $content); + + $resp->setProxies(['http://proxy1.com']); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:proxies', $content); + $this->assertContains('cas:proxy', $content); + $this->assertContains('http://proxy1.com', $content); + + $resp->setProxies(['http://proxy2.com', 'http://proxy3.com']); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:proxies', $content); + $this->assertContains('cas:proxy', $content); + $this->assertNotContains('http://proxy1.com', $content); + $this->assertContains('http://proxy2.com', $content); + $this->assertContains('http://proxy3.com', $content); + } + + public function testSetAttributes() + { + $resp = Mockery::mock(XmlAuthenticationSuccessResponse::class, []) + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('stringify') + ->andReturn('string') + ->getMock(); + $content = $resp->toResponse()->getContent(); + $this->assertNotContains('cas:attributes', $content); + + $resp->setAttributes(['key1' => 'value1']); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:attributes', $content); + $this->assertContains('cas:key1', $content); + + $resp->setAttributes(['key2' => 'value2', 'key3' => 'value3']); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:attributes', $content); + $this->assertNotContains('cas:key1', $content); + $this->assertContains('cas:key2', $content); + $this->assertContains('cas:key3', $content); + } + + public function testSetProxyGrantingTicket() + { + $resp = new XmlAuthenticationSuccessResponse(); + $content = $resp->toResponse()->getContent(); + $this->assertNotContains('cas:proxyGrantingTicket', $content); + + $ticket1 = 'ticket1'; + $ticket2 = 'ticket2'; + $resp->setProxyGrantingTicket($ticket1); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:proxyGrantingTicket', $content); + $this->assertContains($ticket1, $content); + + $resp->setProxyGrantingTicket($ticket2); + $content = $resp->toResponse()->getContent(); + $this->assertContains('cas:proxyGrantingTicket', $content); + $this->assertNotContains($ticket1, $content); + $this->assertContains($ticket2, $content); + } + + public function testToResponse() + { + $this->markTestIncomplete(); + } +} diff --git a/tests/TestCase.php b/tests/TestCase.php index 8daa3da..8862b4c 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -76,4 +76,13 @@ public function migrate() $migrationClass = $classFinder->findClass($usersMigrationFile); (new $migrationClass())->up(); } + + protected static function getMethod($obj, $name) + { + $class = new ReflectionClass($obj); + $method = $class->getMethod($name); + $method->setAccessible(true); + + return $method; + } } diff --git a/tests/Traits/XmlResponseTest.php b/tests/Traits/XmlResponseTest.php new file mode 100644 index 0000000..f96e5a3 --- /dev/null +++ b/tests/Traits/XmlResponseTest.php @@ -0,0 +1,89 @@ +method_exists($obj, $method); +} + +class XmlResponseTest extends TestCase +{ + protected $testObj; + public static $functions; + + public function setUp() + { + $this->testObj = new TestXmlResponseTrait(); + self::$functions = Mockery::mock(); + } + + public function testStringify() + { + $method = self::getMethod($this->testObj, 'stringify'); + + $objWithToString = Mockery::mock()->shouldReceive('__toString')->andReturn('string from __toString'); + self::$functions + ->shouldReceive('method_exists') + ->with($objWithToString, '__toString') + ->andReturn(true) + ->shouldReceive('method_exists') + ->andReturn(false); + $serializableModel = new SerializableModel(); + $resource = fopen(__FILE__, 'a'); + $this->assertEquals('string', $method->invoke($this->testObj, 'string')); + $this->assertEquals(json_encode([1, 2, 3]), $method->invoke($this->testObj, [1, 2, 3])); + $this->assertEquals(json_encode(['key' => 'value']), $method->invoke($this->testObj, ['key' => 'value'])); + $this->assertEquals( + json_encode(['key' => 'value']), + $method->invoke($this->testObj, (object) ['key' => 'value']) + ); + $this->assertEquals($objWithToString->__toString(), $method->invoke($this->testObj, $objWithToString)); + $this->assertEquals(serialize($serializableModel), $method->invoke($this->testObj, $serializableModel)); + $this->assertFalse($method->invoke($this->testObj, $resource)); + } + + public function testRemoveXmlFirstLine() + { + $xml = new SimpleXMLElement(''); + $method = self::getMethod($this->testObj, 'removeXmlFirstLine'); + $this->assertNotContains('', $method->invoke($this->testObj, $xml->asXML())); + + $normalStr = 'some string'; + $this->assertEquals($normalStr, $method->invoke($this->testObj, $normalStr)); + } + + public function testRemoveByXPath() + { + $xml = new SimpleXMLElement(''); + $xml->addChild('cas:tag', '123'); + $this->assertContains('cas:tag', $xml->asXML()); + $this->assertContains('123', $xml->asXML()); + $method = self::getMethod($this->testObj, 'removeByXPath'); + $method->invoke($this->testObj, $xml, 'cas:tag'); + $this->assertNotContains('cas:tag', $xml->asXML()); + $this->assertNotContains('123', $xml->asXML()); + } + + public function testGetRootNode() + { + $method = self::getMethod($this->testObj, 'getRootNode'); + $this->assertContains( + '', + $method->invoke($this->testObj)->asXML() + ); + } +} diff --git a/tests/_support/TestXmlResponseTrait.php b/tests/_support/TestXmlResponseTrait.php new file mode 100644 index 0000000..650e6da --- /dev/null +++ b/tests/_support/TestXmlResponseTrait.php @@ -0,0 +1,12 @@ + Date: Mon, 24 Oct 2016 13:46:30 +0800 Subject: [PATCH 02/17] fix unit tests failed in php7 --- composer.json | 1 + src/Traits/XmlResponse.php | 11 ++++++----- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index 6838710..f48075e 100644 --- a/composer.json +++ b/composer.json @@ -11,6 +11,7 @@ ], "require": { "php": ">=5.5.9", + "ext-dom": "*", "laravel/framework": "5.1.*|5.2.*|5.3.*" }, "require-dev": { diff --git a/src/Traits/XmlResponse.php b/src/Traits/XmlResponse.php index 83cc545..b1d16d9 100644 --- a/src/Traits/XmlResponse.php +++ b/src/Traits/XmlResponse.php @@ -18,9 +18,7 @@ trait XmlResponse */ protected function getRootNode() { - $str = ''; - - return simplexml_load_string($str); + return simplexml_load_string(''); } /** @@ -29,8 +27,11 @@ protected function getRootNode() */ protected function removeByXPath(SimpleXMLElement $xml, $xpath) { - $node = $xml->xpath($xpath); - unset($node[0]->{0}); + $nodes = $xml->xpath($xpath); + foreach ($nodes as $node) { + $dom = dom_import_simplexml($node); + $dom->parentNode->removeChild($dom); + } } /** From 62be951e66fdb81b84182497975fb36860efdd2d Mon Sep 17 00:00:00 2001 From: leo108 Date: Tue, 25 Oct 2016 13:26:25 +0800 Subject: [PATCH 03/17] optimize UserLogin contract --- src/Contracts/Interactions/UserLogin.php | 18 +- src/Http/Controllers/SecurityController.php | 33 ++-- .../Controllers/SecurityControllerTest.php | 157 ++++++++++++++---- 3 files changed, 150 insertions(+), 58 deletions(-) diff --git a/src/Contracts/Interactions/UserLogin.php b/src/Contracts/Interactions/UserLogin.php index d05cc35..7f09ea9 100644 --- a/src/Contracts/Interactions/UserLogin.php +++ b/src/Contracts/Interactions/UserLogin.php @@ -15,11 +15,10 @@ interface UserLogin { /** - * @param Request $request - * @param callable $authenticated - * @return Response + * @param Request $request + * @return UserModel */ - public function login(Request $request, callable $authenticated); + public function login(Request $request); /** * @param Request $request @@ -49,9 +48,14 @@ public function showLoginPage(Request $request, array $errors = []); public function redirectToHome(array $errors = []); /** - * @param Request $request - * @param callable $beforeLogout + * @param Request $request + * @return Response + */ + public function logout(Request $request); + + /** + * @param Request $request * @return Response */ - public function logout(Request $request, callable $beforeLogout); + public function showLoggedOut(Request $request); } diff --git a/src/Http/Controllers/SecurityController.php b/src/Http/Controllers/SecurityController.php index f3f9c39..74d3853 100644 --- a/src/Http/Controllers/SecurityController.php +++ b/src/Http/Controllers/SecurityController.php @@ -9,6 +9,7 @@ namespace Leo108\CAS\Http\Controllers; use Leo108\CAS\Contracts\Interactions\UserLogin; +use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Events\CasUserLoginEvent; use Leo108\CAS\Events\CasUserLogoutEvent; use Leo108\CAS\Exceptions\CAS\CasException; @@ -77,7 +78,8 @@ public function showLogin(Request $request) return $this->loginInteraction->showLoginWarnPage($request, $url, $service); } - return $this->authenticated($request); + return $this->authenticated($request, $user); + } return $this->loginInteraction->showLoginPage($request, $errors); @@ -85,19 +87,13 @@ public function showLogin(Request $request) public function login(Request $request) { - return $this->loginInteraction->login($request, array($this, 'authenticated')); + $user = $this->loginInteraction->login($request); + + return $this->authenticated($request, $user); } - public function authenticated(Request $request) + public function authenticated(Request $request, UserModel $user) { - $user = $this->loginInteraction->getCurrentUser($request); - if ($user === null) { - //unreachable code - throw new CasException( - CasException::INTERNAL_ERROR, - 'should call authenticated only after getCurrentUser return not null' - ); - } event(new CasUserLoginEvent($request, $user)); $serviceUrl = $request->get('service', ''); if (!empty($serviceUrl)) { @@ -117,11 +113,14 @@ public function authenticated(Request $request) public function logout(Request $request) { - return $this->loginInteraction->logout( - $request, - function (Request $request) { - event(new CasUserLogoutEvent($request, $this->loginInteraction->getCurrentUser($request))); - } - ); + $user = $this->loginInteraction->getCurrentUser($request); + $this->loginInteraction->logout($request); + event(new CasUserLogoutEvent($request, $user)); + $service = $request->get('service'); + if ($service && $this->serviceRepository->isUrlValid($service)) { + return redirect($service); + } + + return $this->loginInteraction->showLoggedOut($request); } } diff --git a/tests/Http/Controllers/SecurityControllerTest.php b/tests/Http/Controllers/SecurityControllerTest.php index cf2f74e..0c84409 100644 --- a/tests/Http/Controllers/SecurityControllerTest.php +++ b/tests/Http/Controllers/SecurityControllerTest.php @@ -36,12 +36,12 @@ public function setUp() self::$functions = Mockery::mock(); } - public function testShowLogin() + public function testShowLoginWithValidServiceUrl() { - //not logged in with valid service url $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) + ->once() ->getMock(); app()->instance(ServiceRepository::class, $serviceRepository); $loginInteraction = Mockery::mock(UserLogin::class) @@ -53,21 +53,28 @@ function ($request, $errors) { return 'show login called'; } ) + ->once() ->shouldReceive('getCurrentUser') ->andReturn(false) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') + ->once() ->getMock(); $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); - //not logged in with invalid service url + } + + public function testShowLoginWithInvalidServiceUrl() + { $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(false) + ->once() ->getMock(); app()->instance(ServiceRepository::class, $serviceRepository); $loginInteraction = Mockery::mock(UserLogin::class) @@ -80,34 +87,45 @@ function ($request, $errors) { return 'show login called'; } ) + ->once() ->shouldReceive('getCurrentUser') ->andReturn(false) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') + ->once() ->getMock(); $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); + } + public function testShowLoginWhenLoggedInWithValidServiceUrlWithoutWarn() + { //logged in with valid service url without warn parameter $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) + ->once() ->getMock(); $ticketRepository = Mockery::mock(TicketRepository::class); + $user = new User(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') - ->andReturn(true)//just not false is OK + ->andReturn($user) + ->once() ->getMock(); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') + ->once() ->shouldReceive('get') ->withArgs(['warn']) ->andReturn(false) + ->once() ->getMock(); $controller = Mockery::mock( SecurityController::class, @@ -115,14 +133,20 @@ function ($request, $errors) { ) ->makePartial() ->shouldReceive('authenticated') + ->withArgs([$request, $user]) ->andReturn('authenticated called') + ->once() ->getMock(); $this->assertEquals('authenticated called', $controller->showLogin($request)); + } + public function testShowLoginWhenLoggedInWithValidServiceUrlWithWarn() + { //logged in with valid service url with warn parameter $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) + ->once() ->getMock(); app()->instance(ServiceRepository::class, $serviceRepository); $ticketRepository = Mockery::mock(TicketRepository::class); @@ -130,30 +154,39 @@ function ($request, $errors) { $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') ->andReturn(true)//just not false is OK + ->once() ->shouldReceive('showLoginWarnPage') ->andReturn('showLoginWarnPage called') + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') + ->once() ->shouldReceive('get') ->withArgs(['warn']) ->andReturn('true') + ->once() ->getMock(); $request->query = Mockery::mock() ->shouldReceive('all') ->andReturn([]) + ->once() ->getMock(); - self::$functions->shouldReceive('cas_route')->andReturn('some string'); + self::$functions->shouldReceive('cas_route')->andReturn('some string')->once(); $controller = app()->make(SecurityController::class); $this->assertEquals('showLoginWarnPage called', $controller->showLogin($request)); + } + public function testShowLoginWhenLoggedInWithInvalidServiceUrl() + { //logged in with invalid service url $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(false) + ->once() ->getMock(); app()->instance(ServiceRepository::class, $serviceRepository); $ticketRepository = Mockery::mock(TicketRepository::class); @@ -161,6 +194,7 @@ function ($request, $errors) { $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') ->andReturn(true)//just not false is OK + ->once() ->shouldReceive('redirectToHome') ->andReturnUsing( function ($errors) { @@ -170,20 +204,23 @@ function ($errors) { return 'redirectToHome called'; } ) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) + $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') + ->once() ->getMock(); $controller = app()->make(SecurityController::class); $this->assertEquals('redirectToHome called', $controller->showLogin($request)); } - public function testAuthenticated() + public function testAuthenticatedWithoutService() { //without service url + $user = new User(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('redirectToHome') ->andReturnUsing( @@ -191,19 +228,26 @@ function () { return 'redirectToHome called'; } ) - ->shouldReceive('getCurrentUser') - ->andReturn(new User()) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('') + ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $this->assertEquals('redirectToHome called', app()->make(SecurityController::class)->authenticated($request)); + $this->assertEquals( + 'redirectToHome called', + app()->make(SecurityController::class)->authenticated($request, $user) + ); + } + public function testAuthenticatedWithService() + { //with service url but apply ticket failed + $user = new User(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('redirectToHome') ->andReturnUsing( @@ -214,79 +258,124 @@ function ($errors) { return 'redirectToHome called'; } ) - ->shouldReceive('getCurrentUser') - ->andReturn(new User()) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('applyTicket') ->andThrow(new CasException(CasException::INTERNAL_ERROR)) + ->once() ->getMock(); app()->instance(TicketRepository::class, $ticketRepository); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('http://leo108.com') + ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $this->assertEquals('redirectToHome called', app()->make(SecurityController::class)->authenticated($request)); + $this->assertEquals( + 'redirectToHome called', + app()->make(SecurityController::class)->authenticated($request, $user) + ); //with service url - $loginInteraction = Mockery::mock(UserLogin::class) - ->shouldReceive('getCurrentUser') - ->andReturn(new User()) - ->getMock(); - app()->instance(UserLogin::class, $loginInteraction); $ticket = Mockery::mock(); $ticket->ticket = 'ST-abc'; $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('applyTicket') ->andReturn($ticket) + ->once() ->getMock(); app()->instance(TicketRepository::class, $ticketRepository); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('http://leo108.com') + ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $resp = app()->make(SecurityController::class)->authenticated($request); + $resp = app()->make(SecurityController::class)->authenticated($request, $user); $this->assertInstanceOf(RedirectResponse::class, $resp); $this->assertEquals($resp->getTargetUrl(), 'http://leo108.com?ticket=ST-abc'); } - public function testLogout() + public function testLogoutWithoutService() { $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('logout') + ->once() + ->shouldReceive('getCurrentUser') + ->andReturn(new User())//just not false is OK + ->once() + ->shouldReceive('showLoggedOut') ->andReturnUsing( - function ($request, $callback) { - $this->expectsEvents(CasUserLogoutEvent::class); - call_user_func_array($callback, [$request]); - - return 'logout called'; + function ($request) { + return 'showLoggedOut called'; } ) + ->once() + ->getMock(); + app()->instance(UserLogin::class, $loginInteraction); + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service']) + ->andReturn(null) + ->once() + ->getMock(); + $this->expectsEvents(CasUserLogoutEvent::class); + $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); + } + + public function testLogoutWithValidService() + { + $serviceRepository = Mockery::mock(ServiceRepository::class) + ->shouldReceive('isUrlValid') + ->andReturn(true) + ->once() + ->getMock(); + app()->instance(ServiceRepository::class, $serviceRepository); + $loginInteraction = Mockery::mock(UserLogin::class) + ->shouldReceive('logout') + ->once() ->shouldReceive('getCurrentUser') ->andReturn(new User()) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class); - $this->assertEquals('logout called', app()->make(SecurityController::class)->logout($request)); + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service']) + ->andReturn('http://leo108.com') + ->once() + ->getMock(); + $this->expectsEvents(CasUserLogoutEvent::class); + $resp = app()->make(SecurityController::class)->logout($request); + $this->assertInstanceOf(RedirectResponse::class, $resp); } public function testLogin() { + $user = new User(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('login') - ->andReturnUsing( - function () { - return 'login called'; - } - ) + ->andReturn($user) + ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class); - $this->assertEquals('login called', app()->make(SecurityController::class)->login($request)); + $request = Mockery::mock(Request::class); + $serviceRepository = Mockery::mock(ServiceRepository::class); + $ticketRepository = Mockery::mock(TicketRepository::class); + $controller = Mockery::mock( + SecurityController::class, + [$serviceRepository, $ticketRepository, $loginInteraction] + ) + ->makePartial() + ->shouldReceive('authenticated') + ->withArgs([$request, $user]) + ->andReturn('authenticated called') + ->once() + ->getMock(); + $this->assertEquals('authenticated called', $controller->login($request)); } } From 978f4885e54f219b95a2aaf866e6eb27f2d1fb0b Mon Sep 17 00:00:00 2001 From: leo108 Date: Tue, 25 Oct 2016 15:44:48 +0800 Subject: [PATCH 04/17] add more tests --- composer.json | 3 +- src/Http/Controllers/SecurityController.php | 6 +- src/Responses/BaseJsonResponse.php | 27 +++++++ .../BaseXmlResponse.php} | 35 +++++++-- .../JsonAuthenticationFailureResponse.php | 16 +--- .../JsonAuthenticationSuccessResponse.php | 16 +--- .../XmlAuthenticationFailureResponse.php | 44 +---------- .../XmlAuthenticationSuccessResponse.php | 23 +----- .../Controllers/SecurityControllerTest.php | 27 ++++++- .../Controllers/ValidateControllerTest.php | 18 ++--- tests/Repositories/TicketRepositoryTest.php | 6 +- tests/Responses/BaseJsonResponseTest.php | 28 +++++++ .../BaseXmlResponseTest.php} | 39 +++++++--- .../JsonAuthenticationFailureResponseTest.php | 39 ++++++++++ .../JsonAuthenticationSuccessResponseTest.php | 77 +++++++++++++++++++ .../XmlAuthenticationFailureResponseTest.php | 41 ++++++++++ .../XmlAuthenticationSuccessResponseTest.php | 32 ++++---- tests/TestCase.php | 11 ++- tests/_support/TestXmlResponseTrait.php | 12 --- 19 files changed, 345 insertions(+), 155 deletions(-) create mode 100644 src/Responses/BaseJsonResponse.php rename src/{Traits/XmlResponse.php => Responses/BaseXmlResponse.php} (73%) create mode 100644 tests/Responses/BaseJsonResponseTest.php rename tests/{Traits/XmlResponseTest.php => Responses/BaseXmlResponseTest.php} (69%) create mode 100644 tests/Responses/JsonAuthenticationFailureResponseTest.php create mode 100644 tests/Responses/JsonAuthenticationSuccessResponseTest.php create mode 100644 tests/Responses/XmlAuthenticationFailureResponseTest.php delete mode 100644 tests/_support/TestXmlResponseTrait.php diff --git a/composer.json b/composer.json index f48075e..1cc1fba 100644 --- a/composer.json +++ b/composer.json @@ -28,8 +28,7 @@ "classmap": [ "tests/TestCase.php", "tests/_support/User.php", - "tests/_support/SerializableModel.php", - "tests/_support/TestXmlResponseTrait.php" + "tests/_support/SerializableModel.php" ] } } diff --git a/src/Http/Controllers/SecurityController.php b/src/Http/Controllers/SecurityController.php index 74d3853..16e30dc 100644 --- a/src/Http/Controllers/SecurityController.php +++ b/src/Http/Controllers/SecurityController.php @@ -114,8 +114,10 @@ public function authenticated(Request $request, UserModel $user) public function logout(Request $request) { $user = $this->loginInteraction->getCurrentUser($request); - $this->loginInteraction->logout($request); - event(new CasUserLogoutEvent($request, $user)); + if ($user) { + $this->loginInteraction->logout($request); + event(new CasUserLogoutEvent($request, $user)); + } $service = $request->get('service'); if ($service && $this->serviceRepository->isUrlValid($service)) { return redirect($service); diff --git a/src/Responses/BaseJsonResponse.php b/src/Responses/BaseJsonResponse.php new file mode 100644 index 0000000..59fa1a6 --- /dev/null +++ b/src/Responses/BaseJsonResponse.php @@ -0,0 +1,27 @@ +data), 200, ['Content-Type' => 'application/json']); + } +} diff --git a/src/Traits/XmlResponse.php b/src/Responses/BaseXmlResponse.php similarity index 73% rename from src/Traits/XmlResponse.php rename to src/Responses/BaseXmlResponse.php index b1d16d9..8192705 100644 --- a/src/Traits/XmlResponse.php +++ b/src/Responses/BaseXmlResponse.php @@ -2,17 +2,31 @@ /** * Created by PhpStorm. * User: leo108 - * Date: 2016/10/23 - * Time: 16:26 + * Date: 2016/10/25 + * Time: 15:10 */ -namespace Leo108\CAS\Traits; +namespace Leo108\CAS\Responses; use Illuminate\Support\Str; use SimpleXMLElement; +use Symfony\Component\HttpFoundation\Response; -trait XmlResponse +class BaseXmlResponse { + /** + * @var SimpleXMLElement + */ + protected $node; + + /** + * BaseXmlResponse constructor. + */ + public function __construct() + { + $this->node = $this->getRootNode(); + } + /** * @return SimpleXMLElement */ @@ -55,7 +69,6 @@ protected function removeXmlFirstLine($str) */ protected function stringify($value) { - $str = null; if (is_string($value)) { $str = $value; } else if (is_object($value) && method_exists($value, '__toString')) { @@ -70,4 +83,14 @@ protected function stringify($value) return $str; } -} + + /** + * @return Response + */ + public function toResponse() + { + $content = $this->removeXmlFirstLine($this->node->asXML()); + + return new Response($content, 200, ['Content-Type' => 'application/xml']); + } +} \ No newline at end of file diff --git a/src/Responses/JsonAuthenticationFailureResponse.php b/src/Responses/JsonAuthenticationFailureResponse.php index 781978a..3506fad 100644 --- a/src/Responses/JsonAuthenticationFailureResponse.php +++ b/src/Responses/JsonAuthenticationFailureResponse.php @@ -8,16 +8,10 @@ namespace Leo108\CAS\Responses; -use Illuminate\Http\Response; use Leo108\CAS\Contracts\Responses\AuthenticationFailureResponse; -class JsonAuthenticationFailureResponse implements AuthenticationFailureResponse +class JsonAuthenticationFailureResponse extends BaseJsonResponse implements AuthenticationFailureResponse { - /** - * @var array - */ - protected $data; - /** * JsonAuthenticationFailureResponse constructor. */ @@ -38,12 +32,4 @@ public function setFailure($code, $description) return $this; } - - /** - * @return Response - */ - public function toResponse() - { - return new Response($this->data); - } } diff --git a/src/Responses/JsonAuthenticationSuccessResponse.php b/src/Responses/JsonAuthenticationSuccessResponse.php index 3fac6d1..ae02e7b 100644 --- a/src/Responses/JsonAuthenticationSuccessResponse.php +++ b/src/Responses/JsonAuthenticationSuccessResponse.php @@ -8,16 +8,10 @@ namespace Leo108\CAS\Responses; -use Illuminate\Http\Response; use Leo108\CAS\Contracts\Responses\AuthenticationSuccessResponse; -class JsonAuthenticationSuccessResponse implements AuthenticationSuccessResponse +class JsonAuthenticationSuccessResponse extends BaseJsonResponse implements AuthenticationSuccessResponse { - /** - * @var array - */ - protected $data; - /** * JsonAuthenticationSuccessResponse constructor. */ @@ -53,12 +47,4 @@ public function setProxyGrantingTicket($ticket) return $this; } - - /** - * @return Response - */ - public function toResponse() - { - return new Response($this->data); - } } diff --git a/src/Responses/XmlAuthenticationFailureResponse.php b/src/Responses/XmlAuthenticationFailureResponse.php index 68a4726..9ece4cf 100644 --- a/src/Responses/XmlAuthenticationFailureResponse.php +++ b/src/Responses/XmlAuthenticationFailureResponse.php @@ -8,27 +8,10 @@ namespace Leo108\CAS\Responses; -use Illuminate\Http\Response; use Leo108\CAS\Contracts\Responses\AuthenticationFailureResponse; -use Leo108\CAS\Traits\XmlResponse; -use SimpleXMLElement; -class XmlAuthenticationFailureResponse implements AuthenticationFailureResponse +class XmlAuthenticationFailureResponse extends BaseXmlResponse implements AuthenticationFailureResponse { - use XmlResponse; - /** - * @var SimpleXMLElement - */ - protected $node; - - /** - * XmlAuthenticationFailureResponse constructor. - */ - public function __construct() - { - $this->node = $this->getRootNode(); - } - /** * @param string $code * @param string $description @@ -42,27 +25,4 @@ public function setFailure($code, $description) return $this; } - - /** - * @return Response - */ - public function toResponse() - { - $content = $this->removeXmlFirstLine($this->node->asXML()); - - return new Response($content, 200, array('Content-Type' => 'application/xml')); - } - - /** - * @return SimpleXMLElement - */ - protected function getAuthNode() - { - $authNodes = $this->node->xpath('cas:authenticationFailure'); - if (count($authNodes) < 1) { - return $this->node->addChild('cas:authenticationFailure'); - } - - return $authNodes[0]; - } -} \ No newline at end of file +} diff --git a/src/Responses/XmlAuthenticationSuccessResponse.php b/src/Responses/XmlAuthenticationSuccessResponse.php index 2302421..2147758 100644 --- a/src/Responses/XmlAuthenticationSuccessResponse.php +++ b/src/Responses/XmlAuthenticationSuccessResponse.php @@ -8,26 +8,17 @@ namespace Leo108\CAS\Responses; -use Illuminate\Http\Response; use Leo108\CAS\Contracts\Responses\AuthenticationSuccessResponse; -use Leo108\CAS\Traits\XmlResponse; use SimpleXMLElement; -class XmlAuthenticationSuccessResponse implements AuthenticationSuccessResponse +class XmlAuthenticationSuccessResponse extends BaseXmlResponse implements AuthenticationSuccessResponse { - use XmlResponse; - - /** - * @var SimpleXMLElement - */ - protected $node; - /** * XmlAuthenticationSuccessResponse constructor. */ public function __construct() { - $this->node = $this->getRootNode(); + parent::__construct(); $this->node->addChild('cas:authenticationSuccess'); } @@ -76,16 +67,6 @@ public function setProxyGrantingTicket($ticket) return $this; } - /** - * @return Response - */ - public function toResponse() - { - $content = $this->removeXmlFirstLine($this->node->asXML()); - - return new Response($content, 200, array('Content-Type' => 'application/xml')); - } - /** * @return SimpleXMLElement */ diff --git a/tests/Http/Controllers/SecurityControllerTest.php b/tests/Http/Controllers/SecurityControllerTest.php index 0c84409..1740397 100644 --- a/tests/Http/Controllers/SecurityControllerTest.php +++ b/tests/Http/Controllers/SecurityControllerTest.php @@ -300,13 +300,38 @@ function ($errors) { $this->assertEquals($resp->getTargetUrl(), 'http://leo108.com?ticket=ST-abc'); } + public function testLogoutWhenNotLoggedInWithoutService() + { + $loginInteraction = Mockery::mock(UserLogin::class) + ->shouldReceive('getCurrentUser') + ->andReturn(false) + ->once() + ->shouldReceive('showLoggedOut') + ->andReturnUsing( + function ($request) { + return 'showLoggedOut called'; + } + ) + ->once() + ->getMock(); + app()->instance(UserLogin::class, $loginInteraction); + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service']) + ->andReturn(null) + ->once() + ->getMock(); + $this->doesntExpectEvents(CasUserLogoutEvent::class); + $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); + } + public function testLogoutWithoutService() { $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('logout') ->once() ->shouldReceive('getCurrentUser') - ->andReturn(new User())//just not false is OK + ->andReturn(new User()) ->once() ->shouldReceive('showLoggedOut') ->andReturnUsing( diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index 0ff6340..e87dbfc 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -192,7 +192,7 @@ function ($code, $desc, $format) { } ) ->getMock(); - $method = self::getMethod($controller, 'casValidate'); + $method = self::getNonPublicMethod($controller, 'casValidate'); $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); //lock ticket failed @@ -214,7 +214,7 @@ function ($code, $desc, $format) { } ) ->getMock(); - $method = self::getMethod($controller, 'casValidate'); + $method = self::getNonPublicMethod($controller, 'casValidate'); $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); //ticket not exists @@ -240,7 +240,7 @@ function ($code, $desc, $format) { } ) ->getMock(); - $method = self::getMethod($controller, 'casValidate'); + $method = self::getNonPublicMethod($controller, 'casValidate'); $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); //ticket exists but service url mismatch @@ -272,7 +272,7 @@ function ($code, $desc, $format) { } ) ->getMock(); - $method = self::getMethod($controller, 'casValidate'); + $method = self::getNonPublicMethod($controller, 'casValidate'); $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); //normal @@ -314,7 +314,7 @@ function ($name, $format, $attributes) { ) ->getMock(); - $method = self::getMethod($controller, 'casValidate'); + $method = self::getNonPublicMethod($controller, 'casValidate'); $this->assertEquals('successResponse called', $method->invokeArgs($controller, [$request, false])); } @@ -326,7 +326,7 @@ public function testLockTicket() ->getMock(); $controller = Mockery::mock(ValidateController::class, [$locker, Mockery::mock(TicketRepository::class)]) ->makePartial(); - $method = self::getMethod($controller, 'lockTicket'); + $method = self::getNonPublicMethod($controller, 'lockTicket'); $this->assertEquals('acquireLock called', $method->invokeArgs($controller, ['str', 30])); } @@ -339,7 +339,7 @@ public function testUnlockTicket() app()->instance(TicketLocker::class, $locker); $controller = Mockery::mock(ValidateController::class, [$locker, Mockery::mock(TicketRepository::class)]) ->makePartial(); - $method = self::getMethod($controller, 'unlockTicket'); + $method = self::getNonPublicMethod($controller, 'unlockTicket'); $this->assertEquals('releaseLock called', $method->invokeArgs($controller, ['str', 30])); } @@ -347,7 +347,7 @@ public function testSuccessResponse() { $controller = Mockery::mock(ValidateController::class) ->makePartial(); - $method = self::getMethod($controller, 'successResponse'); + $method = self::getNonPublicMethod($controller, 'successResponse'); $name = 'test_name'; $attributes = [ @@ -395,7 +395,7 @@ public function testFailureResponse() { $controller = Mockery::mock(ValidateController::class) ->makePartial(); - $method = self::getMethod($controller, 'failureResponse'); + $method = self::getNonPublicMethod($controller, 'failureResponse'); $code = 'code'; $desc = 'desc'; $jsonResp = Mockery::mock(JsonAuthenticationFailureResponse::class) diff --git a/tests/Repositories/TicketRepositoryTest.php b/tests/Repositories/TicketRepositoryTest.php index 91a9b0d..3be6321 100644 --- a/tests/Repositories/TicketRepositoryTest.php +++ b/tests/Repositories/TicketRepositoryTest.php @@ -144,7 +144,7 @@ public function testGetAvailableTicket() ->getMock(); $length = 32; - $ticket = self::getMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, $length); + $ticket = self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, $length); $this->assertNotFalse($ticket); $this->assertEquals($length, strlen($ticket)); @@ -154,10 +154,10 @@ public function testGetAvailableTicket() ->shouldReceive('getByTicket') ->andReturn(true) ->getMock(); - $this->assertFalse(self::getMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, 32)); + $this->assertFalse(self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, 32)); } - protected static function getMethod($obj, $name) + protected static function getNonPublicMethod($obj, $name) { $class = new ReflectionClass($obj); $method = $class->getMethod($name); diff --git a/tests/Responses/BaseJsonResponseTest.php b/tests/Responses/BaseJsonResponseTest.php new file mode 100644 index 0000000..95dea3a --- /dev/null +++ b/tests/Responses/BaseJsonResponseTest.php @@ -0,0 +1,28 @@ + 'test']; + $resp = new BaseJsonResponse(); + $property = self::getNonPublicProperty($resp, 'data'); + $property->setValue($resp, $data); + $ret = $resp->toResponse(); + $this->assertInstanceOf(Response::class, $ret); + $this->assertEquals(200, $ret->getStatusCode()); + $this->assertEquals(json_encode($data), $ret->getContent()); + $this->assertEquals('application/json', $ret->headers->get('Content-Type')); + } +} diff --git a/tests/Traits/XmlResponseTest.php b/tests/Responses/BaseXmlResponseTest.php similarity index 69% rename from tests/Traits/XmlResponseTest.php rename to tests/Responses/BaseXmlResponseTest.php index f96e5a3..03632b0 100644 --- a/tests/Traits/XmlResponseTest.php +++ b/tests/Responses/BaseXmlResponseTest.php @@ -1,39 +1,38 @@ method_exists($obj, $method); + return BaseXmlResponseTest::$functions->method_exists($obj, $method); } -class XmlResponseTest extends TestCase +class BaseXmlResponseTest extends TestCase { protected $testObj; public static $functions; public function setUp() { - $this->testObj = new TestXmlResponseTrait(); + $this->testObj = new BaseXmlResponse(); self::$functions = Mockery::mock(); } public function testStringify() { - $method = self::getMethod($this->testObj, 'stringify'); + $method = self::getNonPublicMethod($this->testObj, 'stringify'); $objWithToString = Mockery::mock()->shouldReceive('__toString')->andReturn('string from __toString'); self::$functions @@ -59,7 +58,7 @@ public function testStringify() public function testRemoveXmlFirstLine() { $xml = new SimpleXMLElement(''); - $method = self::getMethod($this->testObj, 'removeXmlFirstLine'); + $method = self::getNonPublicMethod($this->testObj, 'removeXmlFirstLine'); $this->assertNotContains('', $method->invoke($this->testObj, $xml->asXML())); $normalStr = 'some string'; @@ -72,7 +71,7 @@ public function testRemoveByXPath() $xml->addChild('cas:tag', '123'); $this->assertContains('cas:tag', $xml->asXML()); $this->assertContains('123', $xml->asXML()); - $method = self::getMethod($this->testObj, 'removeByXPath'); + $method = self::getNonPublicMethod($this->testObj, 'removeByXPath'); $method->invoke($this->testObj, $xml, 'cas:tag'); $this->assertNotContains('cas:tag', $xml->asXML()); $this->assertNotContains('123', $xml->asXML()); @@ -80,10 +79,26 @@ public function testRemoveByXPath() public function testGetRootNode() { - $method = self::getMethod($this->testObj, 'getRootNode'); + $method = self::getNonPublicMethod($this->testObj, 'getRootNode'); $this->assertContains( '', $method->invoke($this->testObj)->asXML() ); } + + public function testToResponse() + { + $resp = Mockery::mock(BaseXmlResponse::class, []) + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('removeXmlFirstLine') + ->andReturn('some string') + ->getMock(); + + $ret = $resp->toResponse(); + $this->assertInstanceOf(Response::class, $ret); + $this->assertEquals(200, $ret->getStatusCode()); + $this->assertEquals('some string', $ret->getContent()); + $this->assertEquals('application/xml', $ret->headers->get('Content-Type')); + } } diff --git a/tests/Responses/JsonAuthenticationFailureResponseTest.php b/tests/Responses/JsonAuthenticationFailureResponseTest.php new file mode 100644 index 0000000..2ae7ca9 --- /dev/null +++ b/tests/Responses/JsonAuthenticationFailureResponseTest.php @@ -0,0 +1,39 @@ +setFailure('code1', 'desc1'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['authenticationFailure' => ['code' => 'code1', 'description' => 'desc1']]], + $data + ); + + $resp->setFailure('code2', 'desc2'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['authenticationFailure' => ['code' => 'code2', 'description' => 'desc2']]], + $data + ); + } + + protected function getData(JsonAuthenticationFailureResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'data'); + + return $property->getValue($resp); + } +} diff --git a/tests/Responses/JsonAuthenticationSuccessResponseTest.php b/tests/Responses/JsonAuthenticationSuccessResponseTest.php new file mode 100644 index 0000000..301efbe --- /dev/null +++ b/tests/Responses/JsonAuthenticationSuccessResponseTest.php @@ -0,0 +1,77 @@ +setUser('test name'); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['user' => 'test name']]], $data); + $resp->setUser('test name2'); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['user' => 'test name2']]], $data); + } + + public function testSetProxyGrantingTicket() + { + $resp = new JsonAuthenticationSuccessResponse(); + $resp->setProxyGrantingTicket('ticket1'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['authenticationSuccess' => ['proxyGrantingTicket' => 'ticket1']]], + $data + ); + $resp->setProxyGrantingTicket('ticket2'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['authenticationSuccess' => ['proxyGrantingTicket' => 'ticket2']]], + $data + ); + } + + public function testSetProxies() + { + $resp = new JsonAuthenticationSuccessResponse(); + $proxies1 = ['http://proxy1.com', 'http://proxy2.com']; + $resp->setProxies($proxies1); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['proxies' => $proxies1]]], $data); + + $proxies2 = ['http://proxy3.com', 'http://proxy4.com']; + $resp->setProxies($proxies2); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['proxies' => $proxies2]]], $data); + } + + public function testSetAttributes() + { + $resp = new JsonAuthenticationSuccessResponse(); + $attr1 = ['key1' => 'value1', 'key2' => 'value2']; + $resp->setAttributes($attr1); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['attributes' => $attr1]]], $data); + + $attr2 = ['key3' => 'value3', 'key4' => 'value4']; + $resp->setAttributes($attr2); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['authenticationSuccess' => ['attributes' => $attr2]]], $data); + } + + protected function getData(JsonAuthenticationSuccessResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'data'); + + return $property->getValue($resp); + } +} diff --git a/tests/Responses/XmlAuthenticationFailureResponseTest.php b/tests/Responses/XmlAuthenticationFailureResponseTest.php new file mode 100644 index 0000000..af281d0 --- /dev/null +++ b/tests/Responses/XmlAuthenticationFailureResponseTest.php @@ -0,0 +1,41 @@ +getXML($resp); + $this->assertNotContains('cas:authenticationFailure', $content); + $resp->setFailure('code1', 'desc1'); + $content = $this->getXML($resp); + $this->assertContains('cas:authenticationFailure', $content); + $this->assertContains('code1', $content); + $this->assertContains('desc1', $content); + $resp->setFailure('code2', 'desc2'); + $content = $this->getXML($resp); + $this->assertContains('cas:authenticationFailure', $content); + $this->assertNotContains('code1', $content); + $this->assertContains('code2', $content); + $this->assertNotContains('desc1', $content); + $this->assertContains('desc2', $content); + } + + protected function getXML(XmlAuthenticationFailureResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'node'); + $node = $property->getValue($resp); + + return $node->asXML(); + } +} diff --git a/tests/Responses/XmlAuthenticationSuccessResponseTest.php b/tests/Responses/XmlAuthenticationSuccessResponseTest.php index 64c98b0..cdccd0a 100644 --- a/tests/Responses/XmlAuthenticationSuccessResponseTest.php +++ b/tests/Responses/XmlAuthenticationSuccessResponseTest.php @@ -9,6 +9,7 @@ namespace Leo108\CAS\Responses; use Mockery; +use Symfony\Component\HttpFoundation\Response; use TestCase; class XmlAuthenticationSuccessResponseTest extends TestCase @@ -16,15 +17,15 @@ class XmlAuthenticationSuccessResponseTest extends TestCase public function testSetUser() { $resp = new XmlAuthenticationSuccessResponse(); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertNotContains('cas:user', $content); $resp->setUser('test'); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:user', $content); $this->assertContains('test', $content); $resp->setUser('username_2'); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:user', $content); $this->assertNotContains('test', $content); $this->assertContains('username_2', $content); @@ -33,18 +34,18 @@ public function testSetUser() public function testSetProxies() { $resp = new XmlAuthenticationSuccessResponse(); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertNotContains('cas:proxies', $content); $this->assertNotContains('cas:proxy', $content); $resp->setProxies(['http://proxy1.com']); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:proxies', $content); $this->assertContains('cas:proxy', $content); $this->assertContains('http://proxy1.com', $content); $resp->setProxies(['http://proxy2.com', 'http://proxy3.com']); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:proxies', $content); $this->assertContains('cas:proxy', $content); $this->assertNotContains('http://proxy1.com', $content); @@ -60,16 +61,16 @@ public function testSetAttributes() ->shouldReceive('stringify') ->andReturn('string') ->getMock(); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertNotContains('cas:attributes', $content); $resp->setAttributes(['key1' => 'value1']); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:attributes', $content); $this->assertContains('cas:key1', $content); $resp->setAttributes(['key2' => 'value2', 'key3' => 'value3']); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:attributes', $content); $this->assertNotContains('cas:key1', $content); $this->assertContains('cas:key2', $content); @@ -79,25 +80,28 @@ public function testSetAttributes() public function testSetProxyGrantingTicket() { $resp = new XmlAuthenticationSuccessResponse(); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertNotContains('cas:proxyGrantingTicket', $content); $ticket1 = 'ticket1'; $ticket2 = 'ticket2'; $resp->setProxyGrantingTicket($ticket1); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:proxyGrantingTicket', $content); $this->assertContains($ticket1, $content); $resp->setProxyGrantingTicket($ticket2); - $content = $resp->toResponse()->getContent(); + $content = $this->getXML($resp); $this->assertContains('cas:proxyGrantingTicket', $content); $this->assertNotContains($ticket1, $content); $this->assertContains($ticket2, $content); } - public function testToResponse() + protected function getXML(XmlAuthenticationSuccessResponse $resp) { - $this->markTestIncomplete(); + $property = self::getNonPublicProperty($resp, 'node'); + $node = $property->getValue($resp); + + return $node->asXML(); } } diff --git a/tests/TestCase.php b/tests/TestCase.php index 8862b4c..96596e5 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -77,7 +77,7 @@ public function migrate() (new $migrationClass())->up(); } - protected static function getMethod($obj, $name) + protected static function getNonPublicMethod($obj, $name) { $class = new ReflectionClass($obj); $method = $class->getMethod($name); @@ -85,4 +85,13 @@ protected static function getMethod($obj, $name) return $method; } + + protected static function getNonPublicProperty($obj, $name) + { + $class = new ReflectionClass($obj); + $property = $class->getProperty($name); + $property->setAccessible(true); + + return $property; + } } diff --git a/tests/_support/TestXmlResponseTrait.php b/tests/_support/TestXmlResponseTrait.php deleted file mode 100644 index 650e6da..0000000 --- a/tests/_support/TestXmlResponseTrait.php +++ /dev/null @@ -1,12 +0,0 @@ - Date: Tue, 25 Oct 2016 16:00:53 +0800 Subject: [PATCH 05/17] .scrutinizer.yml --- .scrutinizer.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index 10bc037..dc2cbf3 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -11,3 +11,7 @@ build: coverage: file: '.coverage.xml' format: 'clover' +filter: + excluded_paths: + - "database/" + - "config/" From a8ed886cb7b583b3e9e75ffe3a7871a4b571e977 Mon Sep 17 00:00:00 2001 From: leo108 Date: Wed, 26 Oct 2016 18:10:20 +0800 Subject: [PATCH 06/17] add proxy --- composer.json | 3 +- ...24_create_proxy_granting_tickets_table.php | 38 + ...25_083620_add_proxies_to_tickets_table.php | 31 + ...2220_add_allow_proxy_to_services_table.php | 31 + .../Responses/ProxyFailureResponse.php | 20 + .../Responses/ProxySuccessResponse.php | 18 + src/Exceptions/CAS/CasException.php | 1 + src/Http/Controllers/ValidateController.php | 166 ++++- src/Http/routes.php | 15 +- src/Models/PGTicket.php | 64 ++ src/Models/Service.php | 8 + src/Models/Ticket.php | 39 +- src/Repositories/PGTicketRepository.php | 113 +++ src/Repositories/TicketRepository.php | 53 +- src/Responses/JsonProxyFailureResponse.php | 35 + src/Responses/JsonProxySuccessResponse.php | 27 + src/Responses/XmlProxyFailureResponse.php | 28 + src/Responses/XmlProxySuccessResponse.php | 50 ++ src/Services/PGTCaller.php | 47 ++ src/Services/TicketGenerator.php | 46 ++ .../Controllers/ValidateControllerTest.php | 680 ++++++++++++++++-- tests/Repositories/TicketRepositoryTest.php | 77 +- 22 files changed, 1439 insertions(+), 151 deletions(-) create mode 100644 database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php create mode 100644 database/migrations/2016_10_25_083620_add_proxies_to_tickets_table.php create mode 100644 database/migrations/2016_10_25_092220_add_allow_proxy_to_services_table.php create mode 100644 src/Contracts/Responses/ProxyFailureResponse.php create mode 100644 src/Contracts/Responses/ProxySuccessResponse.php create mode 100644 src/Models/PGTicket.php create mode 100644 src/Repositories/PGTicketRepository.php create mode 100644 src/Responses/JsonProxyFailureResponse.php create mode 100644 src/Responses/JsonProxySuccessResponse.php create mode 100644 src/Responses/XmlProxyFailureResponse.php create mode 100644 src/Responses/XmlProxySuccessResponse.php create mode 100644 src/Services/PGTCaller.php create mode 100644 src/Services/TicketGenerator.php diff --git a/composer.json b/composer.json index 1cc1fba..f1c7863 100644 --- a/composer.json +++ b/composer.json @@ -12,7 +12,8 @@ "require": { "php": ">=5.5.9", "ext-dom": "*", - "laravel/framework": "5.1.*|5.2.*|5.3.*" + "laravel/framework": "5.1.*|5.2.*|5.3.*", + "guzzlehttp/guzzle": "^6.2" }, "require-dev": { "mockery/mockery": "0.9.*", diff --git a/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php new file mode 100644 index 0000000..f2424d8 --- /dev/null +++ b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php @@ -0,0 +1,38 @@ +increments('id'); + $table->string('ticket', 32)->unique(); + $table->string('pgt_url', 1024); + $table->integer('service_id')->unsigned(); + $table->integer('user_id')->unsigned(); + $table->text('proxies')->nullable(); + $table->timestamp('created_at')->nullable(); + $table->timestamp('expire_at')->nullable(); + $table->foreign('service_id')->references('id')->on('cas_services'); + $table->foreign('user_id')->references(config('cas.user_table.id'))->on(config('cas.user_table.name')); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::drop('proxy_granting_tickets'); + } +} diff --git a/database/migrations/2016_10_25_083620_add_proxies_to_tickets_table.php b/database/migrations/2016_10_25_083620_add_proxies_to_tickets_table.php new file mode 100644 index 0000000..77f2023 --- /dev/null +++ b/database/migrations/2016_10_25_083620_add_proxies_to_tickets_table.php @@ -0,0 +1,31 @@ +text('proxies')->nullable()->after('user_id'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('cas_tickets', function (Blueprint $table) { + $table->dropColumn('proxies'); + }); + } +} diff --git a/database/migrations/2016_10_25_092220_add_allow_proxy_to_services_table.php b/database/migrations/2016_10_25_092220_add_allow_proxy_to_services_table.php new file mode 100644 index 0000000..3f995ce --- /dev/null +++ b/database/migrations/2016_10_25_092220_add_allow_proxy_to_services_table.php @@ -0,0 +1,31 @@ +boolean('allow_proxy')->default(false)->after('name'); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('cas_services', function (Blueprint $table) { + $table->dropColumn('allow_proxy'); + }); + } +} diff --git a/src/Contracts/Responses/ProxyFailureResponse.php b/src/Contracts/Responses/ProxyFailureResponse.php new file mode 100644 index 0000000..9834216 --- /dev/null +++ b/src/Contracts/Responses/ProxyFailureResponse.php @@ -0,0 +1,20 @@ +'; + /** + * @var PGTicketRepository + */ + protected $pgTicketRepository; + + /** + * @var TicketGenerator + */ + protected $ticketGenerator; + + /** + * @var PGTCaller + */ + protected $pgtCaller; /** * ValidateController constructor. - * @param TicketLocker $ticketLocker - * @param TicketRepository $ticketRepository + * @param TicketLocker $ticketLocker + * @param TicketRepository $ticketRepository + * @param PGTicketRepository $pgTicketRepository + * @param TicketGenerator $ticketGenerator + * @param PGTCaller $pgtCaller */ - public function __construct(TicketLocker $ticketLocker, TicketRepository $ticketRepository) - { - $this->ticketLocker = $ticketLocker; - $this->ticketRepository = $ticketRepository; + public function __construct( + TicketLocker $ticketLocker, + TicketRepository $ticketRepository, + PGTicketRepository $pgTicketRepository, + TicketGenerator $ticketGenerator, + PGTCaller $pgtCaller + ) { + $this->ticketLocker = $ticketLocker; + $this->ticketRepository = $ticketRepository; + $this->pgTicketRepository = $pgTicketRepository; + $this->ticketGenerator = $ticketGenerator; + $this->pgtCaller = $pgtCaller; } public function v1ValidateAction(Request $request) @@ -69,28 +100,68 @@ public function v1ValidateAction(Request $request) return new Response('yes'); } - public function v2ValidateAction(Request $request) + public function v2ServiceValidateAction(Request $request) { - return $this->casValidate($request, false); + return $this->casValidate($request, false, false); } - public function v3ValidateAction(Request $request) + public function v3ServiceValidateAction(Request $request) { - return $this->casValidate($request, true); + return $this->casValidate($request, true, false); + } + + public function v2ProxyValidateAction(Request $request) + { + return $this->casValidate($request, false, true); + } + + public function v3ProxyValidateAction(Request $request) + { + return $this->casValidate($request, true, true); + } + + public function proxyAction(Request $request) + { + $pgt = $request->get('pgt', ''); + $target = $request->get('targetService', ''); + $format = strtoupper($request->get('format', 'XML')); + + if (empty($pgt) || empty($target)) { + return $this->proxyFailureResponse( + CasException::INVALID_REQUEST, + 'param pgt and targetService can not be empty', + $format + ); + } + + $record = $this->pgTicketRepository->getByTicket($pgt); + try { + if (!$record) { + throw new CasException(CasException::INVALID_TICKET, 'ticket is not valid'); + } + $proxies = $record->proxies; + array_unshift($proxies, $record->pgt_url); + $ticket = $this->ticketRepository->applyTicket($record->user, $target, $proxies); + } catch (CasException $e) { + return $this->proxyFailureResponse($e->getCasErrorCode(), $e->getMessage(), $format); + } + + return $this->proxySuccessResponse($ticket->ticket, $format); } /** * @param Request $request * @param bool $returnAttr + * @param bool $allowProxy * @return Response */ - protected function casValidate(Request $request, $returnAttr) + protected function casValidate(Request $request, $returnAttr, $allowProxy) { $service = $request->get('service', ''); $ticket = $request->get('ticket', ''); $format = strtoupper($request->get('format', 'XML')); if (empty($service) || empty($ticket)) { - return $this->failureResponse( + return $this->authFailureResponse( CasException::INVALID_REQUEST, 'param service and ticket can not be empty', $format @@ -98,12 +169,12 @@ protected function casValidate(Request $request, $returnAttr) } if (!$this->lockTicket($ticket)) { - return $this->failureResponse(CasException::INTERNAL_ERROR, 'try to lock ticket failed', $format); + return $this->authFailureResponse(CasException::INTERNAL_ERROR, 'try to lock ticket failed', $format); } $record = $this->ticketRepository->getByTicket($ticket); try { - if (!$record) { + if (!$record || (!$allowProxy && $record->isProxy())) { throw new CasException(CasException::INVALID_TICKET, 'ticket is not valid'); } @@ -115,14 +186,36 @@ protected function casValidate(Request $request, $returnAttr) $record instanceof Ticket && $this->ticketRepository->invalidTicket($record); $this->unlockTicket($ticket); - return $this->failureResponse($e->getCasErrorCode(), $e->getMessage(), $format); + return $this->authFailureResponse($e->getCasErrorCode(), $e->getMessage(), $format); + } + + $proxies = []; + if ($record->isProxy()) { + $proxies = $record->proxies; } + + $user = $record->user; $this->ticketRepository->invalidTicket($record); $this->unlockTicket($ticket); + //handle pgt + $iou = null; + $pgtUrl = $request->get('pgtUrl', ''); + if ($pgtUrl) { + try { + $pgTicket = $this->pgTicketRepository->applyTicket($user, $pgtUrl, $proxies); + $iou = $this->ticketGenerator->generateOne(config('cas.ticket_len', 32), 'PGTIOU-'); + if (!$this->pgtCaller->call($pgtUrl, $pgTicket, $iou)) { + $iou = null; + } + } catch (CasException $e) { + $iou = null; + } + } + $attr = $returnAttr ? $record->user->getCASAttributes() : []; - return $this->successResponse($record->user->getName(), $format, $attr); + return $this->authSuccessResponse($record->user->getName(), $format, $attr, $proxies, $iou); } /** @@ -133,7 +226,7 @@ protected function casValidate(Request $request, $returnAttr) * @param string|null $pgt * @return Response */ - protected function successResponse($username, $format, $attributes, $proxies = [], $pgt = null) + protected function authSuccessResponse($username, $format, $attributes, $proxies = [], $pgt = null) { if (strtoupper($format) === 'JSON') { $resp = app(JsonAuthenticationSuccessResponse::class); @@ -161,7 +254,7 @@ protected function successResponse($username, $format, $attributes, $proxies = [ * @param string $format * @return Response */ - protected function failureResponse($code, $description, $format) + protected function authFailureResponse($code, $description, $format) { if (strtoupper($format) === 'JSON') { $resp = app(JsonAuthenticationFailureResponse::class); @@ -173,6 +266,41 @@ protected function failureResponse($code, $description, $format) return $resp->toResponse(); } + /** + * @param string $ticket + * @param string $format + * @return Response + */ + protected function proxySuccessResponse($ticket, $format) + { + if (strtoupper($format) === 'JSON') { + $resp = app(JsonProxySuccessResponse::class); + } else { + $resp = app(XmlProxySuccessResponse::class); + } + $resp->setProxyTicket($ticket); + + return $resp->toResponse(); + } + + /** + * @param string $code + * @param string $description + * @param string $format + * @return Response + */ + protected function proxyFailureResponse($code, $description, $format) + { + if (strtoupper($format) === 'JSON') { + $resp = app(JsonProxyFailureResponse::class); + } else { + $resp = app(XmlProxyFailureResponse::class); + } + $resp->setFailure($code, $description); + + return $resp->toResponse(); + } + /** * @param string $ticket * @return bool diff --git a/src/Http/routes.php b/src/Http/routes.php index b701baa..301706b 100644 --- a/src/Http/routes.php +++ b/src/Http/routes.php @@ -14,11 +14,14 @@ function () { $auth = config('cas.middleware.auth'); $p = config('cas.router.name_prefix'); - Route::get('login', ['as' => $p.'login_page', 'uses' => 'SecurityController@showLogin']); - Route::post('login', ['as' => $p.'login_action', 'uses' => 'SecurityController@login']); - Route::get('logout', ['as' => $p.'logout', 'uses' => 'SecurityController@logout'])->middleware($auth); - Route::any('validate', ['as' => $p.'v1validate', 'uses' => 'ValidateController@v1ValidateAction']); - Route::any('serviceValidate', ['as' => $p.'v2validate', 'uses' => 'ValidateController@v2ValidateAction']); - Route::any('p3/serviceValidate', ['as' => $p.'v3validate', 'uses' => 'ValidateController@v3ValidateAction']); + Route::get('login', 'SecurityController@showLogin')->name($p.'login.get'); + Route::post('login', 'SecurityController@login')->name($p.'login.post'); + Route::get('logout', 'SecurityController@logout')->name($p.'logout')->middleware($auth); + Route::any('validate', 'ValidateController@v1ValidateAction')->name($p.'v1.validate'); + Route::any('serviceValidate', 'ValidateController@v2ServiceValidateAction')->name($p.'v2.validate.service'); + Route::any('proxyValidate', 'ValidateController@v2ProxyValidateAction')->name($p.'v2.validate.proxy'); + Route::any('proxy', 'ValidateController@proxyAction')->name($p.'proxy'); + Route::any('p3/serviceValidate', 'ValidateController@v3ServiceValidateAction')->name($p.'v3.validate.service'); + Route::any('p3/proxyValidate', 'ValidateController@v3ProxyValidateAction')->name($p.'v3.validate.proxy'); } ); diff --git a/src/Models/PGTicket.php b/src/Models/PGTicket.php new file mode 100644 index 0000000..83e1ad2 --- /dev/null +++ b/src/Models/PGTicket.php @@ -0,0 +1,64 @@ +attributes['proxies'], true); + if (!$ret) { + return []; + } + + return $ret; + } + + public function setProxiesAttribute($value) + { + $this->attributes['proxies'] = json_encode($value); + } + + public function isExpired() + { + return (new Carbon($this->expire_at))->getTimestamp() < time(); + } + + public function service() + { + return $this->belongsTo(Service::class); + } + + public function user() + { + return $this->belongsTo(config('cas.user_table.model'), 'user_id', config('cas.user_table.id')); + } +} diff --git a/src/Models/Service.php b/src/Models/Service.php index 489f2f7..cb8742d 100644 --- a/src/Models/Service.php +++ b/src/Models/Service.php @@ -10,6 +10,14 @@ use Illuminate\Database\Eloquent\Model; +/** + * Class Service + * @package Leo108\CAS\Models + * + * @property string $name + * @property boolean $allow_proxy + * @property boolean $enabled + */ class Service extends Model { protected $table = 'cas_services'; diff --git a/src/Models/Ticket.php b/src/Models/Ticket.php index 082a353..7b87757 100644 --- a/src/Models/Ticket.php +++ b/src/Models/Ticket.php @@ -10,12 +10,44 @@ use Carbon\Carbon; use Illuminate\Database\Eloquent\Model; +use Leo108\CAS\Contracts\Models\UserModel; +/** + * Class Ticket + * @package Leo108\CAS\Models + * + * @property integer $id + * @property string $ticket + * @property string $service_url + * @property integer $service_id + * @property integer $user_id + * @property array $proxies + * @property integer $created_at + * @property integer $expire_at + * @property UserModel $user + */ class Ticket extends Model { protected $table = 'cas_tickets'; public $timestamps = false; - protected $fillable = ['ticket', 'service_url', 'expire_at', 'created_at']; + protected $fillable = ['ticket', 'service_url', 'proxies', 'expire_at', 'created_at']; + + public function getProxiesAttribute() + { + if (!$this->isProxy()) { + return null; + } + + return json_decode($this->attributes['proxies'], true); + } + + public function setProxiesAttribute($value) + { + if ($this->id && !$this->isProxy()) { + return; + } + $this->attributes['proxies'] = json_encode($value); + } public function isExpired() { @@ -31,4 +63,9 @@ public function user() { return $this->belongsTo(config('cas.user_table.model'), 'user_id', config('cas.user_table.id')); } + + public function isProxy() + { + return !is_null($this->attributes['proxies']); + } } diff --git a/src/Repositories/PGTicketRepository.php b/src/Repositories/PGTicketRepository.php new file mode 100644 index 0000000..15323eb --- /dev/null +++ b/src/Repositories/PGTicketRepository.php @@ -0,0 +1,113 @@ +pgTicket = $pgTicket; + $this->serviceRepository = $serviceRepository; + $this->ticketGenerator = $ticketGenerator; + } + + /** + * @param string $ticket + * @param bool $checkExpired + * @return null|PGTicket + */ + public function getByTicket($ticket, $checkExpired = true) + { + $record = $this->pgTicket->where('ticket', $ticket)->first(); + if (!$record) { + return null; + } + + return ($checkExpired && $record->isExpired()) ? null : $record; + } + + /** + * @param UserModel $user + * @param string $pgtUrl + * @param array $proxies + * @return PGTicket + * @throws CasException + */ + public function applyTicket(UserModel $user, $pgtUrl, $proxies = []) + { + $service = $this->serviceRepository->getServiceByUrl($pgtUrl); + if (!$service || !$service->allow_proxy) { + throw new CasException(CasException::UNAUTHORIZED_SERVICE_PROXY); + } + + $ticket = $this->getAvailableTicket(config('cas.ticket_len', 32)); + if ($ticket === false) { + throw new CasException(CasException::INTERNAL_ERROR, 'apply proxy-granting ticket failed'); + } + $record = $this->pgTicket->newInstance( + [ + 'ticket' => $ticket, + 'expire_at' => new Carbon(sprintf('+%dsec', config('cas.ticket_expire', 300))), + 'created_at' => new Carbon(), + 'pgt_url' => $pgtUrl, + 'proxies' => $proxies, + ] + ); + $record->user()->associate($user->getEloquentModel()); + $record->service()->associate($service); + $record->save(); + + return $record; + } + + /** + * @param string $totalLength + * @return string|false + */ + protected function getAvailableTicket($totalLength) + { + return $this->ticketGenerator->generate( + $totalLength, + 'PGT-', + function ($ticket) { + return $this->getByTicket($ticket, false); + }, + 10 + ); + } +} diff --git a/src/Repositories/TicketRepository.php b/src/Repositories/TicketRepository.php index 01d998f..2d81af9 100644 --- a/src/Repositories/TicketRepository.php +++ b/src/Repositories/TicketRepository.php @@ -12,6 +12,7 @@ use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Ticket; +use Leo108\CAS\Services\TicketGenerator; class TicketRepository { @@ -25,31 +26,38 @@ class TicketRepository */ protected $serviceRepository; + /** + * @var TicketGenerator + */ + protected $ticketGenerator; + /** * TicketRepository constructor. * @param Ticket $ticket * @param ServiceRepository $serviceRepository + * @param TicketGenerator $ticketGenerator */ - public function __construct(Ticket $ticket, ServiceRepository $serviceRepository) + public function __construct(Ticket $ticket, ServiceRepository $serviceRepository, TicketGenerator $ticketGenerator) { $this->ticket = $ticket; $this->serviceRepository = $serviceRepository; + $this->ticketGenerator = $ticketGenerator; } - /** * @param UserModel $user * @param string $serviceUrl + * @param array $proxies * @throws CasException * @return \Leo108\CAS\Models\Ticket */ - public function applyTicket(UserModel $user, $serviceUrl) + public function applyTicket(UserModel $user, $serviceUrl, $proxies = []) { $service = $this->serviceRepository->getServiceByUrl($serviceUrl); if (!$service) { throw new CasException(CasException::INVALID_SERVICE); } - $ticket = $this->getAvailableTicket(config('cas.ticket_len', 32)); + $ticket = $this->getAvailableTicket(config('cas.ticket_len', 32), empty($proxies) ? 'ST-' : 'PT-'); if ($ticket === false) { throw new CasException(CasException::INTERNAL_ERROR, 'apply ticket failed'); } @@ -59,6 +67,7 @@ public function applyTicket(UserModel $user, $serviceUrl) 'expire_at' => new Carbon(sprintf('+%dsec', config('cas.ticket_expire', 300))), 'created_at' => new Carbon(), 'service_url' => $serviceUrl, + 'proxies' => $proxies, ] ); $record->user()->associate($user->getEloquentModel()); @@ -71,16 +80,16 @@ public function applyTicket(UserModel $user, $serviceUrl) /** * @param string $ticket * @param bool $checkExpired - * @return bool|Ticket + * @return null|Ticket */ public function getByTicket($ticket, $checkExpired = true) { $record = $this->ticket->where('ticket', $ticket)->first(); if (!$record) { - return false; + return null; } - return ($checkExpired && $record->isExpired()) ? false : $record; + return ($checkExpired && $record->isExpired()) ? null : $record; } /** @@ -93,27 +102,19 @@ public function invalidTicket(Ticket $ticket) } /** - * @param $totalLength + * @param integer $totalLength + * @param string $prefix * @return bool|string */ - protected function getAvailableTicket($totalLength) + protected function getAvailableTicket($totalLength, $prefix) { - $prefix = 'ST-'; - $ticket = false; - $flag = false; - for ($i = 0; $i < 10; $i++) { - $str = bin2hex(random_bytes($totalLength)); - $ticket = $prefix.substr($str, 0, $totalLength - strlen($prefix)); - if (!$this->getByTicket($ticket, false)) { - $flag = true; - break; - } - } - - if (!$flag) { - return false; - } - - return $ticket; + return $this->ticketGenerator->generate( + $totalLength, + $prefix, + function ($ticket) { + return $this->getByTicket($ticket, false); + }, + 10 + ); } } \ No newline at end of file diff --git a/src/Responses/JsonProxyFailureResponse.php b/src/Responses/JsonProxyFailureResponse.php new file mode 100644 index 0000000..1488aeb --- /dev/null +++ b/src/Responses/JsonProxyFailureResponse.php @@ -0,0 +1,35 @@ +data = ['serviceResponse' => ['proxyFailure' => []]]; + } + + /** + * @param string $code + * @param string $description + * @return $this + */ + public function setFailure($code, $description) + { + $this->data['serviceResponse']['proxyFailure']['code'] = $code; + $this->data['serviceResponse']['proxyFailure']['description'] = $description; + + return $this; + } +} diff --git a/src/Responses/JsonProxySuccessResponse.php b/src/Responses/JsonProxySuccessResponse.php new file mode 100644 index 0000000..7e72f0a --- /dev/null +++ b/src/Responses/JsonProxySuccessResponse.php @@ -0,0 +1,27 @@ +data = ['serviceResponse' => ['proxySuccess' => []]]; + } + + public function setProxyTicket($ticket) + { + $this->data['serviceResponse']['proxySuccess']['proxyTicket'] = $ticket; + } +} diff --git a/src/Responses/XmlProxyFailureResponse.php b/src/Responses/XmlProxyFailureResponse.php new file mode 100644 index 0000000..1b266bc --- /dev/null +++ b/src/Responses/XmlProxyFailureResponse.php @@ -0,0 +1,28 @@ +removeByXPath($this->node, 'cas:proxyFailure'); + $authNode = $this->node->addChild('cas:proxyFailure', $description); + $authNode->addAttribute('code', $code); + + return $this; + } +} diff --git a/src/Responses/XmlProxySuccessResponse.php b/src/Responses/XmlProxySuccessResponse.php new file mode 100644 index 0000000..9aa79ee --- /dev/null +++ b/src/Responses/XmlProxySuccessResponse.php @@ -0,0 +1,50 @@ +node->addChild('cas:proxySuccess'); + } + + /** + * @param string $ticket + * @return $this + */ + public function setProxyTicket($ticket) + { + $proxyNode = $this->getProxyNode(); + $this->removeByXPath($proxyNode, 'cas:proxySuccess'); + $proxyNode->addChild('cas:proxySuccess', $ticket); + + return $this; + } + + /** + * @return SimpleXMLElement + */ + public function getProxyNode() + { + $authNodes = $this->node->xpath('cas:proxySuccess'); + if (count($authNodes) < 1) { + return $this->node->addChild('cas:proxySuccess'); + } + + return $authNodes[0]; + } +} diff --git a/src/Services/PGTCaller.php b/src/Services/PGTCaller.php new file mode 100644 index 0000000..f36bbe4 --- /dev/null +++ b/src/Services/PGTCaller.php @@ -0,0 +1,47 @@ +client = $client; + } + + public function call($pgtUrl, $pgt, $pgtiou) + { + $query = http_build_query( + [ + 'pgtId' => $pgt, + 'pgtIou' => $pgtiou, + ] + ); + parse_str(parse_url($pgtUrl, PHP_URL_QUERY), $originQuery); + + try { + $res = $this->client->get($pgtUrl, ['query' => array_merge($originQuery, $query)]); + + return $res->getStatusCode() == 200; + } catch (\Exception $e) { + return false; + } + } +} diff --git a/src/Services/TicketGenerator.php b/src/Services/TicketGenerator.php new file mode 100644 index 0000000..4be163d --- /dev/null +++ b/src/Services/TicketGenerator.php @@ -0,0 +1,46 @@ +generateOne($totalLength, $prefix); + if (!call_user_func_array($checkFunc, [$ticket])) { + $flag = true; + break; + } + } + + if (!$flag) { + return false; + } + + return $ticket; + } + + public function generateOne($totalLength, $prefix) + { + return $prefix.Str::random($totalLength - strlen($prefix)); + } +} diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index e87dbfc..37c2aeb 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -13,11 +13,14 @@ use Leo108\CAS\Contracts\TicketLocker; use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Ticket; +use Leo108\CAS\Repositories\PGTicketRepository; use Leo108\CAS\Repositories\TicketRepository; use Leo108\CAS\Responses\JsonAuthenticationFailureResponse; use Leo108\CAS\Responses\JsonAuthenticationSuccessResponse; use Leo108\CAS\Responses\XmlAuthenticationFailureResponse; use Leo108\CAS\Responses\XmlAuthenticationSuccessResponse; +use Leo108\CAS\Services\PGTCaller; +use Leo108\CAS\Services\TicketGenerator; use SerializableModel; use SimpleXMLElement; use TestCase; @@ -40,9 +43,8 @@ public function setUp() app()->instance(TicketLocker::class, Mockery::mock(TicketLocker::class)); } - public function testV1ValidateAction() + public function testV1ValidateActionWithInvalidRequest() { - //invalid input $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['ticket', '']) @@ -54,8 +56,10 @@ public function testV1ValidateAction() $resp = app()->make(ValidateController::class)->v1ValidateAction($request); $this->assertInstanceOf(Response::class, $resp); $this->assertEquals('no', $resp->getOriginalContent()); + } - //lock failed + public function testV1ValidateActionWithLockFailed() + { $request = $this->getValidRequest(); $controller = Mockery::mock(ValidateController::class) ->makePartial() @@ -66,25 +70,30 @@ public function testV1ValidateAction() $resp = $controller->v1ValidateAction($request); $this->assertInstanceOf(Response::class, $resp); $this->assertEquals('no', $resp->getOriginalContent()); + } - //ticket not exists + public function testV1ValidateActionWithInvalidTicket() + { $request = $this->getValidRequest(); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('getByTicket') ->andReturnNull() ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('lockTicket') ->andReturn(true) ->shouldReceive('unlockTicket') ->getMock(); - $resp = $controller->v1ValidateAction($request); + $resp = $controller->v1ValidateAction($request); $this->assertInstanceOf(Response::class, $resp); $this->assertEquals('no', $resp->getOriginalContent()); + } - //ticket exists but service url mismatch + public function testV1ValidateActionWithValidTicketButServiceMismatch() + { $request = $this->getValidRequest(); $ticket = Mockery::mock(); $ticket->service_url = 'http//google.com'; @@ -92,18 +101,21 @@ public function testV1ValidateAction() ->shouldReceive('getByTicket') ->andReturn($ticket) ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('lockTicket') ->andReturn(true) ->shouldReceive('unlockTicket') ->getMock(); - $resp = $controller->v1ValidateAction($request); + $resp = $controller->v1ValidateAction($request); $this->assertInstanceOf(Response::class, $resp); $this->assertEquals('no', $resp->getOriginalContent()); + } - //valid + public function testV1ValidateActionWithValidTicketAndService() + { $request = $this->getValidRequest(); $ticket = Mockery::mock(Ticket::class) ->shouldReceive('getAttribute') @@ -115,216 +127,720 @@ public function testV1ValidateAction() ->andReturn($ticket) ->shouldReceive('invalidTicket') ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('lockTicket') ->andReturn(true) ->shouldReceive('unlockTicket') ->getMock(); - $resp = $controller->v1ValidateAction($request); + $resp = $controller->v1ValidateAction($request); $this->assertInstanceOf(Response::class, $resp); $this->assertEquals('yes', $resp->getOriginalContent()); } - public function testV2ValidateAction() + public function testV2ServiceValidateAction() + { + $controller = Mockery::mock(ValidateController::class) + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('casValidate') + ->andReturnUsing( + function ($request, $returnAttr, $allowProxy) { + $this->assertFalse($returnAttr); + $this->assertFalse($allowProxy); + + return 'casValidate called'; + } + ) + ->once() + ->getMock(); + $request = Mockery::mock(Request::class); + $this->assertEquals('casValidate called', $controller->v2ServiceValidateAction($request)); + } + + public function testV2ProxyValidateAction() { $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') ->andReturnUsing( - function ($request, $returnAttr) { + function ($request, $returnAttr, $allowProxy) { $this->assertFalse($returnAttr); + $this->assertTrue($allowProxy); + + return 'casValidate called'; + } + ) + ->once() + ->getMock(); + $request = Mockery::mock(Request::class); + $this->assertEquals('casValidate called', $controller->v2ProxyValidateAction($request)); + } + + public function testV3ServiceValidateAction() + { + $controller = Mockery::mock(ValidateController::class) + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('casValidate') + ->andReturnUsing( + function ($request, $returnAttr, $allowProxy) { + $this->assertTrue($returnAttr); + $this->assertFalse($allowProxy); return 'casValidate called'; } ) + ->once() ->getMock(); $request = Mockery::mock(Request::class); - $this->assertEquals('casValidate called', $controller->v2ValidateAction($request)); + $this->assertEquals('casValidate called', $controller->v3ServiceValidateAction($request)); } - public function testV3ValidateAction() + public function testV3ProxyValidateAction() { $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') ->andReturnUsing( - function ($request, $returnAttr) { + function ($request, $returnAttr, $allowProxy) { $this->assertTrue($returnAttr); + $this->assertTrue($allowProxy); return 'casValidate called'; } ) + ->once() ->getMock(); $request = Mockery::mock(Request::class); - $this->assertEquals('casValidate called', $controller->v3ValidateAction($request)); + $this->assertEquals('casValidate called', $controller->v3ProxyValidateAction($request)); + } + + public function testProxyActionWithInvalidRequest() + { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->with('pgt', '') + ->andReturn('') + ->once() + ->shouldReceive('get') + ->with('targetService', '') + ->andReturn('') + ->once() + ->shouldReceive('get') + ->with('format', 'XML') + ->andReturn('XML') + ->once() + ->getMock(); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('proxyFailureResponse') + ->andReturnUsing( + function ($code, $desc, $format) { + $this->assertEquals(CasException::INVALID_REQUEST, $code); + $this->assertEquals('param pgt and targetService can not be empty', $desc); + $this->assertEquals('XML', $format); + + return 'proxyFailureResponse called'; + } + ) + ->once() + ->getMock(); + $this->assertEquals('proxyFailureResponse called', $controller->proxyAction($request)); } - public function testCasValidate() + public function testCasValidateWithInvalidRequest() { - //invalid input - $request = Mockery::mock(Request::class) + $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['ticket', '']) ->andReturn('') + ->once() ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('') + ->once() ->shouldReceive('get') ->withArgs(['format', 'XML']) ->andReturn('JSON') + ->once() ->getMock(); - $ticketRepository = Mockery::mock(TicketRepository::class); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() - ->shouldReceive('failureResponse') + ->shouldReceive('authFailureResponse') ->andReturnUsing( function ($code, $desc, $format) { $this->assertEquals(CasException::INVALID_REQUEST, $code); $this->assertEquals('param service and ticket can not be empty', $desc); $this->assertEquals('JSON', $format); - return 'failureResponse called'; + return 'authFailureResponse called'; } ) + ->once() ->getMock(); - $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authFailureResponse called', $method->invokeArgs($controller, [$request, false, false])); + } - //lock ticket failed - $request = $this->getValidRequest(); - $ticketRepository = Mockery::mock(TicketRepository::class); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + public function testCasValidateAndLockTicketFailed() + { + $request = $this->getValidRequest(); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('lockTicket') ->andReturn(false) - ->shouldReceive('failureResponse') + ->once() + ->shouldReceive('authFailureResponse') ->andReturnUsing( function ($code, $desc, $format) { $this->assertEquals(CasException::INTERNAL_ERROR, $code); $this->assertEquals('try to lock ticket failed', $desc); $this->assertEquals('JSON', $format); - return 'failureResponse called'; + return 'authFailureResponse called'; } ) + ->once() ->getMock(); - $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authFailureResponse called', $method->invokeArgs($controller, [$request, false, false])); + } - //ticket not exists + public function testCasValidateWithInvalidTicket() + { $request = $this->getValidRequest(); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('getByTicket') ->andReturnNull() + ->once() ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('unlockTicket') + ->once() ->shouldReceive('lockTicket') ->andReturn(true) - ->shouldReceive('failureResponse') + ->once() + ->shouldReceive('authFailureResponse') ->andReturnUsing( function ($code, $desc, $format) { $this->assertEquals(CasException::INVALID_TICKET, $code); $this->assertEquals('ticket is not valid', $desc); $this->assertEquals('JSON', $format); - return 'failureResponse called'; + return 'authFailureResponse called'; } ) + ->once() ->getMock(); - $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authFailureResponse called', $method->invokeArgs($controller, [$request, false, false])); + } - //ticket exists but service url mismatch + public function testCasValidateWithValidTicketButServiceMismatch() + { $request = $this->getValidRequest(); $ticket = Mockery::mock(Ticket::class) ->shouldReceive('getAttribute') ->withArgs(['service_url']) ->andReturn('http://github.com') + ->once() + ->shouldReceive('isProxy') + ->andReturn(false) + ->once() ->getMock(); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('invalidTicket') + ->once() ->shouldReceive('getByTicket') ->andReturn($ticket) + ->once() ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('unlockTicket') + ->once() ->shouldReceive('lockTicket') ->andReturn(true) - ->shouldReceive('failureResponse') + ->once() + ->shouldReceive('authFailureResponse') ->andReturnUsing( function ($code, $desc, $format) { $this->assertEquals(CasException::INVALID_SERVICE, $code); $this->assertEquals('service is not valid', $desc); $this->assertEquals('JSON', $format); - return 'failureResponse called'; + return 'authFailureResponse called'; + } + ) + ->once() + ->getMock(); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authFailureResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidProxyTicketButNotAllowProxy() + { + $request = $this->getValidRequest(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(true) + ->once() + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('invalidTicket') + ->once() + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('authFailureResponse') + ->andReturnUsing( + function ($code, $desc, $format) { + $this->assertEquals(CasException::INVALID_TICKET, $code); + $this->assertEquals('ticket is not valid', $desc); + $this->assertEquals('JSON', $format); + + return 'authFailureResponse called'; + } + ) + ->once() + ->getMock(); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authFailureResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidProxyTicketAndAllowProxy() + { + $proxies = ['http://proxy1.com', 'http://proxy2.com']; + $request = $this->getValidRequest(''); + $user = Mockery::mock(User::class) + ->shouldReceive('getName') + ->andReturn('test_user') + ->once() + ->getMock(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(true) + ->once() + ->shouldReceive('getAttribute') + ->with('proxies') + ->andReturn($proxies) + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['service_url']) + ->andReturn('http://leo108.com') + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['user']) + ->andReturn($user) + ->times(2) + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('invalidTicket') + ->once() + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('authSuccessResponse') + ->andReturnUsing( + function ($name, $format, $attributes, $proxiesParam, $iou) use ($proxies) { + $this->assertEquals('test_user', $name); + $this->assertEmpty($attributes); + $this->assertEquals('JSON', $format); + $this->assertEquals($proxies, $proxiesParam); + $this->assertNull($iou); + + return 'authSuccessResponse called'; + } + ) + ->once() + ->getMock(); + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, true])); + } + + public function testCasValidateWithValidTicketAndServiceAndNoPgt() + { + $request = $this->getValidRequest(''); + $user = Mockery::mock(User::class) + ->shouldReceive('getName') + ->andReturn('test_user') + ->once() + ->getMock(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(false) + ->times(2) + ->shouldReceive('getAttribute') + ->withArgs(['service_url']) + ->andReturn('http://leo108.com') + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['user']) + ->andReturn($user) + ->times(2) + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->shouldReceive('invalidTicket') + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('authSuccessResponse') + ->andReturnUsing( + function ($name, $format, $attributes, $proxies, $iou) { + $this->assertEquals('test_user', $name); + $this->assertEmpty($attributes); + $this->assertEquals('JSON', $format); + $this->assertEmpty($proxies); + $this->assertNull($iou); + + return 'authSuccessResponse called'; + } + ) + ->once() + ->getMock(); + + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidTicketAndServiceAndPgtButApplyPGTFailed() + { + $request = $this->getValidRequest('http://app1.com/pgtCallback'); + $user = Mockery::mock(User::class) + ->shouldReceive('getName') + ->andReturn('test_user') + ->once() + ->getMock(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(false) + ->times(2) + ->shouldReceive('getAttribute') + ->withArgs(['service_url']) + ->andReturn('http://leo108.com') + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['user']) + ->andReturn($user) + ->times(2) + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->shouldReceive('invalidTicket') + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('applyTicket') + ->andThrow(new CasException(CasException::INTERNAL_ERROR)) + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('authSuccessResponse') + ->andReturnUsing( + function ($name, $format, $attributes, $proxies, $iou) { + $this->assertEquals('test_user', $name); + $this->assertEmpty($attributes); + $this->assertEquals('JSON', $format); + $this->assertEmpty($proxies); + $this->assertNull($iou); + + return 'authSuccessResponse called'; } ) + ->once() ->getMock(); - $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('failureResponse called', $method->invokeArgs($controller, [$request, false])); - //normal - $request = $this->getValidRequest(); - $user = Mockery::mock(User::class) - ->shouldReceive('getCASAttributes') - ->andReturn([]) + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlFailed() + { + $request = $this->getValidRequest('http://app1.com/pgtCallback'); + $user = Mockery::mock(User::class) ->shouldReceive('getName') ->andReturn('test_user') + ->once() ->getMock(); - $ticket = Mockery::mock(Ticket::class); - $ticket->shouldReceive('getAttribute') + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(false) + ->times(2) + ->shouldReceive('getAttribute') ->withArgs(['service_url']) ->andReturn('http://leo108.com') + ->once() ->shouldReceive('getAttribute') ->withArgs(['user']) ->andReturn($user) + ->times(2) ->getMock(); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('getByTicket') ->andReturn($ticket) + ->once() ->shouldReceive('invalidTicket') + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('applyTicket') + ->andReturn('some string') + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); + $ticketGenerator = Mockery::mock(TicketGenerator::class) + ->shouldReceive('generateOne') + ->andReturn('pgtiou string') + ->once() ->getMock(); - $controller = Mockery::mock(ValidateController::class, [app(TicketLocker::class), $ticketRepository]) + app()->instance(TicketGenerator::class, $ticketGenerator); + $pgtCaller = Mockery::mock(PGTCaller::class) + ->shouldReceive('call') + ->andReturn(false) + ->once() + ->getMock(); + app()->instance(PGTCaller::class, $pgtCaller); + $controller = $this->initController() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('lockTicket') ->andReturn(true) + ->once() ->shouldReceive('unlockTicket') - ->shouldReceive('successResponse') + ->once() + ->shouldReceive('authSuccessResponse') ->andReturnUsing( - function ($name, $format, $attributes) { + function ($name, $format, $attributes, $proxies, $iou) { $this->assertEquals('test_user', $name); $this->assertEmpty($attributes); $this->assertEquals('JSON', $format); + $this->assertEmpty($proxies); + $this->assertNull($iou); - return 'successResponse called'; + return 'authSuccessResponse called'; } ) + ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('successResponse called', $method->invokeArgs($controller, [$request, false])); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlSuccess() + { + $request = $this->getValidRequest('http://app1.com/pgtCallback'); + $user = Mockery::mock(User::class) + ->shouldReceive('getName') + ->andReturn('test_user') + ->once() + ->getMock(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(false) + ->times(2) + ->shouldReceive('getAttribute') + ->withArgs(['service_url']) + ->andReturn('http://leo108.com') + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['user']) + ->andReturn($user) + ->times(2) + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->shouldReceive('invalidTicket') + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('applyTicket') + ->andReturn('some string') + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); + $ticketGenerator = Mockery::mock(TicketGenerator::class) + ->shouldReceive('generateOne') + ->andReturn('pgtiou string') + ->once() + ->getMock(); + app()->instance(TicketGenerator::class, $ticketGenerator); + $pgtCaller = Mockery::mock(PGTCaller::class) + ->shouldReceive('call') + ->andReturn(true) + ->once() + ->getMock(); + app()->instance(PGTCaller::class, $pgtCaller); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('authSuccessResponse') + ->andReturnUsing( + function ($name, $format, $attributes, $proxies, $iou) { + $this->assertEquals('test_user', $name); + $this->assertEmpty($attributes); + $this->assertEquals('JSON', $format); + $this->assertEmpty($proxies); + $this->assertEquals('pgtiou string', $iou); + + return 'authSuccessResponse called'; + } + ) + ->once() + ->getMock(); + + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); + } + + public function testCasValidateWithValidProxyTicketAndServiceAndPgtButCallPgtUrlSuccess() + { + $request = $this->getValidRequest('http://app1.com/pgtCallback'); + $user = Mockery::mock(User::class) + ->shouldReceive('getName') + ->andReturn('test_user') + ->once() + ->getMock(); + $ticket = Mockery::mock(Ticket::class) + ->shouldReceive('isProxy') + ->andReturn(false) + ->times(2) + ->shouldReceive('getAttribute') + ->withArgs(['service_url']) + ->andReturn('http://leo108.com') + ->once() + ->shouldReceive('getAttribute') + ->withArgs(['user']) + ->andReturn($user) + ->times(2) + ->getMock(); + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn($ticket) + ->once() + ->shouldReceive('invalidTicket') + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('applyTicket') + ->andReturn('some string') + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); + $ticketGenerator = Mockery::mock(TicketGenerator::class) + ->shouldReceive('generateOne') + ->andReturn('pgtiou string') + ->once() + ->getMock(); + app()->instance(TicketGenerator::class, $ticketGenerator); + $pgtCaller = Mockery::mock(PGTCaller::class) + ->shouldReceive('call') + ->andReturn(true) + ->once() + ->getMock(); + app()->instance(PGTCaller::class, $pgtCaller); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('lockTicket') + ->andReturn(true) + ->once() + ->shouldReceive('unlockTicket') + ->once() + ->shouldReceive('authSuccessResponse') + ->andReturnUsing( + function ($name, $format, $attributes, $proxies, $iou) { + $this->assertEquals('test_user', $name); + $this->assertEmpty($attributes); + $this->assertEquals('JSON', $format); + $this->assertEmpty($proxies); + $this->assertEquals('pgtiou string', $iou); + + return 'authSuccessResponse called'; + } + ) + ->once() + ->getMock(); + + $method = self::getNonPublicMethod($controller, 'casValidate'); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); } public function testLockTicket() { - $locker = Mockery::mock(TicketLocker::class) + $locker = Mockery::mock(TicketLocker::class) ->shouldReceive('acquireLock') ->andReturn('acquireLock called') + ->once() ->getMock(); - $controller = Mockery::mock(ValidateController::class, [$locker, Mockery::mock(TicketRepository::class)]) + app()->instance(TicketLocker::class, $locker); + $controller = $this->initController() ->makePartial(); $method = self::getNonPublicMethod($controller, 'lockTicket'); $this->assertEquals('acquireLock called', $method->invokeArgs($controller, ['str', 30])); @@ -335,19 +851,20 @@ public function testUnlockTicket() $locker = Mockery::mock(TicketLocker::class) ->shouldReceive('releaseLock') ->andReturn('releaseLock called') + ->once() ->getMock(); app()->instance(TicketLocker::class, $locker); - $controller = Mockery::mock(ValidateController::class, [$locker, Mockery::mock(TicketRepository::class)]) + $controller = $this->initController() ->makePartial(); $method = self::getNonPublicMethod($controller, 'unlockTicket'); $this->assertEquals('releaseLock called', $method->invokeArgs($controller, ['str', 30])); } - public function testSuccessResponse() + public function testAuthSuccessResponse() { $controller = Mockery::mock(ValidateController::class) ->makePartial(); - $method = self::getNonPublicMethod($controller, 'successResponse'); + $method = self::getNonPublicMethod($controller, 'authSuccessResponse'); $name = 'test_name'; $attributes = [ @@ -391,11 +908,11 @@ public function testSuccessResponse() $method->invokeArgs($controller, ['test_name', 'XML', $attributes, $proxies, $pgt]); } - public function testFailureResponse() + public function testAuthFailureResponse() { $controller = Mockery::mock(ValidateController::class) ->makePartial(); - $method = self::getNonPublicMethod($controller, 'failureResponse'); + $method = self::getNonPublicMethod($controller, 'authFailureResponse'); $code = 'code'; $desc = 'desc'; $jsonResp = Mockery::mock(JsonAuthenticationFailureResponse::class) @@ -419,9 +936,9 @@ public function testFailureResponse() $method->invokeArgs($controller, [$code, $desc, 'XML']); } - protected function getValidRequest() + protected function getValidRequest($pgt = null) { - return Mockery::mock(Request::class) + $mock = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['ticket', '']) ->andReturn('ticket') @@ -430,7 +947,30 @@ protected function getValidRequest() ->andReturn('http://leo108.com') ->shouldReceive('get') ->withArgs(['format', 'XML']) - ->andReturn('JSON') - ->getMock(); + ->andReturn('JSON'); + if (!is_null($pgt)) { + $mock->shouldReceive('get') + ->withArgs(['pgtUrl', '']) + ->andReturn($pgt); + } + + return $mock->getMock(); + } + + /** + * @return Mockery\MockInterface + */ + protected function initController() + { + return Mockery::mock( + ValidateController::class, + [ + app(TicketLocker::class), + app(TicketRepository::class), + app(PGTicketRepository::class), + app(TicketGenerator::class), + app(PGTCaller::class), + ] + ); } } diff --git a/tests/Repositories/TicketRepositoryTest.php b/tests/Repositories/TicketRepositoryTest.php index 3be6321..a591517 100644 --- a/tests/Repositories/TicketRepositoryTest.php +++ b/tests/Repositories/TicketRepositoryTest.php @@ -11,6 +11,7 @@ use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Service; use Leo108\CAS\Models\Ticket; +use Leo108\CAS\Services\TicketGenerator; use Mockery; use ReflectionClass; use TestCase; @@ -51,7 +52,8 @@ public function testApplyTicket() ->shouldReceive('getServiceByUrl') ->andReturn($service) ->getMock(); - $ticketRepository = Mockery::mock(TicketRepository::class, [new Ticket(), $serviceRepository]) + app()->instance(ServiceRepository::class, $serviceRepository); + $ticketRepository = $this->initTicketRepository() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('getAvailableTicket') @@ -78,7 +80,8 @@ public function testApplyTicket() ->shouldReceive('getServiceByUrl') ->andReturn($service) ->getMock(); - $ticket = Mockery::mock(Ticket::class) + app()->instance(ServiceRepository::class, $serviceRepository); + $ticket = Mockery::mock(Ticket::class) ->shouldReceive('newInstance') ->andReturnUsing( function ($param) { @@ -93,7 +96,8 @@ function ($param) { } ) ->getMock(); - $ticketRepository = Mockery::mock(TicketRepository::class, [$ticket, $serviceRepository]) + app()->instance(Ticket::class, $ticket); + $ticketRepository = $this->initTicketRepository() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('getAvailableTicket') @@ -110,7 +114,7 @@ public function testGetByTicket() $ticket = Mockery::mock(Ticket::class); $ticket->shouldReceive('where->first')->andReturn(null); app()->instance(Ticket::class, $ticket); - $this->assertFalse(app()->make(TicketRepository::class)->getByTicket('what ever')); + $this->assertNull(app()->make(TicketRepository::class)->getByTicket('what ever')); $mockTicket = Mockery::mock(Ticket::class) ->shouldReceive('isExpired') @@ -120,9 +124,9 @@ public function testGetByTicket() $ticket = Mockery::mock(Ticket::class); $ticket->shouldReceive('where->first')->andReturn($mockTicket); app()->instance(Ticket::class, $ticket); - $this->assertNotFalse(app()->make(TicketRepository::class)->getByTicket('what ever', false)); - $this->assertNotFalse(app()->make(TicketRepository::class)->getByTicket('what ever')); - $this->assertFalse(app()->make(TicketRepository::class)->getByTicket('what ever')); + $this->assertNotNull(app()->make(TicketRepository::class)->getByTicket('what ever', false)); + $this->assertNotNull(app()->make(TicketRepository::class)->getByTicket('what ever')); + $this->assertNull(app()->make(TicketRepository::class)->getByTicket('what ever')); } public function testInvalidTicket() @@ -136,33 +140,50 @@ public function testInvalidTicket() public function testGetAvailableTicket() { - //normal - $ticketRepository = Mockery::mock(TicketRepository::class) - ->makePartial() - ->shouldReceive('getByTicket') - ->andReturn(false) - ->getMock(); - - $length = 32; - $ticket = self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, $length); - $this->assertNotFalse($ticket); - $this->assertEquals($length, strlen($ticket)); + $length = 32; + $prefix = 'ST-'; + $ticket = 'ticket string'; + $ticketGenerator = Mockery::mock(TicketGenerator::class) + ->shouldReceive('generate') + ->andReturnUsing( + function ($totalLength, $paramPrefix, callable $checkFunc, $maxRetry) use ($length, $prefix, $ticket) { + $this->assertEquals($length, $totalLength); + $this->assertEquals($prefix, $paramPrefix); + $this->assertEquals('getByTicket called', call_user_func_array($checkFunc, [$ticket])); + $this->assertEquals(10, $maxRetry); - //always get occupied ticket - $ticketRepository = Mockery::mock(TicketRepository::class) + return 'generate called'; + } + ) + ->once() + ->getMock(); + app()->instance(TicketGenerator::class, $ticketGenerator); + $ticketRepository = $this->initTicketRepository() ->makePartial() ->shouldReceive('getByTicket') - ->andReturn(true) + ->with($ticket, false) + ->andReturn('getByTicket called') + ->once() ->getMock(); - $this->assertFalse(self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke($ticketRepository, 32)); + + $this->assertEquals( + 'generate called', + self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke( + $ticketRepository, + $length, + $prefix + ) + ); } - protected static function getNonPublicMethod($obj, $name) + /** + * @return Mockery\MockInterface + */ + protected function initTicketRepository() { - $class = new ReflectionClass($obj); - $method = $class->getMethod($name); - $method->setAccessible(true); - - return $method; + return Mockery::mock( + TicketRepository::class, + [app(Ticket::class), app(ServiceRepository::class), app(TicketGenerator::class)] + ); } } From abe56445f9a06d07624e716b6fb4d5d86ca6a4e5 Mon Sep 17 00:00:00 2001 From: leo108 Date: Wed, 26 Oct 2016 18:35:27 +0800 Subject: [PATCH 07/17] small patches --- .scrutinizer.yml | 1 + src/Repositories/TicketRepository.php | 2 +- .../Controllers/ValidateControllerTest.php | 42 ++++++++++++++++++- tests/Repositories/TicketRepositoryTest.php | 1 - .../XmlAuthenticationSuccessResponseTest.php | 1 - 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/.scrutinizer.yml b/.scrutinizer.yml index dc2cbf3..d05c944 100644 --- a/.scrutinizer.yml +++ b/.scrutinizer.yml @@ -15,3 +15,4 @@ filter: excluded_paths: - "database/" - "config/" + - "tests/" diff --git a/src/Repositories/TicketRepository.php b/src/Repositories/TicketRepository.php index 2d81af9..9307797 100644 --- a/src/Repositories/TicketRepository.php +++ b/src/Repositories/TicketRepository.php @@ -104,7 +104,7 @@ public function invalidTicket(Ticket $ticket) /** * @param integer $totalLength * @param string $prefix - * @return bool|string + * @return string|false */ protected function getAvailableTicket($totalLength, $prefix) { diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index 37c2aeb..270aee6 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -21,8 +21,6 @@ use Leo108\CAS\Responses\XmlAuthenticationSuccessResponse; use Leo108\CAS\Services\PGTCaller; use Leo108\CAS\Services\TicketGenerator; -use SerializableModel; -use SimpleXMLElement; use TestCase; use Mockery; use User; @@ -254,6 +252,46 @@ function ($code, $desc, $format) { $this->assertEquals('proxyFailureResponse called', $controller->proxyAction($request)); } + public function testProxyActionWithInvalidTicket() + { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->with('pgt', '') + ->andReturn('pgt string') + ->once() + ->shouldReceive('get') + ->with('targetService', '') + ->andReturn('http://target.com') + ->once() + ->shouldReceive('get') + ->with('format', 'XML') + ->andReturn('XML') + ->once() + ->getMock(); + $pgtRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn(false) + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgtRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('proxyFailureResponse') + ->andReturnUsing( + function ($code, $desc, $format) { + $this->assertEquals(CasException::INVALID_TICKET, $code); + $this->assertEquals('ticket is not valid', $desc); + $this->assertEquals('XML', $format); + + return 'proxyFailureResponse called'; + } + ) + ->once() + ->getMock(); + $this->assertEquals('proxyFailureResponse called', $controller->proxyAction($request)); + } + public function testCasValidateWithInvalidRequest() { $request = Mockery::mock(Request::class) diff --git a/tests/Repositories/TicketRepositoryTest.php b/tests/Repositories/TicketRepositoryTest.php index a591517..625c5ec 100644 --- a/tests/Repositories/TicketRepositoryTest.php +++ b/tests/Repositories/TicketRepositoryTest.php @@ -13,7 +13,6 @@ use Leo108\CAS\Models\Ticket; use Leo108\CAS\Services\TicketGenerator; use Mockery; -use ReflectionClass; use TestCase; use User; diff --git a/tests/Responses/XmlAuthenticationSuccessResponseTest.php b/tests/Responses/XmlAuthenticationSuccessResponseTest.php index cdccd0a..56612dc 100644 --- a/tests/Responses/XmlAuthenticationSuccessResponseTest.php +++ b/tests/Responses/XmlAuthenticationSuccessResponseTest.php @@ -9,7 +9,6 @@ namespace Leo108\CAS\Responses; use Mockery; -use Symfony\Component\HttpFoundation\Response; use TestCase; class XmlAuthenticationSuccessResponseTest extends TestCase From ca01d620ddbf10e9633482f3357c20ec25e0cbf9 Mon Sep 17 00:00:00 2001 From: leo108 Date: Thu, 27 Oct 2016 08:06:48 +0800 Subject: [PATCH 08/17] more tests --- src/Repositories/PGTicketRepository.php | 2 +- src/Repositories/TicketRepository.php | 2 +- src/Services/PGTCaller.php | 16 +- src/Services/TicketGenerator.php | 8 +- .../Controllers/SecurityControllerTest.php | 122 +++--- .../Controllers/ValidateControllerTest.php | 360 +++++++++--------- tests/Repositories/PGTicketRepositoryTest.php | 214 +++++++++++ tests/Repositories/TicketRepositoryTest.php | 8 +- .../JsonProxyFailureResponseTest.php | 39 ++ .../JsonProxySuccessResponseTest.php | 33 ++ .../Responses/XmlProxyFailureResponseTest.php | 42 ++ .../Responses/XmlProxySuccessResponseTest.php | 38 ++ tests/Services/PGTCallerTest.php | 49 +++ tests/Services/TicketGeneratorTest.php | 63 +++ 14 files changed, 726 insertions(+), 270 deletions(-) create mode 100644 tests/Repositories/PGTicketRepositoryTest.php create mode 100644 tests/Responses/JsonProxyFailureResponseTest.php create mode 100644 tests/Responses/JsonProxySuccessResponseTest.php create mode 100644 tests/Responses/XmlProxyFailureResponseTest.php create mode 100644 tests/Responses/XmlProxySuccessResponseTest.php create mode 100644 tests/Services/PGTCallerTest.php create mode 100644 tests/Services/TicketGeneratorTest.php diff --git a/src/Repositories/PGTicketRepository.php b/src/Repositories/PGTicketRepository.php index 15323eb..7a761e6 100644 --- a/src/Repositories/PGTicketRepository.php +++ b/src/Repositories/PGTicketRepository.php @@ -105,7 +105,7 @@ protected function getAvailableTicket($totalLength) $totalLength, 'PGT-', function ($ticket) { - return $this->getByTicket($ticket, false); + return is_null($this->getByTicket($ticket, false)); }, 10 ); diff --git a/src/Repositories/TicketRepository.php b/src/Repositories/TicketRepository.php index 9307797..a6de151 100644 --- a/src/Repositories/TicketRepository.php +++ b/src/Repositories/TicketRepository.php @@ -112,7 +112,7 @@ protected function getAvailableTicket($totalLength, $prefix) $totalLength, $prefix, function ($ticket) { - return $this->getByTicket($ticket, false); + return is_null($this->getByTicket($ticket, false)); }, 10 ); diff --git a/src/Services/PGTCaller.php b/src/Services/PGTCaller.php index f36bbe4..6a43313 100644 --- a/src/Services/PGTCaller.php +++ b/src/Services/PGTCaller.php @@ -26,14 +26,18 @@ public function __construct(Client $client) $this->client = $client; } + /** + * @param string $pgtUrl + * @param string $pgt + * @param string $pgtiou + * @return bool + */ public function call($pgtUrl, $pgt, $pgtiou) { - $query = http_build_query( - [ - 'pgtId' => $pgt, - 'pgtIou' => $pgtiou, - ] - ); + $query = [ + 'pgtId' => $pgt, + 'pgtIou' => $pgtiou, + ]; parse_str(parse_url($pgtUrl, PHP_URL_QUERY), $originQuery); try { diff --git a/src/Services/TicketGenerator.php b/src/Services/TicketGenerator.php index 4be163d..2f5417a 100644 --- a/src/Services/TicketGenerator.php +++ b/src/Services/TicketGenerator.php @@ -12,7 +12,6 @@ class TicketGenerator { - /** * @param integer $totalLength * @param string $prefix @@ -26,7 +25,7 @@ public function generate($totalLength, $prefix, callable $checkFunc, $maxRetry) $flag = false; for ($i = 0; $i < $maxRetry; $i++) { $ticket = $this->generateOne($totalLength, $prefix); - if (!call_user_func_array($checkFunc, [$ticket])) { + if (call_user_func_array($checkFunc, [$ticket])) { $flag = true; break; } @@ -39,6 +38,11 @@ public function generate($totalLength, $prefix, callable $checkFunc, $maxRetry) return $ticket; } + /** + * @param integer $totalLength + * @param string $prefix + * @return string + */ public function generateOne($totalLength, $prefix) { return $prefix.Str::random($totalLength - strlen($prefix)); diff --git a/tests/Http/Controllers/SecurityControllerTest.php b/tests/Http/Controllers/SecurityControllerTest.php index 1740397..52afe7c 100644 --- a/tests/Http/Controllers/SecurityControllerTest.php +++ b/tests/Http/Controllers/SecurityControllerTest.php @@ -11,6 +11,7 @@ use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; use Leo108\CAS\Contracts\Interactions\UserLogin; +use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Events\CasUserLoginEvent; use Leo108\CAS\Events\CasUserLogoutEvent; use Leo108\CAS\Exceptions\CAS\CasException; @@ -18,7 +19,6 @@ use Leo108\CAS\Repositories\TicketRepository; use TestCase; use Mockery; -use User; //mock function function cas_route($name, $query) @@ -38,6 +38,12 @@ public function setUp() public function testShowLoginWithValidServiceUrl() { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service', '']) + ->andReturn('what ever') + ->once() + ->getMock(); $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) @@ -46,31 +52,26 @@ public function testShowLoginWithValidServiceUrl() app()->instance(ServiceRepository::class, $serviceRepository); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('showLoginPage') - ->andReturnUsing( - function ($request, $errors) { - $this->assertEmpty($errors); - - return 'show login called'; - } - ) + ->with($request, []) + ->andReturn('show login called') ->once() ->shouldReceive('getCurrentUser') ->andReturn(false) ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) - ->shouldReceive('get') - ->withArgs(['service', '']) - ->andReturn('what ever') - ->once() - ->getMock(); $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); } public function testShowLoginWithInvalidServiceUrl() { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service', '']) + ->andReturn('what ever') + ->once() + ->getMock(); $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(false) @@ -79,26 +80,15 @@ public function testShowLoginWithInvalidServiceUrl() app()->instance(ServiceRepository::class, $serviceRepository); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('showLoginPage') - ->andReturnUsing( - function ($request, $errors) { - $this->assertNotEmpty($errors); - $this->assertEquals(CasException::INVALID_SERVICE, $errors[0]); - - return 'show login called'; - } - ) + ->with($request, [CasException::INVALID_SERVICE]) + ->andReturn('show login called') ->once() ->shouldReceive('getCurrentUser') ->andReturn(false) ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) - ->shouldReceive('get') - ->withArgs(['service', '']) - ->andReturn('what ever') - ->once() - ->getMock(); + $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); } @@ -111,7 +101,7 @@ public function testShowLoginWhenLoggedInWithValidServiceUrlWithoutWarn() ->once() ->getMock(); $ticketRepository = Mockery::mock(TicketRepository::class); - $user = new User(); + $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') ->andReturn($user) @@ -196,14 +186,8 @@ public function testShowLoginWhenLoggedInWithInvalidServiceUrl() ->andReturn(true)//just not false is OK ->once() ->shouldReceive('redirectToHome') - ->andReturnUsing( - function ($errors) { - $this->assertNotEmpty($errors); - $this->assertEquals(CasException::INVALID_SERVICE, $errors[0]); - - return 'redirectToHome called'; - } - ) + ->with([CasException::INVALID_SERVICE]) + ->andReturn('redirectToHome called') ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); @@ -220,14 +204,10 @@ function ($errors) { public function testAuthenticatedWithoutService() { //without service url - $user = new User(); + $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('redirectToHome') - ->andReturnUsing( - function () { - return 'redirectToHome called'; - } - ) + ->andReturn('redirectToHome called') ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); @@ -247,17 +227,11 @@ function () { public function testAuthenticatedWithService() { //with service url but apply ticket failed - $user = new User(); + $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('redirectToHome') - ->andReturnUsing( - function ($errors) { - $this->assertNotEmpty($errors); - $this->assertEquals(CasException::INTERNAL_ERROR, $errors[0]); - - return 'redirectToHome called'; - } - ) + ->with([CasException::INTERNAL_ERROR]) + ->andReturn('redirectToHome called') ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); @@ -302,52 +276,46 @@ function ($errors) { public function testLogoutWhenNotLoggedInWithoutService() { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service']) + ->andReturn(null) + ->once() + ->getMock(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') ->andReturn(false) ->once() ->shouldReceive('showLoggedOut') - ->andReturnUsing( - function ($request) { - return 'showLoggedOut called'; - } - ) + ->with($request) + ->andReturn('showLoggedOut called') ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) - ->shouldReceive('get') - ->withArgs(['service']) - ->andReturn(null) - ->once() - ->getMock(); $this->doesntExpectEvents(CasUserLogoutEvent::class); $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); } public function testLogoutWithoutService() { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->withArgs(['service']) + ->andReturn(null) + ->once() + ->getMock(); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('logout') ->once() ->shouldReceive('getCurrentUser') - ->andReturn(new User()) + ->andReturn(Mockery::mock(UserModel::class)) ->once() ->shouldReceive('showLoggedOut') - ->andReturnUsing( - function ($request) { - return 'showLoggedOut called'; - } - ) + ->with($request) + ->andReturn('showLoggedOut called') ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) - ->shouldReceive('get') - ->withArgs(['service']) - ->andReturn(null) - ->once() - ->getMock(); $this->expectsEvents(CasUserLogoutEvent::class); $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); } @@ -364,7 +332,7 @@ public function testLogoutWithValidService() ->shouldReceive('logout') ->once() ->shouldReceive('getCurrentUser') - ->andReturn(new User()) + ->andReturn(Mockery::mock(UserModel::class)) ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); @@ -381,7 +349,7 @@ public function testLogoutWithValidService() public function testLogin() { - $user = new User(); + $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('login') ->andReturn($user) diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index 270aee6..a9ae1fa 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -10,6 +10,7 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; +use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Contracts\TicketLocker; use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Ticket; @@ -17,13 +18,16 @@ use Leo108\CAS\Repositories\TicketRepository; use Leo108\CAS\Responses\JsonAuthenticationFailureResponse; use Leo108\CAS\Responses\JsonAuthenticationSuccessResponse; +use Leo108\CAS\Responses\JsonProxyFailureResponse; +use Leo108\CAS\Responses\JsonProxySuccessResponse; use Leo108\CAS\Responses\XmlAuthenticationFailureResponse; use Leo108\CAS\Responses\XmlAuthenticationSuccessResponse; +use Leo108\CAS\Responses\XmlProxyFailureResponse; +use Leo108\CAS\Responses\XmlProxySuccessResponse; use Leo108\CAS\Services\PGTCaller; use Leo108\CAS\Services\TicketGenerator; use TestCase; use Mockery; -use User; function method_exists($obj, $method) { @@ -140,81 +144,60 @@ public function testV1ValidateActionWithValidTicketAndService() public function testV2ServiceValidateAction() { + $request = Mockery::mock(Request::class); $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') - ->andReturnUsing( - function ($request, $returnAttr, $allowProxy) { - $this->assertFalse($returnAttr); - $this->assertFalse($allowProxy); - - return 'casValidate called'; - } - ) + ->with($request, false, false) + ->andReturn('casValidate called') ->once() ->getMock(); - $request = Mockery::mock(Request::class); $this->assertEquals('casValidate called', $controller->v2ServiceValidateAction($request)); } public function testV2ProxyValidateAction() { + $request = Mockery::mock(Request::class); $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') - ->andReturnUsing( - function ($request, $returnAttr, $allowProxy) { - $this->assertFalse($returnAttr); - $this->assertTrue($allowProxy); - - return 'casValidate called'; - } - ) + ->with($request, false, true) + ->andReturn('casValidate called') ->once() ->getMock(); - $request = Mockery::mock(Request::class); + $this->assertEquals('casValidate called', $controller->v2ProxyValidateAction($request)); } public function testV3ServiceValidateAction() { + $request = Mockery::mock(Request::class); $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') - ->andReturnUsing( - function ($request, $returnAttr, $allowProxy) { - $this->assertTrue($returnAttr); - $this->assertFalse($allowProxy); - - return 'casValidate called'; - } - ) + ->with($request, true, false) + ->andReturn('casValidate called') ->once() ->getMock(); - $request = Mockery::mock(Request::class); + $this->assertEquals('casValidate called', $controller->v3ServiceValidateAction($request)); } public function testV3ProxyValidateAction() { + $request = Mockery::mock(Request::class); $controller = Mockery::mock(ValidateController::class) ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('casValidate') - ->andReturnUsing( - function ($request, $returnAttr, $allowProxy) { - $this->assertTrue($returnAttr); - $this->assertTrue($allowProxy); - - return 'casValidate called'; - } - ) + ->with($request, true, true) + ->andReturn('casValidate called') ->once() ->getMock(); - $request = Mockery::mock(Request::class); + $this->assertEquals('casValidate called', $controller->v3ProxyValidateAction($request)); } @@ -238,15 +221,8 @@ public function testProxyActionWithInvalidRequest() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('proxyFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_REQUEST, $code); - $this->assertEquals('param pgt and targetService can not be empty', $desc); - $this->assertEquals('XML', $format); - - return 'proxyFailureResponse called'; - } - ) + ->with(CasException::INVALID_REQUEST, 'param pgt and targetService can not be empty', 'XML') + ->andReturn('proxyFailureResponse called') ->once() ->getMock(); $this->assertEquals('proxyFailureResponse called', $controller->proxyAction($request)); @@ -278,20 +254,63 @@ public function testProxyActionWithInvalidTicket() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('proxyFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_TICKET, $code); - $this->assertEquals('ticket is not valid', $desc); - $this->assertEquals('XML', $format); - - return 'proxyFailureResponse called'; - } - ) + ->with(CasException::INVALID_TICKET, 'ticket is not valid', 'XML') + ->andReturn('proxyFailureResponse called') ->once() ->getMock(); $this->assertEquals('proxyFailureResponse called', $controller->proxyAction($request)); } + public function testProxyActionWithValidTicket() + { + $request = Mockery::mock(Request::class) + ->shouldReceive('get') + ->with('pgt', '') + ->andReturn('pgt string') + ->once() + ->shouldReceive('get') + ->with('targetService', '') + ->andReturn('http://target.com') + ->once() + ->shouldReceive('get') + ->with('format', 'XML') + ->andReturn('XML') + ->once() + ->getMock(); + $user = Mockery::mock(UserModel::class); + $pgTicket = Mockery::mock(); + $pgTicket->proxies = ['http://proxy2.com', 'http://proxy1.com']; + $pgTicket->pgt_url = 'http://proxy3.com'; + $pgTicket->user = $user; + + $pgtRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('getByTicket') + ->andReturn($pgTicket) + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgtRepository); + + $ticket = Mockery::mock(); + $ticket->ticket = 'proxy ticket string'; + + $ticketRepository = Mockery::mock(TicketRepository::class) + ->shouldReceive('applyTicket') + ->with($user, 'http://target.com', ['http://proxy3.com', 'http://proxy2.com', 'http://proxy1.com']) + ->andReturn($ticket) + ->once() + ->getMock(); + app()->instance(TicketRepository::class, $ticketRepository); + $controller = $this->initController() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('proxySuccessResponse') + ->with('proxy ticket string', 'XML') + ->andReturn('proxySuccessResponse called') + ->once() + ->getMock(); + $this->assertEquals('proxySuccessResponse called', $controller->proxyAction($request)); + } + public function testCasValidateWithInvalidRequest() { $request = Mockery::mock(Request::class) @@ -313,15 +332,8 @@ public function testCasValidateWithInvalidRequest() ->makePartial() ->shouldAllowMockingProtectedMethods() ->shouldReceive('authFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_REQUEST, $code); - $this->assertEquals('param service and ticket can not be empty', $desc); - $this->assertEquals('JSON', $format); - - return 'authFailureResponse called'; - } - ) + ->with(CasException::INVALID_REQUEST, 'param service and ticket can not be empty', 'JSON') + ->andReturn('authFailureResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -338,15 +350,8 @@ public function testCasValidateAndLockTicketFailed() ->andReturn(false) ->once() ->shouldReceive('authFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INTERNAL_ERROR, $code); - $this->assertEquals('try to lock ticket failed', $desc); - $this->assertEquals('JSON', $format); - - return 'authFailureResponse called'; - } - ) + ->with(CasException::INTERNAL_ERROR, 'try to lock ticket failed', 'JSON') + ->andReturn('authFailureResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -371,15 +376,8 @@ public function testCasValidateWithInvalidTicket() ->andReturn(true) ->once() ->shouldReceive('authFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_TICKET, $code); - $this->assertEquals('ticket is not valid', $desc); - $this->assertEquals('JSON', $format); - - return 'authFailureResponse called'; - } - ) + ->with(CasException::INVALID_TICKET, 'ticket is not valid', 'JSON') + ->andReturn('authFailureResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -415,15 +413,8 @@ public function testCasValidateWithValidTicketButServiceMismatch() ->andReturn(true) ->once() ->shouldReceive('authFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_SERVICE, $code); - $this->assertEquals('service is not valid', $desc); - $this->assertEquals('JSON', $format); - - return 'authFailureResponse called'; - } - ) + ->with(CasException::INVALID_SERVICE, 'service is not valid', 'JSON') + ->andReturn('authFailureResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -455,15 +446,8 @@ public function testCasValidateWithValidProxyTicketButNotAllowProxy() ->andReturn(true) ->once() ->shouldReceive('authFailureResponse') - ->andReturnUsing( - function ($code, $desc, $format) { - $this->assertEquals(CasException::INVALID_TICKET, $code); - $this->assertEquals('ticket is not valid', $desc); - $this->assertEquals('JSON', $format); - - return 'authFailureResponse called'; - } - ) + ->with(CasException::INVALID_TICKET, 'ticket is not valid', 'JSON') + ->andReturn('authFailureResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -474,7 +458,7 @@ public function testCasValidateWithValidProxyTicketAndAllowProxy() { $proxies = ['http://proxy1.com', 'http://proxy2.com']; $request = $this->getValidRequest(''); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() @@ -513,17 +497,8 @@ public function testCasValidateWithValidProxyTicketAndAllowProxy() ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxiesParam, $iou) use ($proxies) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEquals($proxies, $proxiesParam); - $this->assertNull($iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], $proxies, null) + ->andReturn('authSuccessResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); @@ -533,7 +508,7 @@ function ($name, $format, $attributes, $proxiesParam, $iou) use ($proxies) { public function testCasValidateWithValidTicketAndServiceAndNoPgt() { $request = $this->getValidRequest(''); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() @@ -568,17 +543,8 @@ public function testCasValidateWithValidTicketAndServiceAndNoPgt() ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxies, $iou) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEmpty($proxies); - $this->assertNull($iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], [], null) + ->andReturn('authSuccessResponse called') ->once() ->getMock(); @@ -589,7 +555,7 @@ function ($name, $format, $attributes, $proxies, $iou) { public function testCasValidateWithValidTicketAndServiceAndPgtButApplyPGTFailed() { $request = $this->getValidRequest('http://app1.com/pgtCallback'); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() @@ -630,17 +596,8 @@ public function testCasValidateWithValidTicketAndServiceAndPgtButApplyPGTFailed( ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxies, $iou) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEmpty($proxies); - $this->assertNull($iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], [], null) + ->andReturn('authSuccessResponse called') ->once() ->getMock(); @@ -651,7 +608,7 @@ function ($name, $format, $attributes, $proxies, $iou) { public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlFailed() { $request = $this->getValidRequest('http://app1.com/pgtCallback'); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() @@ -704,17 +661,8 @@ public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlFaile ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxies, $iou) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEmpty($proxies); - $this->assertNull($iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], [], null) + ->andReturn('authSuccessResponse called') ->once() ->getMock(); @@ -722,10 +670,10 @@ function ($name, $format, $attributes, $proxies, $iou) { $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); } - public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlSuccess() + public function testCasValidateWithValidTicketAndServiceAndPgtAndCallPgtUrlSuccess() { $request = $this->getValidRequest('http://app1.com/pgtCallback'); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() @@ -778,17 +726,8 @@ public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlSucce ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxies, $iou) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEmpty($proxies); - $this->assertEquals('pgtiou string', $iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], [], 'pgtiou string') + ->andReturn('authSuccessResponse called') ->once() ->getMock(); @@ -796,18 +735,18 @@ function ($name, $format, $attributes, $proxies, $iou) { $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); } - public function testCasValidateWithValidProxyTicketAndServiceAndPgtButCallPgtUrlSuccess() + public function testCasValidateWithValidProxyTicketAndServiceAndPgtAndCallPgtUrlSuccess() { $request = $this->getValidRequest('http://app1.com/pgtCallback'); - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getName') ->andReturn('test_user') ->once() ->getMock(); $ticket = Mockery::mock(Ticket::class) ->shouldReceive('isProxy') - ->andReturn(false) - ->times(2) + ->andReturn(true) + ->once() ->shouldReceive('getAttribute') ->withArgs(['service_url']) ->andReturn('http://leo108.com') @@ -816,6 +755,10 @@ public function testCasValidateWithValidProxyTicketAndServiceAndPgtButCallPgtUrl ->withArgs(['user']) ->andReturn($user) ->times(2) + ->shouldReceive('getAttribute') + ->withArgs(['proxies']) + ->andReturn(['http://proxy1.com']) + ->once() ->getMock(); $ticketRepository = Mockery::mock(TicketRepository::class) ->shouldReceive('getByTicket') @@ -852,22 +795,13 @@ public function testCasValidateWithValidProxyTicketAndServiceAndPgtButCallPgtUrl ->shouldReceive('unlockTicket') ->once() ->shouldReceive('authSuccessResponse') - ->andReturnUsing( - function ($name, $format, $attributes, $proxies, $iou) { - $this->assertEquals('test_user', $name); - $this->assertEmpty($attributes); - $this->assertEquals('JSON', $format); - $this->assertEmpty($proxies); - $this->assertEquals('pgtiou string', $iou); - - return 'authSuccessResponse called'; - } - ) + ->with('test_user', 'JSON', [], ['http://proxy1.com'], 'pgtiou string') + ->andReturn('authSuccessResponse called') ->once() ->getMock(); $method = self::getNonPublicMethod($controller, 'casValidate'); - $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, false])); + $this->assertEquals('authSuccessResponse called', $method->invokeArgs($controller, [$request, false, true])); } public function testLockTicket() @@ -921,10 +855,14 @@ public function testAuthSuccessResponse() ->with($proxies) ->once() ->shouldReceive('toResponse') + ->andReturn('toResponse called') ->once() ->getMock(); app()->instance(JsonAuthenticationSuccessResponse::class, $jsonResp); - $method->invokeArgs($controller, ['test_name', 'JSON', $attributes, $proxies, []]); + $this->assertEquals( + 'toResponse called', + $method->invokeArgs($controller, ['test_name', 'JSON', $attributes, $proxies, []]) + ); $xmlResp = Mockery::mock(XmlAuthenticationSuccessResponse::class) ->shouldReceive('setUser') @@ -940,10 +878,14 @@ public function testAuthSuccessResponse() ->with($pgt) ->once() ->shouldReceive('toResponse') + ->andReturn('toResponse called') ->once() ->getMock(); app()->instance(XmlAuthenticationSuccessResponse::class, $xmlResp); - $method->invokeArgs($controller, ['test_name', 'XML', $attributes, $proxies, $pgt]); + $this->assertEquals( + 'toResponse called', + $method->invokeArgs($controller, ['test_name', 'XML', $attributes, $proxies, $pgt]) + ); } public function testAuthFailureResponse() @@ -974,6 +916,66 @@ public function testAuthFailureResponse() $method->invokeArgs($controller, [$code, $desc, 'XML']); } + public function testProxySuccessResponse() + { + $controller = Mockery::mock(ValidateController::class) + ->makePartial(); + $method = self::getNonPublicMethod($controller, 'proxySuccessResponse'); + + $ticket = 'ticket'; + $jsonResp = Mockery::mock(JsonProxySuccessResponse::class) + ->shouldReceive('setProxyTicket') + ->with($ticket) + ->once() + ->shouldReceive('toResponse') + ->andReturn('toResponse called') + ->once() + ->getMock(); + app()->instance(JsonProxySuccessResponse::class, $jsonResp); + $this->assertEquals('toResponse called', $method->invokeArgs($controller, [$ticket, 'JSON'])); + + $xmlResp = Mockery::mock(XmlProxySuccessResponse::class) + ->shouldReceive('setProxyTicket') + ->with($ticket) + ->once() + ->shouldReceive('toResponse') + ->andReturn('toResponse called') + ->once() + ->getMock(); + app()->instance(XmlProxySuccessResponse::class, $xmlResp); + $this->assertEquals('toResponse called', $method->invokeArgs($controller, [$ticket, 'XML'])); + } + + public function testProxyFailureResponse() + { + $controller = Mockery::mock(ValidateController::class) + ->makePartial(); + $method = self::getNonPublicMethod($controller, 'proxyFailureResponse'); + $code = 'code'; + $desc = 'desc'; + $jsonResp = Mockery::mock(JsonProxyFailureResponse::class) + ->shouldReceive('setFailure') + ->withArgs([$code, $desc]) + ->once() + ->shouldReceive('toResponse') + ->andReturn('toResponse called') + ->once() + ->getMock(); + app()->instance(JsonProxyFailureResponse::class, $jsonResp); + $this->assertEquals('toResponse called', $method->invokeArgs($controller, [$code, $desc, 'JSON'])); + + $xmlResp = Mockery::mock(XmlProxyFailureResponse::class) + ->shouldReceive('setFailure') + ->withArgs([$code, $desc]) + ->once() + ->shouldReceive('toResponse') + ->andReturn('toResponse called') + ->once() + ->getMock(); + app()->instance(XmlProxyFailureResponse::class, $xmlResp); + $this->assertEquals('toResponse called', $method->invokeArgs($controller, [$code, $desc, 'XML'])); + } + protected function getValidRequest($pgt = null) { $mock = Mockery::mock(Request::class) diff --git a/tests/Repositories/PGTicketRepositoryTest.php b/tests/Repositories/PGTicketRepositoryTest.php new file mode 100644 index 0000000..1f504cc --- /dev/null +++ b/tests/Repositories/PGTicketRepositoryTest.php @@ -0,0 +1,214 @@ +shouldReceive('getServiceByUrl') + ->andReturn(false) + ->once() + ->getMock(); + app()->instance(ServiceRepository::class, $serviceRepository); + try { + app()->make(PGTicketRepository::class)->applyTicket($user, 'what ever'); + } catch (Exception $e) { + $this->assertInstanceOf(CasException::class, $e); + $this->assertEquals(CasException::UNAUTHORIZED_SERVICE_PROXY, $e->getCasErrorCode()); + } + } + + public function testApplyTicketWithNonProxyService() + { + $user = Mockery::mock(UserModel::class); + $service = Mockery::mock(Service::class) + ->shouldReceive('getAttribute') + ->withArgs(['allow_proxy']) + ->andReturn(false) + ->once() + ->getMock(); + $serviceRepository = Mockery::mock(ServiceRepository::class) + ->shouldReceive('getServiceByUrl') + ->andReturn($service) + ->getMock(); + app()->instance(ServiceRepository::class, $serviceRepository); + + try { + app()->make(PGTicketRepository::class)->applyTicket($user, 'what ever'); + } catch (Exception $e) { + $this->assertInstanceOf(CasException::class, $e); + $this->assertEquals(CasException::UNAUTHORIZED_SERVICE_PROXY, $e->getCasErrorCode()); + } + } + + public function testApplyTicketWithValidServiceButApplyTicketFailed() + { + $user = Mockery::mock(UserModel::class); + $service = Mockery::mock(Service::class) + ->shouldReceive('getAttribute') + ->withArgs(['allow_proxy']) + ->andReturn(true) + ->once() + ->getMock(); + $serviceRepository = Mockery::mock(ServiceRepository::class) + ->shouldReceive('getServiceByUrl') + ->andReturn($service) + ->once() + ->getMock(); + app()->instance(ServiceRepository::class, $serviceRepository); + $pgTicketRepository = $this->initPGTicketRepository() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('getAvailableTicket') + ->andReturn(false) + ->once() + ->getMock(); + + try { + $pgTicketRepository->applyTicket($user, 'what ever'); + } catch (Exception $e) { + $this->assertInstanceOf(CasException::class, $e); + $this->assertEquals(CasException::INTERNAL_ERROR, $e->getCasErrorCode()); + $this->assertEquals('apply proxy-granting ticket failed', $e->getMessage()); + } + } + + public function testApplyTicketWithValidServiceAndApplyTicketOK() + { + $user = Mockery::mock(UserModel::class) + ->shouldReceive('getEloquentModel') + ->andReturn(Mockery::self()) + ->once() + ->getMock(); + $ticketStr = 'ST-abc'; + $pgtUrl = 'what ever'; + $proxies = ['http://proxy2.com', 'http://proxy1.com']; + $service = Mockery::mock(Service::class) + ->shouldReceive('getAttribute') + ->withArgs(['allow_proxy']) + ->andReturn(true) + ->once() + ->getMock(); + $serviceRepository = Mockery::mock(ServiceRepository::class) + ->shouldReceive('getServiceByUrl') + ->andReturn($service) + ->getMock(); + app()->instance(ServiceRepository::class, $serviceRepository); + $ticket = Mockery::mock(PGTicket::class) + ->shouldReceive('newInstance') + ->andReturnUsing( + function ($param) { + $obj = Mockery::mock(); + $obj->shouldReceive('user->associate'); + $obj->shouldReceive('service->associate'); + $obj->shouldReceive('save'); + $obj->ticket = $param['ticket']; + $obj->pgt_url = $param['pgt_url']; + $obj->proxies = $param['proxies']; + + return $obj; + } + ) + ->getMock(); + app()->instance(PGTicket::class, $ticket); + $ticketRepository = $this->initPGTicketRepository() + ->makePartial() + ->shouldAllowMockingProtectedMethods() + ->shouldReceive('getAvailableTicket') + ->andReturn($ticketStr) + ->getMock(); + + $record = $ticketRepository->applyTicket($user, $pgtUrl, $proxies); + $this->assertEquals($ticketStr, $record->ticket); + $this->assertEquals($pgtUrl, $record->pgt_url); + $this->assertEquals($proxies, $record->proxies); + } + + public function testGetByTicket() + { + $ticket = Mockery::mock(PGTicket::class); + $ticket->shouldReceive('where->first')->andReturn(null); + app()->instance(PGTicket::class, $ticket); + $this->assertNull(app()->make(PGTicketRepository::class)->getByTicket('what ever')); + + $mockTicket = Mockery::mock(PGTicket::class) + ->shouldReceive('isExpired') + ->andReturnValues([false, true]) + ->getMock(); + + $ticket = Mockery::mock(PGTicket::class); + $ticket->shouldReceive('where->first')->andReturn($mockTicket); + app()->instance(PGTicket::class, $ticket); + $this->assertNotNull(app()->make(PGTicketRepository::class)->getByTicket('what ever', false)); + $this->assertNotNull(app()->make(PGTicketRepository::class)->getByTicket('what ever')); + $this->assertNull(app()->make(PGTicketRepository::class)->getByTicket('what ever')); + } + + public function testGetAvailableTicket() + { + $length = 32; + $prefix = 'PGT-'; + $ticket = 'ticket string'; + $ticketGenerator = Mockery::mock(TicketGenerator::class) + ->shouldReceive('generate') + ->andReturnUsing( + function ($totalLength, $paramPrefix, callable $checkFunc, $maxRetry) use ($length, $prefix, $ticket) { + $this->assertEquals($length, $totalLength); + $this->assertEquals($prefix, $paramPrefix); + $this->assertTrue(call_user_func_array($checkFunc, [$ticket])); + $this->assertEquals(10, $maxRetry); + + return 'generate called'; + } + ) + ->once() + ->getMock(); + app()->instance(TicketGenerator::class, $ticketGenerator); + $ticketRepository = $this->initPGTicketRepository() + ->makePartial() + ->shouldReceive('getByTicket') + ->with($ticket, false) + ->andReturn(null) + ->once() + ->getMock(); + + $this->assertEquals( + 'generate called', + self::getNonPublicMethod($ticketRepository, 'getAvailableTicket')->invoke( + $ticketRepository, + $length, + $prefix + ) + ); + } + + /** + * @return Mockery\MockInterface + */ + protected function initPGTicketRepository() + { + return Mockery::mock( + PGTicketRepository::class, + [app(PGTicket::class), app(ServiceRepository::class), app(TicketGenerator::class)] + ); + } +} diff --git a/tests/Repositories/TicketRepositoryTest.php b/tests/Repositories/TicketRepositoryTest.php index 625c5ec..a334238 100644 --- a/tests/Repositories/TicketRepositoryTest.php +++ b/tests/Repositories/TicketRepositoryTest.php @@ -8,20 +8,20 @@ namespace Leo108\CAS\Repositories; use Exception; +use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\Service; use Leo108\CAS\Models\Ticket; use Leo108\CAS\Services\TicketGenerator; use Mockery; use TestCase; -use User; class TicketRepositoryTest extends TestCase { public function testApplyTicket() { //test if service url is not valid - $user = Mockery::mock(User::class) + $user = Mockery::mock(UserModel::class) ->shouldReceive('getAttribute') ->withArgs(['id']) ->andReturn(1) @@ -148,7 +148,7 @@ public function testGetAvailableTicket() function ($totalLength, $paramPrefix, callable $checkFunc, $maxRetry) use ($length, $prefix, $ticket) { $this->assertEquals($length, $totalLength); $this->assertEquals($prefix, $paramPrefix); - $this->assertEquals('getByTicket called', call_user_func_array($checkFunc, [$ticket])); + $this->assertTrue(call_user_func_array($checkFunc, [$ticket])); $this->assertEquals(10, $maxRetry); return 'generate called'; @@ -161,7 +161,7 @@ function ($totalLength, $paramPrefix, callable $checkFunc, $maxRetry) use ($leng ->makePartial() ->shouldReceive('getByTicket') ->with($ticket, false) - ->andReturn('getByTicket called') + ->andReturn(null) ->once() ->getMock(); diff --git a/tests/Responses/JsonProxyFailureResponseTest.php b/tests/Responses/JsonProxyFailureResponseTest.php new file mode 100644 index 0000000..bf35da8 --- /dev/null +++ b/tests/Responses/JsonProxyFailureResponseTest.php @@ -0,0 +1,39 @@ +setFailure('code1', 'desc1'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['proxyFailure' => ['code' => 'code1', 'description' => 'desc1']]], + $data + ); + + $resp->setFailure('code2', 'desc2'); + $data = $this->getData($resp); + $this->assertEquals( + ['serviceResponse' => ['proxyFailure' => ['code' => 'code2', 'description' => 'desc2']]], + $data + ); + } + + protected function getData(JsonProxyFailureResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'data'); + + return $property->getValue($resp); + } +} diff --git a/tests/Responses/JsonProxySuccessResponseTest.php b/tests/Responses/JsonProxySuccessResponseTest.php new file mode 100644 index 0000000..64528c0 --- /dev/null +++ b/tests/Responses/JsonProxySuccessResponseTest.php @@ -0,0 +1,33 @@ +setProxyTicket('proxy ticket'); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['proxySuccess' => ['proxyTicket' => 'proxy ticket']]], $data); + $resp->setProxyTicket('proxy ticket2'); + $data = $this->getData($resp); + $this->assertEquals(['serviceResponse' => ['proxySuccess' => ['proxyTicket' => 'proxy ticket2']]], $data); + } + + protected function getData(JsonProxySuccessResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'data'); + + return $property->getValue($resp); + } +} diff --git a/tests/Responses/XmlProxyFailureResponseTest.php b/tests/Responses/XmlProxyFailureResponseTest.php new file mode 100644 index 0000000..d7f43bb --- /dev/null +++ b/tests/Responses/XmlProxyFailureResponseTest.php @@ -0,0 +1,42 @@ +getXML($resp); + $this->assertNotContains('cas:proxyFailure', $content); + $resp->setFailure('code1', 'desc1'); + $content = $this->getXML($resp); + $this->assertContains('cas:proxyFailure', $content); + $this->assertContains('code1', $content); + $this->assertContains('desc1', $content); + $resp->setFailure('code2', 'desc2'); + $content = $this->getXML($resp); + $this->assertContains('cas:proxyFailure', $content); + $this->assertNotContains('code1', $content); + $this->assertContains('code2', $content); + $this->assertNotContains('desc1', $content); + $this->assertContains('desc2', $content); + } + + protected function getXML(XmlProxyFailureResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'node'); + $node = $property->getValue($resp); + + return $node->asXML(); + } +} diff --git a/tests/Responses/XmlProxySuccessResponseTest.php b/tests/Responses/XmlProxySuccessResponseTest.php new file mode 100644 index 0000000..8f90b31 --- /dev/null +++ b/tests/Responses/XmlProxySuccessResponseTest.php @@ -0,0 +1,38 @@ +setProxyTicket('proxy ticket1'); + $content = $this->getXML($resp); + $this->assertContains('cas:proxySuccess', $content); + $this->assertContains('proxy ticket1', $content); + + $resp->setProxyTicket('proxy ticket2'); + $content = $this->getXML($resp); + $this->assertContains('cas:proxySuccess', $content); + $this->assertNotContains('proxy ticket1', $content); + $this->assertContains('proxy ticket2', $content); + } + + protected function getXML(XmlProxySuccessResponse $resp) + { + $property = self::getNonPublicProperty($resp, 'node'); + $node = $property->getValue($resp); + + return $node->asXML(); + } +} diff --git a/tests/Services/PGTCallerTest.php b/tests/Services/PGTCallerTest.php new file mode 100644 index 0000000..ac90987 --- /dev/null +++ b/tests/Services/PGTCallerTest.php @@ -0,0 +1,49 @@ +push($history); + app()->instance(Client::class, new Client(['handler' => $handler])); + + $call = app(PGTCaller::class); + $this->assertTrue($call->call('https://leo108.com/callback?a=1', 'pgt', 'pgtiou')); + $this->assertCount(1, $container); + $transaction = $container[0]; + /* @var Request $request */ + $request = $transaction['request']; + $this->assertEquals('GET', $request->getMethod()); + $this->assertEquals('leo108.com', $request->getUri()->getHost()); + $this->assertEquals('https', $request->getUri()->getScheme()); + $this->assertEquals('/callback', $request->getUri()->getPath()); + $this->assertEquals('a=1&pgtId=pgt&pgtIou=pgtiou', $request->getUri()->getQuery()); + + $this->assertFalse($call->call('https://leo108.com/404?a=1', 'pgt', 'pgtiou')); + } +} diff --git a/tests/Services/TicketGeneratorTest.php b/tests/Services/TicketGeneratorTest.php new file mode 100644 index 0000000..1a3bbda --- /dev/null +++ b/tests/Services/TicketGeneratorTest.php @@ -0,0 +1,63 @@ +makePartial() + ->shouldReceive('generateOne') + ->andReturn('some string') + ->times(5) + ->getMock(); + + $funcObj = Mockery::mock() + ->shouldReceive('check') + ->andReturnValues([false, false, false, false, true]) + ->times(5) + ->getMock(); + + $this->assertNotFalse($generator->generate(32, 'ST-', [$funcObj, 'check'], 10)); + } + + public function testGenerateButAlwaysCheckFailed() + { + $generator = Mockery::mock(TicketGenerator::class) + ->makePartial() + ->shouldReceive('generateOne') + ->andReturn('some string') + ->times(10) + ->getMock(); + + $funcObj = Mockery::mock() + ->shouldReceive('check') + ->andReturn(false) + ->times(10) + ->getMock(); + + $this->assertFalse($generator->generate(32, 'ST-', [$funcObj, 'check'], 10)); + } + + public function testGenerateOne() + { + $totalLength = 32; + $generator = app(TicketGenerator::class); + $prefixArr = ['PGTIOU-', 'PGT-', 'ST-', 'PT-']; + foreach ($prefixArr as $prefix) { + $ticket = $generator->generateOne($totalLength, $prefix); + $this->assertEquals($totalLength, strlen($ticket)); + $this->assertEquals(0, strpos($ticket, $prefix)); + } + } +} From 995ee17df10de4fe452468b4d1249eb87bab44be Mon Sep 17 00:00:00 2001 From: leo108 Date: Thu, 27 Oct 2016 09:44:28 +0800 Subject: [PATCH 09/17] update model --- src/Models/Service.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Models/Service.php b/src/Models/Service.php index cb8742d..1808f6a 100644 --- a/src/Models/Service.php +++ b/src/Models/Service.php @@ -21,9 +21,10 @@ class Service extends Model { protected $table = 'cas_services'; - protected $fillable = ['name', 'enabled']; + protected $fillable = ['name', 'enabled', 'allow_proxy']; protected $casts = [ - 'enabled' => 'boolean', + 'enabled' => 'boolean', + 'allow_proxy' => 'boolean', ]; public function hosts() From 36f9e7be2eb775c4ec310f4ecb1fae5b52db5cbc Mon Sep 17 00:00:00 2001 From: leo108 Date: Thu, 27 Oct 2016 11:14:10 +0800 Subject: [PATCH 10/17] add showAuthenticateFailed to UserLogin --- src/Contracts/Interactions/UserLogin.php | 6 ++ src/Http/Controllers/SecurityController.php | 3 + .../Controllers/SecurityControllerTest.php | 84 ++++++++++--------- 3 files changed, 54 insertions(+), 39 deletions(-) diff --git a/src/Contracts/Interactions/UserLogin.php b/src/Contracts/Interactions/UserLogin.php index 7f09ea9..4b06abf 100644 --- a/src/Contracts/Interactions/UserLogin.php +++ b/src/Contracts/Interactions/UserLogin.php @@ -26,6 +26,12 @@ public function login(Request $request); */ public function getCurrentUser(Request $request); + /** + * @param Request $request + * @return Response + */ + public function showAuthenticateFailed(Request $request); + /** * @param Request $request * @param string $jumpUrl diff --git a/src/Http/Controllers/SecurityController.php b/src/Http/Controllers/SecurityController.php index 16e30dc..967ed3c 100644 --- a/src/Http/Controllers/SecurityController.php +++ b/src/Http/Controllers/SecurityController.php @@ -88,6 +88,9 @@ public function showLogin(Request $request) public function login(Request $request) { $user = $this->loginInteraction->login($request); + if (is_null($user)) { + return $this->loginInteraction->showAuthenticateFailed($request); + } return $this->authenticated($request, $user); } diff --git a/tests/Http/Controllers/SecurityControllerTest.php b/tests/Http/Controllers/SecurityControllerTest.php index 52afe7c..ecfb790 100644 --- a/tests/Http/Controllers/SecurityControllerTest.php +++ b/tests/Http/Controllers/SecurityControllerTest.php @@ -60,8 +60,7 @@ public function testShowLoginWithValidServiceUrl() ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); - + $this->assertEquals('show login called', app(SecurityController::class)->showLogin($request)); } public function testShowLoginWithInvalidServiceUrl() @@ -89,25 +88,25 @@ public function testShowLoginWithInvalidServiceUrl() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $this->assertEquals('show login called', app()->make(SecurityController::class)->showLogin($request)); + $this->assertEquals('show login called', app(SecurityController::class)->showLogin($request)); } public function testShowLoginWhenLoggedInWithValidServiceUrlWithoutWarn() { - //logged in with valid service url without warn parameter $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) ->once() ->getMock(); - $ticketRepository = Mockery::mock(TicketRepository::class); - $user = Mockery::mock(UserModel::class); - $loginInteraction = Mockery::mock(UserLogin::class) + app()->instance(ServiceRepository::class, $serviceRepository); + $user = Mockery::mock(UserModel::class); + $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('getCurrentUser') ->andReturn($user) ->once() ->getMock(); - $request = Mockery::mock(Request::class) + app()->instance(UserLogin::class, $loginInteraction); + $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') @@ -117,10 +116,7 @@ public function testShowLoginWhenLoggedInWithValidServiceUrlWithoutWarn() ->andReturn(false) ->once() ->getMock(); - $controller = Mockery::mock( - SecurityController::class, - [$serviceRepository, $ticketRepository, $loginInteraction] - ) + $controller = $this->initController() ->makePartial() ->shouldReceive('authenticated') ->withArgs([$request, $user]) @@ -132,7 +128,6 @@ public function testShowLoginWhenLoggedInWithValidServiceUrlWithoutWarn() public function testShowLoginWhenLoggedInWithValidServiceUrlWithWarn() { - //logged in with valid service url with warn parameter $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) @@ -166,13 +161,11 @@ public function testShowLoginWhenLoggedInWithValidServiceUrlWithWarn() ->once() ->getMock(); self::$functions->shouldReceive('cas_route')->andReturn('some string')->once(); - $controller = app()->make(SecurityController::class); - $this->assertEquals('showLoginWarnPage called', $controller->showLogin($request)); + $this->assertEquals('showLoginWarnPage called', app(SecurityController::class)->showLogin($request)); } public function testShowLoginWhenLoggedInWithInvalidServiceUrl() { - //logged in with invalid service url $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(false) @@ -191,19 +184,17 @@ public function testShowLoginWhenLoggedInWithInvalidServiceUrl() ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) + $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service', '']) ->andReturn('what ever') ->once() ->getMock(); - $controller = app()->make(SecurityController::class); - $this->assertEquals('redirectToHome called', $controller->showLogin($request)); + $this->assertEquals('redirectToHome called', app(SecurityController::class)->showLogin($request)); } public function testAuthenticatedWithoutService() { - //without service url $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('redirectToHome') @@ -218,10 +209,7 @@ public function testAuthenticatedWithoutService() ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $this->assertEquals( - 'redirectToHome called', - app()->make(SecurityController::class)->authenticated($request, $user) - ); + $this->assertEquals('redirectToHome called', app(SecurityController::class)->authenticated($request, $user)); } public function testAuthenticatedWithService() @@ -248,10 +236,7 @@ public function testAuthenticatedWithService() ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $this->assertEquals( - 'redirectToHome called', - app()->make(SecurityController::class)->authenticated($request, $user) - ); + $this->assertEquals('redirectToHome called', app(SecurityController::class)->authenticated($request, $user)); //with service url $ticket = Mockery::mock(); @@ -269,7 +254,7 @@ public function testAuthenticatedWithService() ->once() ->getMock(); $this->expectsEvents(CasUserLoginEvent::class); - $resp = app()->make(SecurityController::class)->authenticated($request, $user); + $resp = app(SecurityController::class)->authenticated($request, $user); $this->assertInstanceOf(RedirectResponse::class, $resp); $this->assertEquals($resp->getTargetUrl(), 'http://leo108.com?ticket=ST-abc'); } @@ -293,7 +278,7 @@ public function testLogoutWhenNotLoggedInWithoutService() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $this->doesntExpectEvents(CasUserLogoutEvent::class); - $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); + $this->assertEquals('showLoggedOut called', app(SecurityController::class)->logout($request)); } public function testLogoutWithoutService() @@ -317,7 +302,7 @@ public function testLogoutWithoutService() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); $this->expectsEvents(CasUserLogoutEvent::class); - $this->assertEquals('showLoggedOut called', app()->make(SecurityController::class)->logout($request)); + $this->assertEquals('showLoggedOut called', app(SecurityController::class)->logout($request)); } public function testLogoutWithValidService() @@ -343,12 +328,23 @@ public function testLogoutWithValidService() ->once() ->getMock(); $this->expectsEvents(CasUserLogoutEvent::class); - $resp = app()->make(SecurityController::class)->logout($request); + $resp = app(SecurityController::class)->logout($request); $this->assertInstanceOf(RedirectResponse::class, $resp); } public function testLogin() { + $request = Mockery::mock(Request::class); + $loginInteraction = Mockery::mock(UserLogin::class) + ->shouldReceive('login') + ->andReturn(null) + ->once() + ->shouldReceive('showAuthenticateFailed') + ->andReturn('showAuthenticateFailed called') + ->getMock(); + app()->instance(UserLogin::class, $loginInteraction); + $this->assertEquals('showAuthenticateFailed called', app(SecurityController::class)->login($request)); + $user = Mockery::mock(UserModel::class); $loginInteraction = Mockery::mock(UserLogin::class) ->shouldReceive('login') @@ -356,13 +352,8 @@ public function testLogin() ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class); - $serviceRepository = Mockery::mock(ServiceRepository::class); - $ticketRepository = Mockery::mock(TicketRepository::class); - $controller = Mockery::mock( - SecurityController::class, - [$serviceRepository, $ticketRepository, $loginInteraction] - ) + $request = Mockery::mock(Request::class); + $controller = $this->initController() ->makePartial() ->shouldReceive('authenticated') ->withArgs([$request, $user]) @@ -371,4 +362,19 @@ public function testLogin() ->getMock(); $this->assertEquals('authenticated called', $controller->login($request)); } + + /** + * @return Mockery\MockInterface + */ + protected function initController() + { + return Mockery::mock( + SecurityController::class, + [ + app(ServiceRepository::class), + app(TicketRepository::class), + app(UserLogin::class), + ] + ); + } } From 4fccb80bfe2d9e9e4f1715d3d07bf68705be0138 Mon Sep 17 00:00:00 2001 From: leo108 Date: Thu, 27 Oct 2016 21:48:25 +0800 Subject: [PATCH 11/17] add casts --- src/Models/PGTicket.php | 10 +++++++--- src/Models/Ticket.php | 19 ++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/Models/PGTicket.php b/src/Models/PGTicket.php index 83e1ad2..9e41d5f 100644 --- a/src/Models/PGTicket.php +++ b/src/Models/PGTicket.php @@ -22,8 +22,8 @@ * @property integer $service_id * @property integer $user_id * @property array $proxies - * @property integer $created_at - * @property integer $expire_at + * @property Carbon $created_at + * @property Carbon $expire_at * @property UserModel $user */ class PGTicket extends Model @@ -31,6 +31,10 @@ class PGTicket extends Model protected $table = 'cas_proxy_granting_tickets'; public $timestamps = false; protected $fillable = ['ticket', 'pgt_url', 'proxies', 'expire_at', 'created_at']; + protected $casts = [ + 'expire_at' => 'datetime', + 'created_at' => 'datetime', + ]; public function getProxiesAttribute() { @@ -49,7 +53,7 @@ public function setProxiesAttribute($value) public function isExpired() { - return (new Carbon($this->expire_at))->getTimestamp() < time(); + return $this->expire_at->getTimestamp() < time(); } public function service() diff --git a/src/Models/Ticket.php b/src/Models/Ticket.php index 7b87757..02651c0 100644 --- a/src/Models/Ticket.php +++ b/src/Models/Ticket.php @@ -22,8 +22,8 @@ * @property integer $service_id * @property integer $user_id * @property array $proxies - * @property integer $created_at - * @property integer $expire_at + * @property Carbon $created_at + * @property Carbon $expire_at * @property UserModel $user */ class Ticket extends Model @@ -31,19 +31,20 @@ class Ticket extends Model protected $table = 'cas_tickets'; public $timestamps = false; protected $fillable = ['ticket', 'service_url', 'proxies', 'expire_at', 'created_at']; + protected $casts = [ + 'expire_at' => 'datetime', + 'created_at' => 'datetime', + ]; public function getProxiesAttribute() { - if (!$this->isProxy()) { - return null; - } - return json_decode($this->attributes['proxies'], true); } public function setProxiesAttribute($value) { - if ($this->id && !$this->isProxy()) { + //can not modify an existing record + if ($this->id) { return; } $this->attributes['proxies'] = json_encode($value); @@ -51,7 +52,7 @@ public function setProxiesAttribute($value) public function isExpired() { - return (new Carbon($this->expire_at))->getTimestamp() < time(); + return $this->expire_at->getTimestamp() < time(); } public function service() @@ -66,6 +67,6 @@ public function user() public function isProxy() { - return !is_null($this->attributes['proxies']); + return !empty($this->proxies); } } From 50f87dcaea6915851ee72425807af761933ad17c Mon Sep 17 00:00:00 2001 From: leo108 Date: Thu, 27 Oct 2016 22:26:44 +0800 Subject: [PATCH 12/17] document --- src/Contracts/Interactions/UserLogin.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Contracts/Interactions/UserLogin.php b/src/Contracts/Interactions/UserLogin.php index 4b06abf..841e064 100644 --- a/src/Contracts/Interactions/UserLogin.php +++ b/src/Contracts/Interactions/UserLogin.php @@ -55,7 +55,7 @@ public function redirectToHome(array $errors = []); /** * @param Request $request - * @return Response + * @return void */ public function logout(Request $request); From acc18348531f0fd077be24304c9242bb1f8edf54 Mon Sep 17 00:00:00 2001 From: leo108 Date: Fri, 28 Oct 2016 14:22:49 +0800 Subject: [PATCH 13/17] some fix --- src/Http/Controllers/ValidateController.php | 2 +- src/Responses/XmlProxySuccessResponse.php | 4 ++-- tests/Http/Controllers/ValidateControllerTest.php | 12 +++++++++--- tests/Responses/XmlProxySuccessResponseTest.php | 8 +++++--- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/Http/Controllers/ValidateController.php b/src/Http/Controllers/ValidateController.php index 1342e49..460ae0b 100644 --- a/src/Http/Controllers/ValidateController.php +++ b/src/Http/Controllers/ValidateController.php @@ -205,7 +205,7 @@ protected function casValidate(Request $request, $returnAttr, $allowProxy) try { $pgTicket = $this->pgTicketRepository->applyTicket($user, $pgtUrl, $proxies); $iou = $this->ticketGenerator->generateOne(config('cas.ticket_len', 32), 'PGTIOU-'); - if (!$this->pgtCaller->call($pgtUrl, $pgTicket, $iou)) { + if (!$this->pgtCaller->call($pgtUrl, $pgTicket->ticket, $iou)) { $iou = null; } } catch (CasException $e) { diff --git a/src/Responses/XmlProxySuccessResponse.php b/src/Responses/XmlProxySuccessResponse.php index 9aa79ee..d4096c9 100644 --- a/src/Responses/XmlProxySuccessResponse.php +++ b/src/Responses/XmlProxySuccessResponse.php @@ -29,8 +29,8 @@ public function __construct() public function setProxyTicket($ticket) { $proxyNode = $this->getProxyNode(); - $this->removeByXPath($proxyNode, 'cas:proxySuccess'); - $proxyNode->addChild('cas:proxySuccess', $ticket); + $this->removeByXPath($proxyNode, 'cas:proxyTicket'); + $proxyNode->addChild('cas:proxyTicket', $ticket); return $this; } diff --git a/tests/Http/Controllers/ValidateControllerTest.php b/tests/Http/Controllers/ValidateControllerTest.php index a9ae1fa..4471288 100644 --- a/tests/Http/Controllers/ValidateControllerTest.php +++ b/tests/Http/Controllers/ValidateControllerTest.php @@ -634,9 +634,11 @@ public function testCasValidateWithValidTicketAndServiceAndPgtButCallPgtUrlFaile ->once() ->getMock(); app()->instance(TicketRepository::class, $ticketRepository); + $pgTicket = Mockery::mock(); + $pgTicket->ticket = 'some string'; $pgTicketRepository = Mockery::mock(PGTicketRepository::class) ->shouldReceive('applyTicket') - ->andReturn('some string') + ->andReturn($pgTicket) ->once() ->getMock(); app()->instance(PGTicketRepository::class, $pgTicketRepository); @@ -699,9 +701,11 @@ public function testCasValidateWithValidTicketAndServiceAndPgtAndCallPgtUrlSucce ->once() ->getMock(); app()->instance(TicketRepository::class, $ticketRepository); + $pgTicket = Mockery::mock(); + $pgTicket->ticket = 'some string'; $pgTicketRepository = Mockery::mock(PGTicketRepository::class) ->shouldReceive('applyTicket') - ->andReturn('some string') + ->andReturn($pgTicket) ->once() ->getMock(); app()->instance(PGTicketRepository::class, $pgTicketRepository); @@ -768,9 +772,11 @@ public function testCasValidateWithValidProxyTicketAndServiceAndPgtAndCallPgtUrl ->once() ->getMock(); app()->instance(TicketRepository::class, $ticketRepository); + $pgTicket = Mockery::mock(); + $pgTicket->ticket = 'some string'; $pgTicketRepository = Mockery::mock(PGTicketRepository::class) ->shouldReceive('applyTicket') - ->andReturn('some string') + ->andReturn($pgTicket) ->once() ->getMock(); app()->instance(PGTicketRepository::class, $pgTicketRepository); diff --git a/tests/Responses/XmlProxySuccessResponseTest.php b/tests/Responses/XmlProxySuccessResponseTest.php index 8f90b31..af9da8b 100644 --- a/tests/Responses/XmlProxySuccessResponseTest.php +++ b/tests/Responses/XmlProxySuccessResponseTest.php @@ -15,15 +15,17 @@ class XmlProxySuccessResponseTest extends TestCase { public function testSetProxyTicket() { - $resp = new XmlProxySuccessResponse(); + $resp = new XmlProxySuccessResponse(); + $content = $this->getXML($resp); + $this->assertNotContains('cas:proxyTicket', $content); $resp->setProxyTicket('proxy ticket1'); $content = $this->getXML($resp); - $this->assertContains('cas:proxySuccess', $content); + $this->assertContains('cas:proxyTicket', $content); $this->assertContains('proxy ticket1', $content); $resp->setProxyTicket('proxy ticket2'); $content = $this->getXML($resp); - $this->assertContains('cas:proxySuccess', $content); + $this->assertContains('cas:proxyTicket', $content); $this->assertNotContains('proxy ticket1', $content); $this->assertContains('proxy ticket2', $content); } From 592c81cfcb18650816e28200db3718f156972dfd Mon Sep 17 00:00:00 2001 From: leo108 Date: Fri, 28 Oct 2016 17:54:25 +0800 Subject: [PATCH 14/17] 1. invalid pg-tickets when user logout 2. add config to enable/disable ssl check when doing pgt call --- config/cas.php | 16 ++++++++----- src/Http/Controllers/SecurityController.php | 21 ++++++++++++----- src/Http/Controllers/ValidateController.php | 2 +- src/Repositories/PGTicketRepository.php | 12 ++++++++-- src/Services/PGTCaller.php | 9 +++++++- .../Controllers/SecurityControllerTest.php | 22 +++++++++++++++--- tests/Repositories/PGTicketRepositoryTest.php | 23 +++++++++++++++++++ 7 files changed, 86 insertions(+), 19 deletions(-) diff --git a/config/cas.php b/config/cas.php index 844ed39..a1f3c5b 100644 --- a/config/cas.php +++ b/config/cas.php @@ -1,19 +1,23 @@ env('CAS_LOCK_TIMEOUT', 5000), - 'ticket_expire' => env('CAS_TICKET_EXPIRE', 300), - 'ticket_len' => env('CAS_TICKET_LEN', 32), - 'user_table' => [ + 'lock_timeout' => env('CAS_LOCK_TIMEOUT', 5000), + 'ticket_expire' => env('CAS_TICKET_EXPIRE', 300), + 'ticket_len' => env('CAS_TICKET_LEN', 32), + 'pg_ticket_expire' => env('CAS_PROXY_GRANTING_TICKET_EXPIRE', 7200), + 'pg_ticket_len' => env('CAS_PROXY_GRANTING_TICKET_LEN', 64), + 'pg_ticket_iou_len' => env('CAS_PROXY_GRANTING_TICKET_IOU_LEN', 64), + 'verify_ssl' => env('CAS_VERIFY_SSL', true), + 'user_table' => [ 'id' => 'id', 'name' => 'users', 'model' => \App\User::class, //change to your user model class ], - 'router' => [ + 'router' => [ 'prefix' => 'cas', 'name_prefix' => 'cas.', ], - 'middleware' => [ + 'middleware' => [ 'common' => 'web', 'auth' => 'auth', ], diff --git a/src/Http/Controllers/SecurityController.php b/src/Http/Controllers/SecurityController.php index 967ed3c..9d56f9d 100644 --- a/src/Http/Controllers/SecurityController.php +++ b/src/Http/Controllers/SecurityController.php @@ -14,6 +14,7 @@ use Leo108\CAS\Events\CasUserLogoutEvent; use Leo108\CAS\Exceptions\CAS\CasException; use Illuminate\Http\Request; +use Leo108\CAS\Repositories\PGTicketRepository; use Leo108\CAS\Repositories\ServiceRepository; use Leo108\CAS\Repositories\TicketRepository; @@ -29,6 +30,10 @@ class SecurityController extends Controller */ protected $ticketRepository; + /** + * @var PGTicketRepository + */ + protected $pgTicketRepository; /** * @var UserLogin */ @@ -36,18 +41,21 @@ class SecurityController extends Controller /** * SecurityController constructor. - * @param ServiceRepository $serviceRepository - * @param TicketRepository $ticketRepository - * @param UserLogin $loginInteraction + * @param ServiceRepository $serviceRepository + * @param TicketRepository $ticketRepository + * @param PGTicketRepository $pgTicketRepository + * @param UserLogin $loginInteraction */ public function __construct( ServiceRepository $serviceRepository, TicketRepository $ticketRepository, + PGTicketRepository $pgTicketRepository, UserLogin $loginInteraction ) { - $this->serviceRepository = $serviceRepository; - $this->ticketRepository = $ticketRepository; - $this->loginInteraction = $loginInteraction; + $this->serviceRepository = $serviceRepository; + $this->ticketRepository = $ticketRepository; + $this->loginInteraction = $loginInteraction; + $this->pgTicketRepository = $pgTicketRepository; } public function showLogin(Request $request) @@ -119,6 +127,7 @@ public function logout(Request $request) $user = $this->loginInteraction->getCurrentUser($request); if ($user) { $this->loginInteraction->logout($request); + $this->pgTicketRepository->invalidTicketByUser($user); event(new CasUserLogoutEvent($request, $user)); } $service = $request->get('service'); diff --git a/src/Http/Controllers/ValidateController.php b/src/Http/Controllers/ValidateController.php index 460ae0b..72d3ead 100644 --- a/src/Http/Controllers/ValidateController.php +++ b/src/Http/Controllers/ValidateController.php @@ -204,7 +204,7 @@ protected function casValidate(Request $request, $returnAttr, $allowProxy) if ($pgtUrl) { try { $pgTicket = $this->pgTicketRepository->applyTicket($user, $pgtUrl, $proxies); - $iou = $this->ticketGenerator->generateOne(config('cas.ticket_len', 32), 'PGTIOU-'); + $iou = $this->ticketGenerator->generateOne(config('cas.pg_ticket_iou_len', 64), 'PGTIOU-'); if (!$this->pgtCaller->call($pgtUrl, $pgTicket->ticket, $iou)) { $iou = null; } diff --git a/src/Repositories/PGTicketRepository.php b/src/Repositories/PGTicketRepository.php index 7a761e6..23f54d7 100644 --- a/src/Repositories/PGTicketRepository.php +++ b/src/Repositories/PGTicketRepository.php @@ -61,6 +61,14 @@ public function getByTicket($ticket, $checkExpired = true) return ($checkExpired && $record->isExpired()) ? null : $record; } + /** + * @param UserModel $user + */ + public function invalidTicketByUser(UserModel $user) + { + $this->pgTicket->where('user_id', $user->getEloquentModel()->getKey())->delete(); + } + /** * @param UserModel $user * @param string $pgtUrl @@ -75,14 +83,14 @@ public function applyTicket(UserModel $user, $pgtUrl, $proxies = []) throw new CasException(CasException::UNAUTHORIZED_SERVICE_PROXY); } - $ticket = $this->getAvailableTicket(config('cas.ticket_len', 32)); + $ticket = $this->getAvailableTicket(config('cas.pg_ticket_len', 64)); if ($ticket === false) { throw new CasException(CasException::INTERNAL_ERROR, 'apply proxy-granting ticket failed'); } $record = $this->pgTicket->newInstance( [ 'ticket' => $ticket, - 'expire_at' => new Carbon(sprintf('+%dsec', config('cas.ticket_expire', 300))), + 'expire_at' => new Carbon(sprintf('+%dsec', config('cas.pg_ticket_expire', 7200))), 'created_at' => new Carbon(), 'pgt_url' => $pgtUrl, 'proxies' => $proxies, diff --git a/src/Services/PGTCaller.php b/src/Services/PGTCaller.php index 6a43313..508d0a7 100644 --- a/src/Services/PGTCaller.php +++ b/src/Services/PGTCaller.php @@ -9,6 +9,7 @@ namespace Leo108\CAS\Services; use GuzzleHttp\Client; +use Illuminate\Support\Facades\Log; class PGTCaller { @@ -41,10 +42,16 @@ public function call($pgtUrl, $pgt, $pgtiou) parse_str(parse_url($pgtUrl, PHP_URL_QUERY), $originQuery); try { - $res = $this->client->get($pgtUrl, ['query' => array_merge($originQuery, $query)]); + $option = [ + 'query' => array_merge($originQuery, $query), + 'verify' => config('cas.verify_ssl', true), + ]; + $res = $this->client->get($pgtUrl, $option); return $res->getStatusCode() == 200; } catch (\Exception $e) { + Log::warning('call pgt url failed, msg:'.$e->getMessage()); + return false; } } diff --git a/tests/Http/Controllers/SecurityControllerTest.php b/tests/Http/Controllers/SecurityControllerTest.php index ecfb790..c3737d2 100644 --- a/tests/Http/Controllers/SecurityControllerTest.php +++ b/tests/Http/Controllers/SecurityControllerTest.php @@ -15,6 +15,7 @@ use Leo108\CAS\Events\CasUserLoginEvent; use Leo108\CAS\Events\CasUserLogoutEvent; use Leo108\CAS\Exceptions\CAS\CasException; +use Leo108\CAS\Repositories\PGTicketRepository; use Leo108\CAS\Repositories\ServiceRepository; use Leo108\CAS\Repositories\TicketRepository; use TestCase; @@ -283,6 +284,7 @@ public function testLogoutWhenNotLoggedInWithoutService() public function testLogoutWithoutService() { + $user = Mockery::mock(UserModel::class); $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service']) @@ -293,7 +295,7 @@ public function testLogoutWithoutService() ->shouldReceive('logout') ->once() ->shouldReceive('getCurrentUser') - ->andReturn(Mockery::mock(UserModel::class)) + ->andReturn($user) ->once() ->shouldReceive('showLoggedOut') ->with($request) @@ -301,12 +303,19 @@ public function testLogoutWithoutService() ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('invalidTicketByUser') + ->with($user) + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); $this->expectsEvents(CasUserLogoutEvent::class); $this->assertEquals('showLoggedOut called', app(SecurityController::class)->logout($request)); } public function testLogoutWithValidService() { + $user = Mockery::mock(UserModel::class); $serviceRepository = Mockery::mock(ServiceRepository::class) ->shouldReceive('isUrlValid') ->andReturn(true) @@ -317,16 +326,22 @@ public function testLogoutWithValidService() ->shouldReceive('logout') ->once() ->shouldReceive('getCurrentUser') - ->andReturn(Mockery::mock(UserModel::class)) + ->andReturn($user) ->once() ->getMock(); app()->instance(UserLogin::class, $loginInteraction); - $request = Mockery::mock(Request::class) + $request = Mockery::mock(Request::class) ->shouldReceive('get') ->withArgs(['service']) ->andReturn('http://leo108.com') ->once() ->getMock(); + $pgTicketRepository = Mockery::mock(PGTicketRepository::class) + ->shouldReceive('invalidTicketByUser') + ->with($user) + ->once() + ->getMock(); + app()->instance(PGTicketRepository::class, $pgTicketRepository); $this->expectsEvents(CasUserLogoutEvent::class); $resp = app(SecurityController::class)->logout($request); $this->assertInstanceOf(RedirectResponse::class, $resp); @@ -373,6 +388,7 @@ protected function initController() [ app(ServiceRepository::class), app(TicketRepository::class), + app(PGTicketRepository::class), app(UserLogin::class), ] ); diff --git a/tests/Repositories/PGTicketRepositoryTest.php b/tests/Repositories/PGTicketRepositoryTest.php index 1f504cc..dc7ed6f 100644 --- a/tests/Repositories/PGTicketRepositoryTest.php +++ b/tests/Repositories/PGTicketRepositoryTest.php @@ -10,6 +10,7 @@ use Exception; +use Illuminate\Database\Eloquent\Model; use Leo108\CAS\Contracts\Models\UserModel; use Leo108\CAS\Exceptions\CAS\CasException; use Leo108\CAS\Models\PGTicket; @@ -163,6 +164,28 @@ public function testGetByTicket() $this->assertNull(app()->make(PGTicketRepository::class)->getByTicket('what ever')); } + public function testInvalidTicketByUser() + { + $model = Mockery::mock(Model::class) + ->shouldReceive('getKey') + ->andReturn(1) + ->once() + ->getMock(); + $user = Mockery::mock(UserModel::class) + ->shouldReceive('getEloquentModel') + ->andReturn($model) + ->once() + ->getMock(); + $mockTicket = Mockery::mock(PGTicket::class) + ->shouldReceive('where') + ->with('user_id', 1) + ->andReturn(Mockery::mock()->shouldReceive('delete')->once()->getMock()) + ->once() + ->getMock(); + app()->instance(PGTicket::class, $mockTicket); + app()->make(PGTicketRepository::class)->invalidTicketByUser($user); + } + public function testGetAvailableTicket() { $length = 32; From 9e6c55eeefec9522fb393b7a8bc78f502083e2c9 Mon Sep 17 00:00:00 2001 From: leo108 Date: Fri, 28 Oct 2016 18:12:02 +0800 Subject: [PATCH 15/17] change column length --- ...24_create_proxy_granting_tickets_table.php | 2 +- ..._29_083634_change_ticket_column_length.php | 31 +++++++++++++++++++ 2 files changed, 32 insertions(+), 1 deletion(-) create mode 100644 database/migrations/2016_10_29_083634_change_ticket_column_length.php diff --git a/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php index f2424d8..64868f6 100644 --- a/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php +++ b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php @@ -14,7 +14,7 @@ public function up() { Schema::create('cas_proxy_granting_tickets', function (Blueprint $table) { $table->increments('id'); - $table->string('ticket', 32)->unique(); + $table->string('ticket', 256)->unique(); $table->string('pgt_url', 1024); $table->integer('service_id')->unsigned(); $table->integer('user_id')->unsigned(); diff --git a/database/migrations/2016_10_29_083634_change_ticket_column_length.php b/database/migrations/2016_10_29_083634_change_ticket_column_length.php new file mode 100644 index 0000000..64a6238 --- /dev/null +++ b/database/migrations/2016_10_29_083634_change_ticket_column_length.php @@ -0,0 +1,31 @@ +string('ticket', 256)->change(); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('cas_tickets', function (Blueprint $table) { + $table->string('ticket', 32)->change(); + }); + } +} From 3bb2597f5ce92deee0b68200efd198d7f56b121d Mon Sep 17 00:00:00 2001 From: leo108 Date: Sat, 29 Oct 2016 16:54:00 +0800 Subject: [PATCH 16/17] migrate is not necessary for test --- tests/TestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/TestCase.php b/tests/TestCase.php index 96596e5..03b47a4 100644 --- a/tests/TestCase.php +++ b/tests/TestCase.php @@ -50,7 +50,7 @@ public function setUp() $this->app['config']->set('database.default', 'sqlite'); $this->app['config']->set('database.connections.sqlite.database', ':memory:'); - $this->migrate(); + //$this->migrate(); } /** From 813e51ea152c81d0cdc07f0f988d463e466532f7 Mon Sep 17 00:00:00 2001 From: leo108 Date: Sat, 29 Oct 2016 17:05:53 +0800 Subject: [PATCH 17/17] fix migration down error --- .../2016_10_25_083524_create_proxy_granting_tickets_table.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php index 64868f6..17115f1 100644 --- a/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php +++ b/database/migrations/2016_10_25_083524_create_proxy_granting_tickets_table.php @@ -33,6 +33,6 @@ public function up() */ public function down() { - Schema::drop('proxy_granting_tickets'); + Schema::drop('cas_proxy_granting_tickets'); } }