361 lines
10 KiB
PHP
361 lines
10 KiB
PHP
|
<?php
|
||
|
|
||
|
/**
|
||
|
* Licensed to Jasig under one or more contributor license
|
||
|
* agreements. See the NOTICE file distributed with this work for
|
||
|
* additional information regarding copyright ownership.
|
||
|
*
|
||
|
* Jasig licenses this file to you under the Apache License,
|
||
|
* Version 2.0 (the "License"); you may not use this file except in
|
||
|
* compliance with the License. You may obtain a copy of the License at:
|
||
|
*
|
||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||
|
*
|
||
|
* Unless required by applicable law or agreed to in writing, software
|
||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
* See the License for the specific language governing permissions and
|
||
|
* limitations under the License.
|
||
|
*
|
||
|
* PHP Version 5
|
||
|
*
|
||
|
* @file CAS/ProxiedService/Http/Abstract.php
|
||
|
* @category Authentication
|
||
|
* @package PhpCAS
|
||
|
* @author Adam Franco <afranco@middlebury.edu>
|
||
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||
|
* @link https://wiki.jasig.org/display/CASC/phpCAS
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* This class implements common methods for ProxiedService implementations included
|
||
|
* with phpCAS.
|
||
|
*
|
||
|
* @class CAS_ProxiedService_Http_Abstract
|
||
|
* @category Authentication
|
||
|
* @package PhpCAS
|
||
|
* @author Adam Franco <afranco@middlebury.edu>
|
||
|
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
|
||
|
* @link https://wiki.jasig.org/display/CASC/phpCAS
|
||
|
*/
|
||
|
abstract class CAS_ProxiedService_Http_Abstract extends
|
||
|
CAS_ProxiedService_Abstract implements CAS_ProxiedService_Http
|
||
|
{
|
||
|
/**
|
||
|
* The HTTP request mechanism talking to the target service.
|
||
|
*
|
||
|
* @var CAS_Request_RequestInterface $requestHandler
|
||
|
*/
|
||
|
protected $requestHandler;
|
||
|
|
||
|
/**
|
||
|
* The storage mechanism for cookies set by the target service.
|
||
|
*
|
||
|
* @var CAS_CookieJar $_cookieJar
|
||
|
*/
|
||
|
private $_cookieJar;
|
||
|
|
||
|
/**
|
||
|
* Constructor.
|
||
|
*
|
||
|
* @param CAS_Request_RequestInterface $requestHandler request handler object
|
||
|
* @param CAS_CookieJar $cookieJar cookieJar object
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function __construct(CAS_Request_RequestInterface $requestHandler,
|
||
|
CAS_CookieJar $cookieJar
|
||
|
) {
|
||
|
$this->requestHandler = $requestHandler;
|
||
|
$this->_cookieJar = $cookieJar;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* The target service url.
|
||
|
* @var string $_url;
|
||
|
*/
|
||
|
private $_url;
|
||
|
|
||
|
/**
|
||
|
* Answer a service identifier (URL) for whom we should fetch a proxy ticket.
|
||
|
*
|
||
|
* @return string
|
||
|
* @throws Exception If no service url is available.
|
||
|
*/
|
||
|
public function getServiceUrl()
|
||
|
{
|
||
|
if (empty($this->_url)) {
|
||
|
throw new CAS_ProxiedService_Exception(
|
||
|
'No URL set via ' . get_class($this) . '->setUrl($url).'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $this->_url;
|
||
|
}
|
||
|
|
||
|
/*********************************************************
|
||
|
* Configure the Request
|
||
|
*********************************************************/
|
||
|
|
||
|
/**
|
||
|
* Set the URL of the Request
|
||
|
*
|
||
|
* @param string $url url to set
|
||
|
*
|
||
|
* @return void
|
||
|
* @throws CAS_OutOfSequenceException If called after the Request has been sent.
|
||
|
*/
|
||
|
public function setUrl($url)
|
||
|
{
|
||
|
if ($this->hasBeenSent()) {
|
||
|
throw new CAS_OutOfSequenceException(
|
||
|
'Cannot set the URL, request already sent.'
|
||
|
);
|
||
|
}
|
||
|
if (!is_string($url)) {
|
||
|
throw new CAS_InvalidArgumentException('$url must be a string.');
|
||
|
}
|
||
|
|
||
|
$this->_url = $url;
|
||
|
}
|
||
|
|
||
|
/*********************************************************
|
||
|
* 2. Send the Request
|
||
|
*********************************************************/
|
||
|
|
||
|
/**
|
||
|
* Perform the request.
|
||
|
*
|
||
|
* @return void
|
||
|
* @throws CAS_OutOfSequenceException If called multiple times.
|
||
|
* @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
|
||
|
* The code of the Exception will be one of:
|
||
|
* PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
|
||
|
* PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
|
||
|
* PHPCAS_SERVICE_PT_FAILURE
|
||
|
* @throws CAS_ProxiedService_Exception If there is a failure sending the
|
||
|
* request to the target service.
|
||
|
*/
|
||
|
public function send()
|
||
|
{
|
||
|
if ($this->hasBeenSent()) {
|
||
|
throw new CAS_OutOfSequenceException(
|
||
|
'Cannot send, request already sent.'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
phpCAS::traceBegin();
|
||
|
|
||
|
// Get our proxy ticket and append it to our URL.
|
||
|
$this->initializeProxyTicket();
|
||
|
$url = $this->getServiceUrl();
|
||
|
if (strstr($url, '?') === false) {
|
||
|
$url = $url . '?ticket=' . $this->getProxyTicket();
|
||
|
} else {
|
||
|
$url = $url . '&ticket=' . $this->getProxyTicket();
|
||
|
}
|
||
|
|
||
|
try {
|
||
|
$this->makeRequest($url);
|
||
|
} catch (Exception $e) {
|
||
|
phpCAS::traceEnd();
|
||
|
throw $e;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Indicator of the number of requests (including redirects performed.
|
||
|
*
|
||
|
* @var int $_numRequests;
|
||
|
*/
|
||
|
private $_numRequests = 0;
|
||
|
|
||
|
/**
|
||
|
* The response headers.
|
||
|
*
|
||
|
* @var array $_responseHeaders;
|
||
|
*/
|
||
|
private $_responseHeaders = array();
|
||
|
|
||
|
/**
|
||
|
* The response status code.
|
||
|
*
|
||
|
* @var int $_responseStatusCode;
|
||
|
*/
|
||
|
private $_responseStatusCode = '';
|
||
|
|
||
|
/**
|
||
|
* The response headers.
|
||
|
*
|
||
|
* @var string $_responseBody;
|
||
|
*/
|
||
|
private $_responseBody = '';
|
||
|
|
||
|
/**
|
||
|
* Build and perform a request, following redirects
|
||
|
*
|
||
|
* @param string $url url for the request
|
||
|
*
|
||
|
* @return void
|
||
|
* @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
|
||
|
* The code of the Exception will be one of:
|
||
|
* PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
|
||
|
* PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
|
||
|
* PHPCAS_SERVICE_PT_FAILURE
|
||
|
* @throws CAS_ProxiedService_Exception If there is a failure sending the
|
||
|
* request to the target service.
|
||
|
*/
|
||
|
protected function makeRequest($url)
|
||
|
{
|
||
|
// Verify that we are not in a redirect loop
|
||
|
$this->_numRequests++;
|
||
|
if ($this->_numRequests > 4) {
|
||
|
$message = 'Exceeded the maximum number of redirects (3) in proxied service request.';
|
||
|
phpCAS::trace($message);
|
||
|
throw new CAS_ProxiedService_Exception($message);
|
||
|
}
|
||
|
|
||
|
// Create a new request.
|
||
|
$request = clone $this->requestHandler;
|
||
|
$request->setUrl($url);
|
||
|
|
||
|
// Add any cookies to the request.
|
||
|
$request->addCookies($this->_cookieJar->getCookies($url));
|
||
|
|
||
|
// Add any other parts of the request needed by concrete classes
|
||
|
$this->populateRequest($request);
|
||
|
|
||
|
// Perform the request.
|
||
|
phpCAS::trace('Performing proxied service request to \'' . $url . '\'');
|
||
|
if (!$request->send()) {
|
||
|
$message = 'Could not perform proxied service request to URL`'
|
||
|
. $url . '\'. ' . $request->getErrorMessage();
|
||
|
phpCAS::trace($message);
|
||
|
throw new CAS_ProxiedService_Exception($message);
|
||
|
}
|
||
|
|
||
|
// Store any cookies from the response;
|
||
|
$this->_cookieJar->storeCookies($url, $request->getResponseHeaders());
|
||
|
|
||
|
// Follow any redirects
|
||
|
if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders())
|
||
|
) {
|
||
|
phpCAS::trace('Found redirect:' . $redirectUrl);
|
||
|
$this->makeRequest($redirectUrl);
|
||
|
} else {
|
||
|
|
||
|
$this->_responseHeaders = $request->getResponseHeaders();
|
||
|
$this->_responseBody = $request->getResponseBody();
|
||
|
$this->_responseStatusCode = $request->getResponseStatusCode();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Add any other parts of the request needed by concrete classes
|
||
|
*
|
||
|
* @param CAS_Request_RequestInterface $request request interface object
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
abstract protected function populateRequest(
|
||
|
CAS_Request_RequestInterface $request
|
||
|
);
|
||
|
|
||
|
/**
|
||
|
* Answer a redirect URL if a redirect header is found, otherwise null.
|
||
|
*
|
||
|
* @param array $responseHeaders response header to extract a redirect from
|
||
|
*
|
||
|
* @return string|null
|
||
|
*/
|
||
|
protected function getRedirectUrl(array $responseHeaders)
|
||
|
{
|
||
|
// Check for the redirect after authentication
|
||
|
foreach ($responseHeaders as $header) {
|
||
|
if ( preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches)
|
||
|
) {
|
||
|
return trim(array_pop($matches));
|
||
|
}
|
||
|
}
|
||
|
return null;
|
||
|
}
|
||
|
|
||
|
/*********************************************************
|
||
|
* 3. Access the response
|
||
|
*********************************************************/
|
||
|
|
||
|
/**
|
||
|
* Answer true if our request has been sent yet.
|
||
|
*
|
||
|
* @return bool
|
||
|
*/
|
||
|
protected function hasBeenSent()
|
||
|
{
|
||
|
return ($this->_numRequests > 0);
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Answer the headers of the response.
|
||
|
*
|
||
|
* @return array An array of header strings.
|
||
|
* @throws CAS_OutOfSequenceException If called before the Request has been sent.
|
||
|
*/
|
||
|
public function getResponseHeaders()
|
||
|
{
|
||
|
if (!$this->hasBeenSent()) {
|
||
|
throw new CAS_OutOfSequenceException(
|
||
|
'Cannot access response, request not sent yet.'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $this->_responseHeaders;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Answer HTTP status code of the response
|
||
|
*
|
||
|
* @return int
|
||
|
* @throws CAS_OutOfSequenceException If called before the Request has been sent.
|
||
|
*/
|
||
|
public function getResponseStatusCode()
|
||
|
{
|
||
|
if (!$this->hasBeenSent()) {
|
||
|
throw new CAS_OutOfSequenceException(
|
||
|
'Cannot access response, request not sent yet.'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $this->_responseStatusCode;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Answer the body of response.
|
||
|
*
|
||
|
* @return string
|
||
|
* @throws CAS_OutOfSequenceException If called before the Request has been sent.
|
||
|
*/
|
||
|
public function getResponseBody()
|
||
|
{
|
||
|
if (!$this->hasBeenSent()) {
|
||
|
throw new CAS_OutOfSequenceException(
|
||
|
'Cannot access response, request not sent yet.'
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return $this->_responseBody;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Answer the cookies from the response. This may include cookies set during
|
||
|
* redirect responses.
|
||
|
*
|
||
|
* @return array An array containing cookies. E.g. array('name' => 'val');
|
||
|
*/
|
||
|
public function getCookies()
|
||
|
{
|
||
|
return $this->_cookieJar->getCookies($this->getServiceUrl());
|
||
|
}
|
||
|
|
||
|
}
|
||
|
?>
|