/** * {@inheritdoc} */ public function enhance(array $defaults, Request $request) { if ($this->casHelper->provideCasLogoutOverride($request)) { $defaults['_controller'] = '\\Drupal\\cas\\Controller\\LogoutController::logout'; } return $defaults; }
/** * Handles a page request for our forced login route. */ public function forceLogin() { // TODO: What if CAS is not configured? need to handle that case. $query_params = $this->requestStack->getCurrentRequest()->query->all(); $cas_login_url = $this->casHelper->getServerLoginUrl($query_params); $this->casHelper->log("Cas forced login route, redirecting to: {$cas_login_url}"); return TrustedRedirectResponse::create($cas_login_url, 302); }
/** * Test the logout callback. * * @covers ::logout */ public function testLogout() { $logout_controller = $this->getMockBuilder('Drupal\\cas\\Controller\\LogoutController')->setConstructorArgs(array($this->casHelper, $this->requestStack))->setMethods(array('userLogout'))->getMock(); $this->casHelper->expects($this->once())->method('getServerLogoutUrl')->will($this->returnValue('https://example.com/logout')); $logout_controller->expects($this->once())->method('userLogout'); $response = $logout_controller->logout(); $this->assertTrue($response->isRedirect('https://example.com/logout')); }
/** * Logs a user out of Drupal, then redirects them to the CAS server logout. */ public function logout() { // Get the CAS server logout Url. $logout_url = $this->casHelper->getServerLogoutUrl($this->requestStack->getCurrentRequest()); // Log the user out. This invokes hook_user_logout and destroys the // session. $this->userLogout(); $this->casHelper->log("Drupal session terminated; redirecting to CAS logout at: {$logout_url}"); // Redirect the user to the CAS logout screen. return new TrustedRedirectResponse($logout_url, 302); }
/** * Test the forcedLogin redirect. * * @covers ::forceLogin * @covers ::__construct */ public function testForceLogin() { $request = $this->getMock('\\Symfony\\Component\\HttpFoundation\\Request'); $query = $this->getMock('\\Symfony\\Component\\HttpFoundation\\ParameterBag'); $request->query = $query; $this->requestStack->expects($this->once())->method('getCurrentRequest')->will($this->returnValue($request)); $parameters = array('returnto' => 'node/1', 'foo' => 'bar'); $query->expects($this->once())->method('all')->will($this->returnValue($parameters)); $this->casHelper->expects($this->once())->method('getServerLoginUrl')->with($this->equalTo($parameters))->will($this->returnValue('https://example.com')); $expected_response = new TrustedRedirectResponse('https://example.com', 302); $force_login_controller = new ForceLoginController($this->casHelper, $this->requestStack); $response = $force_login_controller->forceLogin(); $this->assertEquals($expected_response, $response); }
/** * Handles a page request for our forced login route. */ public function forceLogin() { // TODO: What if CAS is not configured? need to handle that case. $query_params = $this->requestStack->getCurrentRequest()->query->all(); $cas_login_url = $this->casHelper->getServerLoginUrl($query_params); $this->casHelper->log("Cas forced login route, redirecting to: {$cas_login_url}"); // This response is OK to cache, but since the redirect URL is dependent on // the configured server settings, we need to add some cache metadata tied // to the settings. $cacheable_metadata = new CacheableMetadata(); $cacheable_metadata->addCacheTags(array('config:cas.settings')); $response = TrustedRedirectResponse::create($cas_login_url, 302); $response->addCacheableDependency($cacheable_metadata); return $response; }
/** * Converts a "returnto" query param to a "destination" query param. * * The original service URL for CAS server may contain a "returnto" query * parameter that was placed there to redirect a user to specific page after * logging in with CAS. * * Drupal has a built in mechanism for doing this, by instead using a * "destination" parameter in the URL. Anytime there's a RedirectResponse * returned, RedirectResponseSubscriber looks for the destination param and * will redirect a user there instead. * * We cannot use this built in method when constructing the service URL, * because when we redirect to the CAS server for login, Drupal would see * our destination parameter in the URL and redirect there instead of CAS. * * However, when we redirect the user after a login success / failure, * we can then convert it back to a "destination" parameter and let Drupal * do it's thing when redirecting. * * @param Request $request * The Symfony request object. */ private function handleReturnToParameter(Request $request) { if ($request->query->has('returnto')) { $this->casHelper->log("Converting returnto parameter to destination."); $request->query->set('destination', $request->query->get('returnto')); } }
/** * Proxy authenticates to a target service. * * Returns cookies from the proxied service in a * CookieJar object for use when later accessing resources. * * @param string $target_service * The service to be proxied. * * @return \GuzzleHttp\Cookie\CookieJar * A CookieJar object (array storage) containing cookies from the * proxied service. * * @throws CasProxyException */ public function proxyAuthenticate($target_service) { // Check to see if we have proxied this application already. if (isset($_SESSION['cas_proxy_helper'][$target_service])) { $cookies = array(); foreach ($_SESSION['cas_proxy_helper'][$target_service] as $cookie) { $cookies[$cookie['Name']] = $cookie['Value']; } $domain = $cookie['Domain']; $jar = CookieJar::fromArray($cookies, $domain); $this->casHelper->log("{$target_service} already proxied. Returning information from session."); return $jar; } if (!($this->casHelper->isProxy() && isset($_SESSION['cas_pgt']))) { // We can't perform proxy authentication in this state. throw new CasProxyException("Session state not sufficient for proxying."); } // Make request to CAS server to retrieve a proxy ticket for this service. $cas_url = $this->getServerProxyURL($target_service); try { $this->casHelper->log("Retrieving proxy ticket from: {$cas_url}"); $response = $this->httpClient->get($cas_url); $this->casHelper->log("Received: " . htmlspecialchars($response->getBody()->__toString())); } catch (ClientException $e) { throw new CasProxyException($e->getMessage()); } $proxy_ticket = $this->parseProxyTicket($response->getBody()); $this->casHelper->log("Extracted proxy ticket: {$proxy_ticket}"); // Make request to target service with our new proxy ticket. // The target service will validate this ticket against the CAS server // and set a cookie that grants authentication for further resource calls. $params['ticket'] = $proxy_ticket; $service_url = $target_service . "?" . UrlHelper::buildQuery($params); $cookie_jar = new CookieJar(); try { $this->casHelper->log("Contacting service: {$service_url}"); $this->httpClient->get($service_url, ['cookies' => $cookie_jar]); } catch (ClientException $e) { throw new CasProxyException($e->getMessage()); } // Store in session storage for later reuse. $_SESSION['cas_proxy_helper'][$target_service] = $cookie_jar->toArray(); $this->casHelper->log("Stored cookies from {$target_service} in session."); return $cookie_jar; }
/** * Route callback for the ProxyGrantingTicket information. * * This function stores the incoming PGTIOU and pgtId parameters so that * the incoming response from the CAS Server can be looked up. */ public function callback() { // @TODO: Check that request is coming from configured CAS server to avoid // filling up the table with bogus pgt values. $request = $this->requestStack->getCurrentRequest(); // Check for both a pgtIou and pgtId parameter. If either is not present, // inform CAS Server of an error. if (!($request->query->get('pgtId') && $request->query->get('pgtIou'))) { $this->casHelper->log("Missing necessary parameters for PGT."); return Response::create('Missing necessary parameters', 400); } else { // Store the pgtIou and pgtId in the database for later use. $pgt_id = $request->query->get('pgtId'); $pgt_iou = $request->query->get('pgtIou'); $this->storePgtMapping($pgt_iou, $pgt_id); $this->casHelper->log("Storing pgtId {$pgt_id} with pgtIou {$pgt_iou}"); // PGT stored properly, tell CAS Server to proceed. return Response::create('OK', 200); } }
/** * Tests the enhance() method. * * @covers ::enhance * * @dataProvider enhanceDataProvider */ public function testEnhance($config) { $this->casHelper->expects($this->once())->method('provideCasLogoutOverride')->willReturn($config); $enhancer = new CasRouteEnhancer($this->casHelper); $defaults = array(); $defaults = $enhancer->enhance($defaults, $this->request); if ($config) { $this->assertArraySubset(['_controller' => '\\Drupal\\cas\\Controller\\LogoutController::logout'], $defaults); } else { $this->assertEmpty($defaults); } }
/** * Parse the attributes list from the CAS Server into an array. * * @param \DOMNodeList $xml_list * An XML element containing attributes. * * @return array * An associative array of attributes. */ private function parseAttributes($xml_list) { $attributes = array(); $node = $xml_list->item(0); foreach ($node->childNodes as $child) { $name = $child->localName; $value = $child->nodeValue; $attributes[$name][] = $value; } $this->casHelper->log("Parsed out attributes: " . print_r($attributes, TRUE)); return $attributes; }
/** * Check is the current request is from a known list of web crawlers. * * We don't want to perform any CAS redirects in this case, because crawlers * need to be able to index the pages. * * @return bool * True if the request is coming from a crawler, false otherwise. */ private function isCrawlerRequest() { $current_request = $this->requestStack->getCurrentRequest(); if ($current_request->server->get('HTTP_USER_AGENT')) { $crawlers = array('Google', 'msnbot', 'Rambler', 'Yahoo', 'AbachoBOT', 'accoona', 'AcoiRobot', 'ASPSeek', 'CrocCrawler', 'Dumbot', 'FAST-WebCrawler', 'GeonaBot', 'Gigabot', 'Lycos', 'MSRBOT', 'Scooter', 'AltaVista', 'IDBot', 'eStyle', 'Scrubby', 'gsa-crawler'); // Return on the first find. foreach ($crawlers as $c) { if (stripos($current_request->server->get('HTTP_USER_AGENT'), $c) !== FALSE) { $this->casHelper->log('CasSubscriber ignoring request from suspected crawler "$c"'); return TRUE; } } } return FALSE; }
/** * Tests that a user is validated and logged in with Drupal acting as proxy. * * @dataProvider parameterDataProvider */ public function testSuccessfulLoginProxyEnabled($returnto) { $this->setupRequestParameters($returnto, FALSE, TRUE); $this->requestStack->expects($this->once())->method('getCurrentRequest')->will($this->returnValue($this->requestObject)); if ($returnto) { $this->assertDestinationSetFromReturnTo(); } // Cas Helper should indicate Drupal is a proxy. $this->casHelper->expects($this->once())->method('isProxy')->will($this->returnValue(TRUE)); $this->assertSuccessfulValidation($returnto, TRUE); $validation_data = new CasPropertyBag('testuser'); $validation_data->setPgt('testpgt'); // Login should be called. $this->casLogin->expects($this->once())->method('loginToDrupal')->with($this->equalTo($validation_data), $this->equalTo('ST-foobar')); // PGT should be saved. $this->casHelper->expects($this->once())->method('storePGTSession')->with($this->equalTo('testpgt')); $this->assertRedirectedToFrontPageOnHandle(); }
/** * Test processing gateway with CHECK_ONCE to make sure SESSION gets set. * * @covers ::handle * @covers ::handleGateway */ public function testHandleGatewayWithCheckOnceSuccess() { $config_factory = $this->getConfigFactoryStub(array('cas.settings' => array('forced_login.enabled' => TRUE, 'forced_login.paths' => array('<front>'), 'gateway.check_frequency' => CasHelper::CHECK_ONCE, 'gateway.paths' => array('<front>')))); $cas_subscriber = $this->getMockBuilder('\\Drupal\\cas\\Subscriber\\CasSubscriber')->setConstructorArgs(array($this->requestStack, $this->routeMatcher, $config_factory, $this->currentUser, $this->conditionManager, $this->casHelper))->setMethods(NULL)->getMock(); $this->event->expects($this->any())->method('getRequestType')->will($this->returnValue(HttpKernelInterface::MASTER_REQUEST)); $request_object = $this->getMock('\\Symfony\\Component\\HttpFoundation\\Request'); $attributes = $this->getMock('\\Symfony\\Component\\HttpFoundation\\ParameterBag'); $request_object->attributes = $attributes; $server = $this->getMock('\\Symfony\\Component\\HttpFoundation\\ServerBag'); $request_object->server = $server; $condition = $this->getMockBuilder('\\Drupal\\Core\\Condition\\ConditionPluginBase')->disableOriginalConstructor()->getMock(); $this->conditionManager->expects($this->any())->method('createInstance')->with('request_path')->will($this->returnValue($condition)); $condition->expects($this->any())->method('setConfiguration')->with(array('<front>')); $this->conditionManager->expects($this->any())->method('execute')->with($condition)->will($this->onConsecutiveCalls(FALSE, TRUE)); $request_object->expects($this->once())->method('isMethod')->with('GET')->will($this->returnValue(TRUE)); $this->requestStack->expects($this->any())->method('getCurrentRequest')->will($this->returnValue($request_object)); $this->casHelper->expects($this->once())->method('getServerLoginUrl')->will($this->returnValue('https://example.com')); $this->event->expects($this->once())->method('setResponse'); $cas_subscriber->handle($this->event); $this->assertArrayHasKey('cas_gateway_checked', $_SESSION); }
/** * Check is the current request is a normal web request from a user. * * We don't want to perform any CAS redirects for things like cron * and drush. * * @return bool * Whether or not this is a normal request. */ private function isNotNormalRequest() { $current_request = $this->requestStack->getCurrentRequest(); if (stristr($current_request->server->get('SCRIPT_FILENAME'), 'xmlrpc.php')) { return TRUE; } if (stristr($current_request->server->get('SCRIPT_FILENAME'), 'cron.php')) { $this->casHelper->log("Skip processing requests for cron."); return TRUE; } if ($current_request->server->get('HTTP_USER_AGENT')) { $crawlers = array('Google', 'msnbot', 'Rambler', 'Yahoo', 'AbachoBOT', 'accoona', 'AcoiRobot', 'ASPSeek', 'CrocCrawler', 'Dumbot', 'FAST-WebCrawler', 'GeonaBot', 'Gigabot', 'Lycos', 'MSRBOT', 'Scooter', 'AltaVista', 'IDBot', 'eStyle', 'Scrubby', 'gsa-crawler'); // Return on the first find. foreach ($crawlers as $c) { if (stripos($current_request->server->get('HTTP_USER_AGENT'), $c) !== FALSE) { $this->casHelper->log("Ignoring request from {$c}"); return TRUE; } } } return FALSE; }
/** * Test to make sure we don't log when we're not configured to. * * @covers ::log * @covers ::__construct */ public function testLoggingOff() { $config_factory = $this->getConfigFactoryStub(array('cas.settings' => array('debugging.log' => FALSE))); $cas_helper = new CasHelper($config_factory, $this->urlGenerator, $this->connection, $this->loggerFactory, $this->session); $this->loggerChannel->expects($this->never())->method('log'); $cas_helper->log('This is a test, but should not be logged as such.'); }