xref: /plugin/authssocas/vendor/apereo/phpcas/source/CAS/ProxiedService/Http/Abstract.php (revision d10b5556242e78d8a430c323b91984ec16415a46)
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