1*d10b5556SXylle<?php 2*d10b5556SXylle 3*d10b5556SXylle/** 4*d10b5556SXylle * Licensed to Jasig under one or more contributor license 5*d10b5556SXylle * agreements. See the NOTICE file distributed with this work for 6*d10b5556SXylle * additional information regarding copyright ownership. 7*d10b5556SXylle * 8*d10b5556SXylle * Jasig licenses this file to you under the Apache License, 9*d10b5556SXylle * Version 2.0 (the "License"); you may not use this file except in 10*d10b5556SXylle * compliance with the License. You may obtain a copy of the License at: 11*d10b5556SXylle * 12*d10b5556SXylle * http://www.apache.org/licenses/LICENSE-2.0 13*d10b5556SXylle * 14*d10b5556SXylle * Unless required by applicable law or agreed to in writing, software 15*d10b5556SXylle * distributed under the License is distributed on an "AS IS" BASIS, 16*d10b5556SXylle * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17*d10b5556SXylle * See the License for the specific language governing permissions and 18*d10b5556SXylle * limitations under the License. 19*d10b5556SXylle * 20*d10b5556SXylle * PHP Version 7 21*d10b5556SXylle * 22*d10b5556SXylle * @file CAS/ProxiedService/Http/Abstract.php 23*d10b5556SXylle * @category Authentication 24*d10b5556SXylle * @package PhpCAS 25*d10b5556SXylle * @author Adam Franco <afranco@middlebury.edu> 26*d10b5556SXylle * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 27*d10b5556SXylle * @link https://wiki.jasig.org/display/CASC/phpCAS 28*d10b5556SXylle */ 29*d10b5556SXylle 30*d10b5556SXylle/** 31*d10b5556SXylle * This class implements common methods for ProxiedService implementations included 32*d10b5556SXylle * with phpCAS. 33*d10b5556SXylle * 34*d10b5556SXylle * @class CAS_ProxiedService_Http_Abstract 35*d10b5556SXylle * @category Authentication 36*d10b5556SXylle * @package PhpCAS 37*d10b5556SXylle * @author Adam Franco <afranco@middlebury.edu> 38*d10b5556SXylle * @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0 39*d10b5556SXylle * @link https://wiki.jasig.org/display/CASC/phpCAS 40*d10b5556SXylle */ 41*d10b5556SXylleabstract class CAS_ProxiedService_Http_Abstract extends 42*d10b5556SXylleCAS_ProxiedService_Abstract implements CAS_ProxiedService_Http 43*d10b5556SXylle{ 44*d10b5556SXylle /** 45*d10b5556SXylle * The HTTP request mechanism talking to the target service. 46*d10b5556SXylle * 47*d10b5556SXylle * @var CAS_Request_RequestInterface $requestHandler 48*d10b5556SXylle */ 49*d10b5556SXylle protected $requestHandler; 50*d10b5556SXylle 51*d10b5556SXylle /** 52*d10b5556SXylle * The storage mechanism for cookies set by the target service. 53*d10b5556SXylle * 54*d10b5556SXylle * @var CAS_CookieJar $_cookieJar 55*d10b5556SXylle */ 56*d10b5556SXylle private $_cookieJar; 57*d10b5556SXylle 58*d10b5556SXylle /** 59*d10b5556SXylle * Constructor. 60*d10b5556SXylle * 61*d10b5556SXylle * @param CAS_Request_RequestInterface $requestHandler request handler object 62*d10b5556SXylle * @param CAS_CookieJar $cookieJar cookieJar object 63*d10b5556SXylle * 64*d10b5556SXylle * @return void 65*d10b5556SXylle */ 66*d10b5556SXylle public function __construct(CAS_Request_RequestInterface $requestHandler, 67*d10b5556SXylle CAS_CookieJar $cookieJar 68*d10b5556SXylle ) { 69*d10b5556SXylle $this->requestHandler = $requestHandler; 70*d10b5556SXylle $this->_cookieJar = $cookieJar; 71*d10b5556SXylle } 72*d10b5556SXylle 73*d10b5556SXylle /** 74*d10b5556SXylle * The target service url. 75*d10b5556SXylle * @var string $_url; 76*d10b5556SXylle */ 77*d10b5556SXylle private $_url; 78*d10b5556SXylle 79*d10b5556SXylle /** 80*d10b5556SXylle * Answer a service identifier (URL) for whom we should fetch a proxy ticket. 81*d10b5556SXylle * 82*d10b5556SXylle * @return string 83*d10b5556SXylle * @throws Exception If no service url is available. 84*d10b5556SXylle */ 85*d10b5556SXylle public function getServiceUrl() 86*d10b5556SXylle { 87*d10b5556SXylle if (empty($this->_url)) { 88*d10b5556SXylle throw new CAS_ProxiedService_Exception( 89*d10b5556SXylle 'No URL set via ' . get_class($this) . '->setUrl($url).' 90*d10b5556SXylle ); 91*d10b5556SXylle } 92*d10b5556SXylle 93*d10b5556SXylle return $this->_url; 94*d10b5556SXylle } 95*d10b5556SXylle 96*d10b5556SXylle /********************************************************* 97*d10b5556SXylle * Configure the Request 98*d10b5556SXylle *********************************************************/ 99*d10b5556SXylle 100*d10b5556SXylle /** 101*d10b5556SXylle * Set the URL of the Request 102*d10b5556SXylle * 103*d10b5556SXylle * @param string $url url to set 104*d10b5556SXylle * 105*d10b5556SXylle * @return void 106*d10b5556SXylle * @throws CAS_OutOfSequenceException If called after the Request has been sent. 107*d10b5556SXylle */ 108*d10b5556SXylle public function setUrl($url) 109*d10b5556SXylle { 110*d10b5556SXylle if ($this->hasBeenSent()) { 111*d10b5556SXylle throw new CAS_OutOfSequenceException( 112*d10b5556SXylle 'Cannot set the URL, request already sent.' 113*d10b5556SXylle ); 114*d10b5556SXylle } 115*d10b5556SXylle if (!is_string($url)) { 116*d10b5556SXylle throw new CAS_InvalidArgumentException('$url must be a string.'); 117*d10b5556SXylle } 118*d10b5556SXylle 119*d10b5556SXylle $this->_url = $url; 120*d10b5556SXylle } 121*d10b5556SXylle 122*d10b5556SXylle /********************************************************* 123*d10b5556SXylle * 2. Send the Request 124*d10b5556SXylle *********************************************************/ 125*d10b5556SXylle 126*d10b5556SXylle /** 127*d10b5556SXylle * Perform the request. 128*d10b5556SXylle * 129*d10b5556SXylle * @return void 130*d10b5556SXylle * @throws CAS_OutOfSequenceException If called multiple times. 131*d10b5556SXylle * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. 132*d10b5556SXylle * The code of the Exception will be one of: 133*d10b5556SXylle * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE 134*d10b5556SXylle * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE 135*d10b5556SXylle * PHPCAS_SERVICE_PT_FAILURE 136*d10b5556SXylle * @throws CAS_ProxiedService_Exception If there is a failure sending the 137*d10b5556SXylle * request to the target service. 138*d10b5556SXylle */ 139*d10b5556SXylle public function send() 140*d10b5556SXylle { 141*d10b5556SXylle if ($this->hasBeenSent()) { 142*d10b5556SXylle throw new CAS_OutOfSequenceException( 143*d10b5556SXylle 'Cannot send, request already sent.' 144*d10b5556SXylle ); 145*d10b5556SXylle } 146*d10b5556SXylle 147*d10b5556SXylle phpCAS::traceBegin(); 148*d10b5556SXylle 149*d10b5556SXylle // Get our proxy ticket and append it to our URL. 150*d10b5556SXylle $this->initializeProxyTicket(); 151*d10b5556SXylle $url = $this->getServiceUrl(); 152*d10b5556SXylle if (strstr($url, '?') === false) { 153*d10b5556SXylle $url = $url . '?ticket=' . $this->getProxyTicket(); 154*d10b5556SXylle } else { 155*d10b5556SXylle $url = $url . '&ticket=' . $this->getProxyTicket(); 156*d10b5556SXylle } 157*d10b5556SXylle 158*d10b5556SXylle try { 159*d10b5556SXylle $this->makeRequest($url); 160*d10b5556SXylle } catch (Exception $e) { 161*d10b5556SXylle phpCAS::traceEnd(); 162*d10b5556SXylle throw $e; 163*d10b5556SXylle } 164*d10b5556SXylle } 165*d10b5556SXylle 166*d10b5556SXylle /** 167*d10b5556SXylle * Indicator of the number of requests (including redirects performed. 168*d10b5556SXylle * 169*d10b5556SXylle * @var int $_numRequests; 170*d10b5556SXylle */ 171*d10b5556SXylle private $_numRequests = 0; 172*d10b5556SXylle 173*d10b5556SXylle /** 174*d10b5556SXylle * The response headers. 175*d10b5556SXylle * 176*d10b5556SXylle * @var array $_responseHeaders; 177*d10b5556SXylle */ 178*d10b5556SXylle private $_responseHeaders = array(); 179*d10b5556SXylle 180*d10b5556SXylle /** 181*d10b5556SXylle * The response status code. 182*d10b5556SXylle * 183*d10b5556SXylle * @var int $_responseStatusCode; 184*d10b5556SXylle */ 185*d10b5556SXylle private $_responseStatusCode = ''; 186*d10b5556SXylle 187*d10b5556SXylle /** 188*d10b5556SXylle * The response headers. 189*d10b5556SXylle * 190*d10b5556SXylle * @var string $_responseBody; 191*d10b5556SXylle */ 192*d10b5556SXylle private $_responseBody = ''; 193*d10b5556SXylle 194*d10b5556SXylle /** 195*d10b5556SXylle * Build and perform a request, following redirects 196*d10b5556SXylle * 197*d10b5556SXylle * @param string $url url for the request 198*d10b5556SXylle * 199*d10b5556SXylle * @return void 200*d10b5556SXylle * @throws CAS_ProxyTicketException If there is a proxy-ticket failure. 201*d10b5556SXylle * The code of the Exception will be one of: 202*d10b5556SXylle * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE 203*d10b5556SXylle * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE 204*d10b5556SXylle * PHPCAS_SERVICE_PT_FAILURE 205*d10b5556SXylle * @throws CAS_ProxiedService_Exception If there is a failure sending the 206*d10b5556SXylle * request to the target service. 207*d10b5556SXylle */ 208*d10b5556SXylle protected function makeRequest($url) 209*d10b5556SXylle { 210*d10b5556SXylle // Verify that we are not in a redirect loop 211*d10b5556SXylle $this->_numRequests++; 212*d10b5556SXylle if ($this->_numRequests > 4) { 213*d10b5556SXylle $message = 'Exceeded the maximum number of redirects (3) in proxied service request.'; 214*d10b5556SXylle phpCAS::trace($message); 215*d10b5556SXylle throw new CAS_ProxiedService_Exception($message); 216*d10b5556SXylle } 217*d10b5556SXylle 218*d10b5556SXylle // Create a new request. 219*d10b5556SXylle $request = clone $this->requestHandler; 220*d10b5556SXylle $request->setUrl($url); 221*d10b5556SXylle 222*d10b5556SXylle // Add any cookies to the request. 223*d10b5556SXylle $request->addCookies($this->_cookieJar->getCookies($url)); 224*d10b5556SXylle 225*d10b5556SXylle // Add any other parts of the request needed by concrete classes 226*d10b5556SXylle $this->populateRequest($request); 227*d10b5556SXylle 228*d10b5556SXylle // Perform the request. 229*d10b5556SXylle phpCAS::trace('Performing proxied service request to \'' . $url . '\''); 230*d10b5556SXylle if (!$request->send()) { 231*d10b5556SXylle $message = 'Could not perform proxied service request to URL`' 232*d10b5556SXylle . $url . '\'. ' . $request->getErrorMessage(); 233*d10b5556SXylle phpCAS::trace($message); 234*d10b5556SXylle throw new CAS_ProxiedService_Exception($message); 235*d10b5556SXylle } 236*d10b5556SXylle 237*d10b5556SXylle // Store any cookies from the response; 238*d10b5556SXylle $this->_cookieJar->storeCookies($url, $request->getResponseHeaders()); 239*d10b5556SXylle 240*d10b5556SXylle // Follow any redirects 241*d10b5556SXylle if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders()) 242*d10b5556SXylle ) { 243*d10b5556SXylle phpCAS::trace('Found redirect:' . $redirectUrl); 244*d10b5556SXylle $this->makeRequest($redirectUrl); 245*d10b5556SXylle } else { 246*d10b5556SXylle 247*d10b5556SXylle $this->_responseHeaders = $request->getResponseHeaders(); 248*d10b5556SXylle $this->_responseBody = $request->getResponseBody(); 249*d10b5556SXylle $this->_responseStatusCode = $request->getResponseStatusCode(); 250*d10b5556SXylle } 251*d10b5556SXylle } 252*d10b5556SXylle 253*d10b5556SXylle /** 254*d10b5556SXylle * Add any other parts of the request needed by concrete classes 255*d10b5556SXylle * 256*d10b5556SXylle * @param CAS_Request_RequestInterface $request request interface object 257*d10b5556SXylle * 258*d10b5556SXylle * @return void 259*d10b5556SXylle */ 260*d10b5556SXylle abstract protected function populateRequest( 261*d10b5556SXylle CAS_Request_RequestInterface $request 262*d10b5556SXylle ); 263*d10b5556SXylle 264*d10b5556SXylle /** 265*d10b5556SXylle * Answer a redirect URL if a redirect header is found, otherwise null. 266*d10b5556SXylle * 267*d10b5556SXylle * @param array $responseHeaders response header to extract a redirect from 268*d10b5556SXylle * 269*d10b5556SXylle * @return string|null 270*d10b5556SXylle */ 271*d10b5556SXylle protected function getRedirectUrl(array $responseHeaders) 272*d10b5556SXylle { 273*d10b5556SXylle // Check for the redirect after authentication 274*d10b5556SXylle foreach ($responseHeaders as $header) { 275*d10b5556SXylle if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches) 276*d10b5556SXylle ) { 277*d10b5556SXylle return trim(array_pop($matches)); 278*d10b5556SXylle } 279*d10b5556SXylle } 280*d10b5556SXylle return null; 281*d10b5556SXylle } 282*d10b5556SXylle 283*d10b5556SXylle /********************************************************* 284*d10b5556SXylle * 3. Access the response 285*d10b5556SXylle *********************************************************/ 286*d10b5556SXylle 287*d10b5556SXylle /** 288*d10b5556SXylle * Answer true if our request has been sent yet. 289*d10b5556SXylle * 290*d10b5556SXylle * @return bool 291*d10b5556SXylle */ 292*d10b5556SXylle protected function hasBeenSent() 293*d10b5556SXylle { 294*d10b5556SXylle return ($this->_numRequests > 0); 295*d10b5556SXylle } 296*d10b5556SXylle 297*d10b5556SXylle /** 298*d10b5556SXylle * Answer the headers of the response. 299*d10b5556SXylle * 300*d10b5556SXylle * @return array An array of header strings. 301*d10b5556SXylle * @throws CAS_OutOfSequenceException If called before the Request has been sent. 302*d10b5556SXylle */ 303*d10b5556SXylle public function getResponseHeaders() 304*d10b5556SXylle { 305*d10b5556SXylle if (!$this->hasBeenSent()) { 306*d10b5556SXylle throw new CAS_OutOfSequenceException( 307*d10b5556SXylle 'Cannot access response, request not sent yet.' 308*d10b5556SXylle ); 309*d10b5556SXylle } 310*d10b5556SXylle 311*d10b5556SXylle return $this->_responseHeaders; 312*d10b5556SXylle } 313*d10b5556SXylle 314*d10b5556SXylle /** 315*d10b5556SXylle * Answer HTTP status code of the response 316*d10b5556SXylle * 317*d10b5556SXylle * @return int 318*d10b5556SXylle * @throws CAS_OutOfSequenceException If called before the Request has been sent. 319*d10b5556SXylle */ 320*d10b5556SXylle public function getResponseStatusCode() 321*d10b5556SXylle { 322*d10b5556SXylle if (!$this->hasBeenSent()) { 323*d10b5556SXylle throw new CAS_OutOfSequenceException( 324*d10b5556SXylle 'Cannot access response, request not sent yet.' 325*d10b5556SXylle ); 326*d10b5556SXylle } 327*d10b5556SXylle 328*d10b5556SXylle return $this->_responseStatusCode; 329*d10b5556SXylle } 330*d10b5556SXylle 331*d10b5556SXylle /** 332*d10b5556SXylle * Answer the body of response. 333*d10b5556SXylle * 334*d10b5556SXylle * @return string 335*d10b5556SXylle * @throws CAS_OutOfSequenceException If called before the Request has been sent. 336*d10b5556SXylle */ 337*d10b5556SXylle public function getResponseBody() 338*d10b5556SXylle { 339*d10b5556SXylle if (!$this->hasBeenSent()) { 340*d10b5556SXylle throw new CAS_OutOfSequenceException( 341*d10b5556SXylle 'Cannot access response, request not sent yet.' 342*d10b5556SXylle ); 343*d10b5556SXylle } 344*d10b5556SXylle 345*d10b5556SXylle return $this->_responseBody; 346*d10b5556SXylle } 347*d10b5556SXylle 348*d10b5556SXylle /** 349*d10b5556SXylle * Answer the cookies from the response. This may include cookies set during 350*d10b5556SXylle * redirect responses. 351*d10b5556SXylle * 352*d10b5556SXylle * @return array An array containing cookies. E.g. array('name' => 'val'); 353*d10b5556SXylle */ 354*d10b5556SXylle public function getCookies() 355*d10b5556SXylle { 356*d10b5556SXylle return $this->_cookieJar->getCookies($this->getServiceUrl()); 357*d10b5556SXylle } 358*d10b5556SXylle 359*d10b5556SXylle} 360*d10b5556SXylle?> 361