View source
<?php
namespace Drupal\Tests\cas\Unit\Service;
use Drupal\Tests\UnitTestCase;
use Drupal\cas\Service\CasValidator;
use Drupal\cas\CasPropertyBag;
use GuzzleHttp\Client;
use GuzzleHttp\Handler\MockHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Middleware;
use Drupal\cas\Service\CasHelper;
use Symfony\Component\EventDispatcher\Event;
class CasValidatorTest extends UnitTestCase {
protected $eventDispatcher;
protected $events;
public function setUp() {
parent::setUp();
$this->eventDispatcher = $this
->getMockBuilder('\\Symfony\\Component\\EventDispatcher\\EventDispatcherInterface')
->disableOriginalConstructor()
->getMock();
}
public function dispatchEvent($event_name, Event $event) {
$this->events[$event_name] = $event;
switch ($event_name) {
case CasHelper::EVENT_PRE_VALIDATE:
$event
->setValidationPath("customPath");
$event
->setParameter("foo", "bar");
break;
case CasHelper::EVENT_POST_VALIDATE:
$propertyBag = $event
->getCasPropertyBag();
$propertyBag
->setAttribute('email', [
'modified@example.com',
]);
break;
}
}
public function testValidateTicket($ticket, array $service_params, $username, $response, $validationRequestUrl, $version, $ssl_verification, $is_proxy, $can_be_proxied, $proxy_chains) {
$mock = new MockHandler([
new Response(200, [], $response),
]);
$handler = HandlerStack::create($mock);
$guzzleTransactions = [];
$history = Middleware::history($guzzleTransactions);
$handler
->push($history);
$httpClient = new Client([
'handler' => $handler,
]);
$configFactory = $this
->getConfigFactoryStub([
'cas.settings' => [
'server.hostname' => 'example-server.com',
'server.port' => 443,
'server.protocol' => 'https',
'server.path' => '/cas',
'server.version' => $version,
'server.verify' => $ssl_verification,
'server.cert' => 'foo',
'proxy.initialize' => $is_proxy,
'proxy.can_be_proxied' => $can_be_proxied,
'proxy.proxy_chains' => $proxy_chains,
],
]);
if (!empty($service_params)) {
$params = '';
foreach ($service_params as $key => $value) {
$params .= '&' . $key . '=' . urlencode($value);
}
$params = '?' . substr($params, 1);
$return_value = 'https://example.com/client' . $params;
}
else {
$return_value = 'https://example.com/client';
}
$urlGenerator = $this
->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
$urlGenerator
->expects($this
->once())
->method('generate')
->will($this
->returnValue($return_value));
$urlGenerator
->expects($this
->any())
->method('generateFromRoute')
->will($this
->returnValue('https://example.com/casproxycallback'));
$casHelper = $this
->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')
->disableOriginalConstructor()
->getMock();
$casValidator = new CasValidator($httpClient, $casHelper, $configFactory, $urlGenerator, $this->eventDispatcher);
$property_bag = $casValidator
->validateTicket($ticket, $service_params);
$this
->assertEquals($username, $property_bag
->getUsername());
$validationTransaction = array_shift($guzzleTransactions);
$this
->assertEquals((string) $validationTransaction['request']
->getUri(), $validationRequestUrl);
}
public function validateTicketDataProvider() {
$testCases = [];
$username = $this
->randomMachineName(8);
$response = "yes\n{$username}\n";
$testCases[] = [
'ST-123456',
[],
$username,
$response,
'https://example-server.com/cas/validate?service=https%3A//example.com/client&ticket=ST-123456',
'1.0',
CasHelper::CA_CUSTOM,
FALSE,
FALSE,
'',
];
$username = $this
->randomMachineName(8);
$response = "yes\n{$username}\n";
$testCases[] = [
'ST-123456',
[
'returnto' => 'node/1',
],
$username,
$response,
'https://example-server.com/cas/validate?service=https%3A//example.com/client%3Freturnto%3Dnode%252F1&ticket=ST-123456',
'1.0',
CasHelper::CA_CUSTOM,
FALSE,
FALSE,
'',
];
$username = $this
->randomMachineName(8);
$response = "<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>{$username}</cas:user>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$testCases[] = [
'ST-123456',
[],
$username,
$response,
'https://example-server.com/cas/serviceValidate?service=https%3A//example.com/client&ticket=ST-123456',
'2.0',
CasHelper::CA_NONE,
FALSE,
FALSE,
'',
];
$username = $this
->randomMachineName(8);
$pgt_iou = $this
->randomMachineName(24);
$response = "<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>{$username}</cas:user>\n <cas:proxyGrantingTicket>PGTIOU-{$pgt_iou}\n </cas:proxyGrantingTicket>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$testCases[] = [
'ST-123456',
[],
$username,
$response,
'https://example-server.com/cas/serviceValidate?service=https%3A//example.com/client&ticket=ST-123456&pgtUrl=https%3A//example.com/casproxycallback',
'2.0',
CasHelper::CA_DEFAULT,
TRUE,
FALSE,
'',
];
$username = $this
->randomMachineName(8);
$proxy_chains = '/https:\\/\\/example\\.com/ /https:\\/\\/foo\\.com/' . PHP_EOL . '/https:\\/\\/bar\\.com/';
$response = "<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>{$username}</cas:user>\n <cas:proxies>\n <cas:proxy>https://example.com</cas:proxy>\n <cas:proxy>https://foo.com</cas:proxy>\n </cas:proxies>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$testCases[] = [
'ST-123456',
[],
$username,
$response,
'https://example-server.com/cas/proxyValidate?service=https%3A//example.com/client&ticket=ST-123456',
'2.0',
CasHelper::CA_DEFAULT,
FALSE,
TRUE,
$proxy_chains,
];
$username = $this
->randomMachineName(8);
$pgt_iou = $this
->randomMachineName(24);
$response = "<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>\n <cas:authenticationSuccess>\n <cas:user>{$username}</cas:user>\n <cas:proxyGrantingTicket>PGTIOU-{$pgt_iou}</cas:proxyGrantingTicket>\n <cas:proxies>\n <cas:proxy>https://https://bar.com</cas:proxy>\n </cas:proxies>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$testCases[] = [
'ST-123456',
[],
$username,
$response,
'https://example-server.com/cas/proxyValidate?service=https%3A//example.com/client&ticket=ST-123456&pgtUrl=https%3A//example.com/casproxycallback',
'2.0',
CasHelper::CA_DEFAULT,
TRUE,
TRUE,
$proxy_chains,
];
return $testCases;
}
public function testValidateTicketException($version, $response, $is_proxy, $can_be_proxied, $proxy_chains, $exception, $exception_message, $http_client_exception) {
if ($http_client_exception) {
$mock = new MockHandler([
new RequestException($exception_message, new Request('GET', 'test')),
]);
}
else {
$mock = new MockHandler([
new Response(200, [], $response),
]);
}
$handler = HandlerStack::create($mock);
$httpClient = new Client([
'handler' => $handler,
]);
$casHelper = $this
->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')
->disableOriginalConstructor()
->getMock();
$configFactory = $this
->getConfigFactoryStub([
'cas.settings' => [
'server.hostname' => 'example.com',
'server.port' => 443,
'server.path' => '/cas',
'server.version' => $version,
'proxy.initialize' => $is_proxy,
'proxy.can_be_proxied' => $can_be_proxied,
'proxy.proxy_chains' => $proxy_chains,
],
]);
$urlGenerator = $this
->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
$casValidator = new CasValidator($httpClient, $casHelper, $configFactory, $urlGenerator, $this->eventDispatcher);
$this
->expectException($exception, $exception_message);
$ticket = $this
->randomMachineName(24);
$casValidator
->validateTicket($ticket, []);
}
public function validateTicketExceptionDataProvider() {
$exception_type = '\\Drupal\\cas\\Exception\\CasValidateException';
$params[] = [
'2.0',
'',
FALSE,
FALSE,
'',
$exception_type,
'External http client exception',
TRUE,
];
$params[] = [
'1.0',
"no\n\n",
FALSE,
FALSE,
'',
$exception_type,
'Ticket did not pass validation.',
FALSE,
];
$params[] = [
'1.0',
"Foo\nBar?\n",
FALSE,
FALSE,
'',
$exception_type,
'Malformed response from CAS server.',
FALSE,
];
$params[] = [
'2.0',
"<> </ </> <<",
FALSE,
FALSE,
'',
$exception_type,
'XML from CAS server is not valid.',
FALSE,
];
$ticket = $this
->randomMachineName(24);
$params[] = [
'2.0',
'<cas:serviceResponse xmlns:cas="http://example.com/cas">
<cas:authenticationFailure code="INVALID_TICKET">
Ticket ' . $ticket . ' not recognized
</cas:authenticationFailure>
</cas:serviceResponse>',
FALSE,
FALSE,
'',
$exception_type,
"Error Code INVALID_TICKET: Ticket {$ticket} not recognized",
FALSE,
];
$params[] = [
'2.0',
"<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authentication>\n Username\n </cas:authentication>\n </cas:serviceResponse>",
FALSE,
FALSE,
'',
$exception_type,
"XML from CAS server is not valid.",
FALSE,
];
$params[] = [
'2.0',
"<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n Username\n </cas:authenticationSuccess>\n </cas:serviceResponse>",
FALSE,
FALSE,
'',
$exception_type,
"No user found in ticket validation response.",
FALSE,
];
$proxy_chains = '/https:\\/\\/example\\.com/ /https:\\/\\/foo\\.com/' . PHP_EOL . '/https:\\/\\/bar\\.com/';
$params[] = [
'2.0',
"<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>username</cas:user>\n <cas:proxies>\n <cas:proxy>https://example.com</cas:proxy>\n <cas:proxy>https://bar.com</cas:proxy>\n </cas:proxies>\n </cas:authenticationSuccess>\n </cas:serviceResponse>",
FALSE,
TRUE,
$proxy_chains,
$exception_type,
"Proxy chain did not match allowed list.",
FALSE,
];
$proxy_chains = 'https://bar.com /https:\\/\\/foo\\.com/' . PHP_EOL . '/https:\\/\\/bar\\.com/';
$params[] = [
'2.0',
"<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>username</cas:user>\n <cas:proxies>\n <cas:proxy>https://example.com</cas:proxy>\n <cas:proxy>https://bar.com</cas:proxy>\n </cas:proxies>\n </cas:authenticationSuccess>\n </cas:serviceResponse>",
FALSE,
TRUE,
$proxy_chains,
$exception_type,
"Proxy chain did not match allowed list.",
FALSE,
];
$params[] = [
'2.0',
"<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>username</cas:user>\n </cas:authenticationSuccess>\n </cas:serviceResponse>",
TRUE,
FALSE,
'',
$exception_type,
"Proxy initialized, but no PGTIOU provided in response.",
FALSE,
];
$params[] = [
'foobarbaz',
"<text>",
FALSE,
FALSE,
'',
$exception_type,
"Unknown CAS protocol version specified: foobarbaz",
FALSE,
];
return $params;
}
public function testParseAttributes() {
$ticket = $this
->randomMachineName(8);
$service_params = [];
$response = "<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>username</cas:user>\n <cas:attributes>\n <cas:email>foo@example.com</cas:email>\n <cas:memberof>cn=foo,o=example</cas:memberof>\n <cas:memberof>cn=bar,o=example</cas:memberof>\n </cas:attributes>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$mock = new MockHandler([
new Response(200, [], $response),
]);
$handler = HandlerStack::create($mock);
$httpClient = new Client([
'handler' => $handler,
]);
$configFactory = $this
->getConfigFactoryStub([
'cas.settings' => [
'server.hostname' => 'example.com',
'server.version' => '2.0',
],
]);
$casHelper = $this
->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')
->disableOriginalConstructor()
->getMock();
$urlGenerator = $this
->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
$casValidator = new CasValidator($httpClient, $casHelper, $configFactory, $urlGenerator, $this->eventDispatcher);
$expected_bag = new CasPropertyBag('username');
$expected_bag
->setAttributes([
'email' => [
'foo@example.com',
],
'memberof' => [
'cn=foo,o=example',
'cn=bar,o=example',
],
]);
$actual_bag = $casValidator
->validateTicket($ticket, $service_params);
$this
->assertEquals($expected_bag, $actual_bag);
}
public function testPostValidateEvent() {
$this->eventDispatcher
->method('dispatch')
->willReturnCallback([
$this,
'dispatchEvent',
]);
$this->events = [];
$ticket = $this
->randomMachineName(8);
$service_params = [];
$response = "<cas:serviceResponse xmlns:cas='http://example.com/cas'>\n <cas:authenticationSuccess>\n <cas:user>username</cas:user>\n <cas:attributes>\n <cas:email>foo@example.com</cas:email>\n <cas:memberof>cn=foo,o=example</cas:memberof>\n <cas:memberof>cn=bar,o=example</cas:memberof>\n </cas:attributes>\n </cas:authenticationSuccess>\n </cas:serviceResponse>";
$mock = new MockHandler([
new Response(200, [], $response),
]);
$handler = HandlerStack::create($mock);
$httpClient = new Client([
'handler' => $handler,
]);
$configFactory = $this
->getConfigFactoryStub([
'cas.settings' => [
'server.hostname' => 'example.com',
'server.version' => '2.0',
],
]);
$casHelper = $this
->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')
->disableOriginalConstructor()
->getMock();
$urlGenerator = $this
->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
$casValidator = new CasValidator($httpClient, $casHelper, $configFactory, $urlGenerator, $this->eventDispatcher);
$expected_bag = new CasPropertyBag('username');
$expected_bag
->setAttributes([
'email' => [
'modified@example.com',
],
'memberof' => [
'cn=foo,o=example',
'cn=bar,o=example',
],
]);
$actual_bag = $casValidator
->validateTicket($ticket, $service_params);
$this
->assertEquals($expected_bag, $actual_bag);
}
public function testPreValidateEvent() {
$this->eventDispatcher
->method('dispatch')
->willReturnCallback([
$this,
'dispatchEvent',
]);
$this->events = [];
$mock = new MockHandler([
new Response(200, [], "yes\nfoobar\n"),
]);
$handler = HandlerStack::create($mock);
$guzzleTransactions = [];
$history = Middleware::history($guzzleTransactions);
$handler
->push($history);
$httpClient = new Client([
'handler' => $handler,
]);
$configFactory = $this
->getConfigFactoryStub([
'cas.settings' => [
'server.hostname' => 'example-server.com',
'server.port' => 443,
'server.protocol' => 'https',
'server.path' => '/cas',
'server.version' => '1.0',
'server.verify' => CasHelper::CA_DEFAULT,
],
]);
$ticket = $this
->randomMachineName(8);
$casHelper = $this
->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')
->disableOriginalConstructor()
->getMock();
$urlGenerator = $this
->createMock('\\Drupal\\Core\\Routing\\UrlGeneratorInterface');
$casValidator = new CasValidator($httpClient, $casHelper, $configFactory, $urlGenerator, $this->eventDispatcher);
$casValidator
->validateTicket($ticket);
$expected_url = "https://example-server.com/cas/customPath?service&ticket=" . $ticket . '&foo=bar';
$actual_url = (string) $guzzleTransactions[0]['request']
->getUri();
$this
->assertEquals($expected_url, $actual_url);
}
}