Abstract.php 10 KB

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