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.
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:
12 * http://www.apache.org/licenses/LICENSE-2.0
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.
22 * @file CAS/ProxiedService/Http/Abstract.php
23 * @category Authentication
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
31 * This class implements common methods for ProxiedService implementations included
34 * @class CAS_ProxiedService_Http_Abstract
35 * @category Authentication
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
41 abstract class CAS_ProxiedService_Http_Abstract
42 extends CAS_ProxiedService_Abstract
43 implements CAS_ProxiedService_Http
46 * The HTTP request mechanism talking to the target service.
48 * @var CAS_Request_RequestInterface $requestHandler
50 protected $requestHandler;
53 * The storage mechanism for cookies set by the target service.
55 * @var CAS_CookieJar $_cookieJar
62 * @param CAS_Request_RequestInterface $requestHandler request handler object
63 * @param CAS_CookieJar $cookieJar cookieJar object
67 public function __construct (CAS_Request_RequestInterface $requestHandler, CAS_CookieJar $cookieJar)
69 $this->requestHandler = $requestHandler;
70 $this->_cookieJar = $cookieJar;
74 * The target service url.
80 * Answer a service identifier (URL) for whom we should fetch a proxy ticket.
83 * @throws Exception If no service url is available.
85 public function getServiceUrl ()
87 if (empty($this->_url)) {
88 throw new CAS_ProxiedService_Exception('No URL set via '.get_class($this).'->setUrl($url).');
94 /*********************************************************
95 * Configure the Request
96 *********************************************************/
99 * Set the URL of the Request
101 * @param string $url url to set
104 * @throws CAS_OutOfSequenceException If called after the Request has been sent.
106 public function setUrl ($url)
108 if ($this->hasBeenSent()) {
109 throw new CAS_OutOfSequenceException('Cannot set the URL, request already sent.');
111 if (!is_string($url)) {
112 throw new CAS_InvalidArgumentException('$url must be a string.');
118 /*********************************************************
119 * 2. Send the Request
120 *********************************************************/
123 * Perform the request.
126 * @throws CAS_OutOfSequenceException If called multiple times.
127 * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
128 * The code of the Exception will be one of:
129 * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
130 * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
131 * PHPCAS_SERVICE_PT_FAILURE
132 * @throws CAS_ProxiedService_Exception If there is a failure sending the
133 * request to the target service.
135 public function send ()
137 if ($this->hasBeenSent()) {
138 throw new CAS_OutOfSequenceException('Cannot send, request already sent.');
141 phpCAS::traceBegin();
143 // Get our proxy ticket and append it to our URL.
144 $this->initializeProxyTicket();
145 $url = $this->getServiceUrl();
146 if (strstr($url, '?') === false) {
147 $url = $url.'?ticket='.$this->getProxyTicket();
149 $url = $url.'&ticket='.$this->getProxyTicket();
153 $this->makeRequest($url);
154 } catch (Exception $e) {
161 * Indicator of the number of requests (including redirects performed.
163 * @var int $_numRequests;
165 private $_numRequests = 0;
168 * The response headers.
170 * @var array $_responseHeaders;
172 private $_responseHeaders = array();
175 * The response status code.
177 * @var string $_responseStatusCode;
179 private $_responseStatusCode = '';
182 * The response headers.
184 * @var string $_responseBody;
186 private $_responseBody = '';
189 * Build and perform a request, following redirects
191 * @param string $url url for the request
194 * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
195 * The code of the Exception will be one of:
196 * PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
197 * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
198 * PHPCAS_SERVICE_PT_FAILURE
199 * @throws CAS_ProxiedService_Exception If there is a failure sending the
200 * request to the target service.
202 protected function makeRequest ($url)
204 // Verify that we are not in a redirect loop
205 $this->_numRequests++;
206 if ($this->_numRequests > 4) {
207 $message = 'Exceeded the maximum number of redirects (3) in proxied service request.';
208 phpCAS::trace($message);
209 throw new CAS_ProxiedService_Exception($message);
212 // Create a new request.
213 $request = clone $this->requestHandler;
214 $request->setUrl($url);
216 // Add any cookies to the request.
217 $request->addCookies($this->_cookieJar->getCookies($url));
219 // Add any other parts of the request needed by concrete classes
220 $this->populateRequest($request);
222 // Perform the request.
223 phpCAS::trace('Performing proxied service request to \''.$url.'\'');
224 if (!$request->send()) {
225 $message = 'Could not perform proxied service request to URL`'.$url.'\'. '.$request->getErrorMessage();
226 phpCAS::trace($message);
227 throw new CAS_ProxiedService_Exception($message);
230 // Store any cookies from the response;
231 $this->_cookieJar->storeCookies($url, $request->getResponseHeaders());
233 // Follow any redirects
234 if ($redirectUrl = $this->getRedirectUrl($request->getResponseHeaders())) {
235 phpCAS :: trace('Found redirect:'.$redirectUrl);
236 $this->makeRequest($redirectUrl);
239 $this->_responseHeaders = $request->getResponseHeaders();
240 $this->_responseBody = $request->getResponseBody();
241 $this->_responseStatusCode = $request->getResponseStatusCode();
246 * Add any other parts of the request needed by concrete classes
248 * @param CAS_Request_RequestInterface $request request interface object
252 abstract protected function populateRequest (CAS_Request_RequestInterface $request);
255 * Answer a redirect URL if a redirect header is found, otherwise null.
257 * @param array $responseHeaders response header to extract a redirect from
259 * @return string or null
261 protected function getRedirectUrl (array $responseHeaders)
263 // Check for the redirect after authentication
264 foreach ($responseHeaders as $header) {
265 if (preg_match('/^(Location:|URI:)\s*([^\s]+.*)$/', $header, $matches)) {
266 return trim(array_pop($matches));
272 /*********************************************************
273 * 3. Access the response
274 *********************************************************/
277 * Answer true if our request has been sent yet.
281 protected function hasBeenSent ()
283 return ($this->_numRequests > 0);
287 * Answer the headers of the response.
289 * @return array An array of header strings.
290 * @throws CAS_OutOfSequenceException If called before the Request has been sent.
292 public function getResponseHeaders ()
294 if (!$this->hasBeenSent()) {
295 throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
298 return $this->_responseHeaders;
302 * Answer HTTP status code of the response
305 * @throws CAS_OutOfSequenceException If called before the Request has been sent.
307 public function getResponseStatusCode ()
309 if (!$this->hasBeenSent()) {
310 throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
313 return $this->_responseStatusCode;
317 * Answer the body of response.
320 * @throws CAS_OutOfSequenceException If called before the Request has been sent.
322 public function getResponseBody ()
324 if (!$this->hasBeenSent()) {
325 throw new CAS_OutOfSequenceException('Cannot access response, request not sent yet.');
328 return $this->_responseBody;
332 * Answer the cookies from the response. This may include cookies set during
333 * redirect responses.
335 * @return array An array containing cookies. E.g. array('name' => 'val');
337 public function getCookies ()
339 return $this->_cookieJar->getCookies($this->getServiceUrl());