Search moodle.org's
Developer Documentation

See Release Notes

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