/** * Handles a request to either validate a user login or log a user out. * * The path that this controller/action handle are always set to the "service" * when authenticating with the CAS server, so CAS server communicates back to * the Drupal site using this controller. */ public function handle() { $request = $this->requestStack->getCurrentRequest(); // First, check if this is a single-log-out (SLO) request from the server. if ($request->request->has('logoutRequest')) { $this->casHelper->log("Logout request: passing to casLogout::handleSlo"); $this->casLogout->handleSlo($request->request->get('logoutRequest')); // Always return a 200 code. CAS Server doesn’t care either way what // happens here, since it is a fire-and-forget approach taken. return Response::create('', 200); } // Our CAS Subscriber, which implements forced redirect and gateway, will // set this query string param which indicates we should disable the // subscriber on the next redirect. This prevents an infinite redirect loop. if ($request->query->has('cas_temp_disable')) { $this->casHelper->log("Temp disable flag set, set session flag."); $_SESSION['cas_temp_disable'] = TRUE; } // Check if there is a ticket parameter. If there isn't, we could be // returning from a gateway request and the user may not be logged into CAS. // Just redirect away from here. if (!$request->query->has('ticket')) { $this->casHelper->log("No ticket detected, move along."); $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); } $ticket = $request->query->get('ticket'); // Our CAS service will need to reconstruct the original service URL // when validating the ticket. We always know what the base URL for // the service URL (it's this page), but there may be some query params // attached as well (like a destination param) that we need to pass in // as well. So, detach the ticket param, and pass the rest off. $service_params = $request->query->all(); unset($service_params['ticket']); $cas_version = $this->casHelper->getCasProtocolVersion(); $this->casHelper->log("Configured to use CAS protocol version: {$cas_version}"); try { $cas_validation_info = $this->casValidator->validateTicket($cas_version, $ticket, $service_params); } catch (CasValidateException $e) { // Validation failed, redirect to homepage and set message. $this->setMessage(t('There was a problem validating your login, please contact a site administrator.'), 'error'); $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); } try { $this->casLogin->loginToDrupal($cas_validation_info, $ticket); if ($this->casHelper->isProxy() && $cas_validation_info->getPgt()) { $this->casHelper->log("Storing PGT information for this session."); $this->casHelper->storePGTSession($cas_validation_info->getPgt()); } $this->setMessage(t('You have been logged in.')); } catch (CasLoginException $e) { $this->setMessage(t('There was a problem logging in, please contact a site administrator.'), 'error'); } $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); }
/** * Asserts that validation is executed. */ private function assertSuccessfulValidation($returnto, $for_proxy = FALSE) { $service_params = array(); if ($returnto) { $service_params['returnto'] = 'node/1'; } $validation_data = new CasPropertyBag('testuser'); if ($for_proxy) { $validation_data->setPgt('testpgt'); } // Validation service should be called for that ticket. $this->casValidator->expects($this->once())->method('validateTicket')->with($this->equalTo('ST-foobar'), $this->equalTo($service_params))->will($this->returnValue($validation_data)); }
/** * Test parsing out CAS attributes from response. * * @covers ::validateVersion2 * @covers ::parseAttributes */ public function testParseAttributes() { $ticket = $this->randomMachineName(8); $service_params = array(); $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, array(), $response)]); $handler = HandlerStack::create($mock); $httpClient = new Client(['handler' => $handler]); $casHelper = $this->getMockBuilder('\\Drupal\\cas\\Service\\CasHelper')->disableOriginalConstructor()->getMock(); $casHelper->expects($this->any())->method('getCasProtocolVersion')->willReturn('2.0'); $casValidator = new CasValidator($httpClient, $casHelper); $expected_bag = new CasPropertyBag('username'); $expected_bag->setAttributes(array('email' => array('*****@*****.**'), 'memberof' => array('cn=foo,o=example', 'cn=bar,o=example'))); $actual_bag = $casValidator->validateTicket($ticket, $service_params); $this->assertEquals($expected_bag, $actual_bag); }
/** * Main point of communication between CAS server and the Drupal site. * * The path that this controller/action handle are always set to the "service" * url when authenticating with the CAS server, so CAS server communicates * back to the Drupal site using this controller action. That's why there's * so much going on in here - it needs to process a few different types of * requests. */ public function handle() { $request = $this->requestStack->getCurrentRequest(); // First, check if this is a single-log-out (SLO) request from the server. if ($request->request->has('logoutRequest')) { try { $this->casLogout->handleSlo($request->request->get('logoutRequest')); } catch (CasSloException $e) { $this->casHelper->log($e->getMessage()); } // Always return a 200 code. CAS Server doesn’t care either way what // happens here, since it is a fire-and-forget approach taken. return Response::create('', 200); } // We will be redirecting the user below. To prevent the CasSubscriber from // initiating an automatic authentiation on the that request (like forced // auth or gateway auth) and potentially creating an authentication loop, // we set a session variable instructing the CasSubscriber skip auto auth // for that request. $request->getSession()->set('cas_temp_disable_auto_auth', TRUE); /* If there is no ticket parameter on the request, the browser either: * (a) is returning from a gateway request to the CAS server in which * the user was not already authenticated to CAS, so there is no * service ticket to validate and nothing to do. * (b) has hit this URL for some other reason (crawler, curiosity, etc) * and there is nothing to do. * In either case, we just want to redirect them away from this controller. */ if (!$request->query->has('ticket')) { $this->casHelper->log("No ticket detected, move along."); $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); } // There is a ticket present, meaning CAS server has returned the browser // to the Drupal site so we can authenticate the user locally using the // ticket. $ticket = $request->query->get('ticket'); // Our CAS service will need to reconstruct the original service URL // when validating the ticket. We always know what the base URL for // the service URL (it's this page), but there may be some query params // attached as well (like a destination param) that we need to pass in // as well. So, detach the ticket param, and pass the rest off. $service_params = $request->query->all(); unset($service_params['ticket']); try { $cas_validation_info = $this->casValidator->validateTicket($ticket, $service_params); } catch (CasValidateException $e) { // Validation failed, redirect to homepage and set message. $this->casHelper->log($e->getMessage()); $this->setMessage($this->t('There was a problem validating your login, please contact a site administrator.'), 'error'); $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); } // Now that the ticket has been validated, we can use the information from // validation request to authenticate the user locally on the Drupal site. try { $this->casLogin->loginToDrupal($cas_validation_info, $ticket); if ($this->casHelper->isProxy() && $cas_validation_info->getPgt()) { $this->casHelper->log("Storing PGT information for this session."); $this->casHelper->storePgtSession($cas_validation_info->getPgt()); } $this->setMessage($this->t('You have been logged in.')); } catch (CasLoginException $e) { $this->casHelper->log($e->getMessage()); $this->setMessage($this->t('There was a problem logging in, please contact a site administrator.'), 'error'); } // And finally redirect the user to the homepage, or so a specific // destination found in the destination param (like the page they were on // prior to initiating authentication). $this->handleReturnToParameter($request); return RedirectResponse::create($this->urlGenerator->generate('<front>')); }