xref: /plugin/authssocas/vendor/apereo/phpcas/source/CAS/Client.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/Client.php
23*d10b5556SXylle * @category Authentication
24*d10b5556SXylle * @package  PhpCAS
25*d10b5556SXylle * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
26*d10b5556SXylle * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
27*d10b5556SXylle * @author   Brett Bieber <brett.bieber@gmail.com>
28*d10b5556SXylle * @author   Joachim Fritschi <jfritschi@freenet.de>
29*d10b5556SXylle * @author   Adam Franco <afranco@middlebury.edu>
30*d10b5556SXylle * @author   Tobias Schiebeck <tobias.schiebeck@manchester.ac.uk>
31*d10b5556SXylle * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
32*d10b5556SXylle * @link     https://wiki.jasig.org/display/CASC/phpCAS
33*d10b5556SXylle */
34*d10b5556SXylle
35*d10b5556SXylle/**
36*d10b5556SXylle * The CAS_Client class is a client interface that provides CAS authentication
37*d10b5556SXylle * to PHP applications.
38*d10b5556SXylle *
39*d10b5556SXylle * @class    CAS_Client
40*d10b5556SXylle * @category Authentication
41*d10b5556SXylle * @package  PhpCAS
42*d10b5556SXylle * @author   Pascal Aubry <pascal.aubry@univ-rennes1.fr>
43*d10b5556SXylle * @author   Olivier Berger <olivier.berger@it-sudparis.eu>
44*d10b5556SXylle * @author   Brett Bieber <brett.bieber@gmail.com>
45*d10b5556SXylle * @author   Joachim Fritschi <jfritschi@freenet.de>
46*d10b5556SXylle * @author   Adam Franco <afranco@middlebury.edu>
47*d10b5556SXylle * @author   Tobias Schiebeck <tobias.schiebeck@manchester.ac.uk>
48*d10b5556SXylle * @license  http://www.apache.org/licenses/LICENSE-2.0  Apache License 2.0
49*d10b5556SXylle * @link     https://wiki.jasig.org/display/CASC/phpCAS
50*d10b5556SXylle *
51*d10b5556SXylle */
52*d10b5556SXylle
53*d10b5556SXylleclass CAS_Client
54*d10b5556SXylle{
55*d10b5556SXylle
56*d10b5556SXylle    // ########################################################################
57*d10b5556SXylle    //  HTML OUTPUT
58*d10b5556SXylle    // ########################################################################
59*d10b5556SXylle    /**
60*d10b5556SXylle    * @addtogroup internalOutput
61*d10b5556SXylle    * @{
62*d10b5556SXylle    */
63*d10b5556SXylle
64*d10b5556SXylle    /**
65*d10b5556SXylle     * This method filters a string by replacing special tokens by appropriate values
66*d10b5556SXylle     * and prints it. The corresponding tokens are taken into account:
67*d10b5556SXylle     * - __CAS_VERSION__
68*d10b5556SXylle     * - __PHPCAS_VERSION__
69*d10b5556SXylle     * - __SERVER_BASE_URL__
70*d10b5556SXylle     *
71*d10b5556SXylle     * Used by CAS_Client::PrintHTMLHeader() and CAS_Client::printHTMLFooter().
72*d10b5556SXylle     *
73*d10b5556SXylle     * @param string $str the string to filter and output
74*d10b5556SXylle     *
75*d10b5556SXylle     * @return void
76*d10b5556SXylle     */
77*d10b5556SXylle    private function _htmlFilterOutput($str)
78*d10b5556SXylle    {
79*d10b5556SXylle        $str = str_replace('__CAS_VERSION__', $this->getServerVersion(), $str);
80*d10b5556SXylle        $str = str_replace('__PHPCAS_VERSION__', phpCAS::getVersion(), $str);
81*d10b5556SXylle        $str = str_replace('__SERVER_BASE_URL__', $this->_getServerBaseURL(), $str);
82*d10b5556SXylle        echo $str;
83*d10b5556SXylle    }
84*d10b5556SXylle
85*d10b5556SXylle    /**
86*d10b5556SXylle     * A string used to print the header of HTML pages. Written by
87*d10b5556SXylle     * CAS_Client::setHTMLHeader(), read by CAS_Client::printHTMLHeader().
88*d10b5556SXylle     *
89*d10b5556SXylle     * @hideinitializer
90*d10b5556SXylle     * @see CAS_Client::setHTMLHeader, CAS_Client::printHTMLHeader()
91*d10b5556SXylle     */
92*d10b5556SXylle    private $_output_header = '';
93*d10b5556SXylle
94*d10b5556SXylle    /**
95*d10b5556SXylle     * This method prints the header of the HTML output (after filtering). If
96*d10b5556SXylle     * CAS_Client::setHTMLHeader() was not used, a default header is output.
97*d10b5556SXylle     *
98*d10b5556SXylle     * @param string $title the title of the page
99*d10b5556SXylle     *
100*d10b5556SXylle     * @return void
101*d10b5556SXylle     * @see _htmlFilterOutput()
102*d10b5556SXylle     */
103*d10b5556SXylle    public function printHTMLHeader($title)
104*d10b5556SXylle    {
105*d10b5556SXylle        if (!phpCAS::getVerbose()) {
106*d10b5556SXylle            return;
107*d10b5556SXylle        }
108*d10b5556SXylle
109*d10b5556SXylle        $this->_htmlFilterOutput(
110*d10b5556SXylle            str_replace(
111*d10b5556SXylle                '__TITLE__', $title,
112*d10b5556SXylle                (empty($this->_output_header)
113*d10b5556SXylle                ? '<html><head><title>__TITLE__</title></head><body><h1>__TITLE__</h1>'
114*d10b5556SXylle                : $this->_output_header)
115*d10b5556SXylle            )
116*d10b5556SXylle        );
117*d10b5556SXylle    }
118*d10b5556SXylle
119*d10b5556SXylle    /**
120*d10b5556SXylle     * A string used to print the footer of HTML pages. Written by
121*d10b5556SXylle     * CAS_Client::setHTMLFooter(), read by printHTMLFooter().
122*d10b5556SXylle     *
123*d10b5556SXylle     * @hideinitializer
124*d10b5556SXylle     * @see CAS_Client::setHTMLFooter, CAS_Client::printHTMLFooter()
125*d10b5556SXylle     */
126*d10b5556SXylle    private $_output_footer = '';
127*d10b5556SXylle
128*d10b5556SXylle    /**
129*d10b5556SXylle     * This method prints the footer of the HTML output (after filtering). If
130*d10b5556SXylle     * CAS_Client::setHTMLFooter() was not used, a default footer is output.
131*d10b5556SXylle     *
132*d10b5556SXylle     * @return void
133*d10b5556SXylle     * @see _htmlFilterOutput()
134*d10b5556SXylle     */
135*d10b5556SXylle    public function printHTMLFooter()
136*d10b5556SXylle    {
137*d10b5556SXylle        if (!phpCAS::getVerbose()) {
138*d10b5556SXylle            return;
139*d10b5556SXylle        }
140*d10b5556SXylle
141*d10b5556SXylle        $lang = $this->getLangObj();
142*d10b5556SXylle        $message = empty($this->_output_footer)
143*d10b5556SXylle            ? '<hr><address>phpCAS __PHPCAS_VERSION__ ' . $lang->getUsingServer() .
144*d10b5556SXylle              ' <a href="__SERVER_BASE_URL__">__SERVER_BASE_URL__</a> (CAS __CAS_VERSION__)</a></address></body></html>'
145*d10b5556SXylle            : $this->_output_footer;
146*d10b5556SXylle
147*d10b5556SXylle        $this->_htmlFilterOutput($message);
148*d10b5556SXylle    }
149*d10b5556SXylle
150*d10b5556SXylle    /**
151*d10b5556SXylle     * This method set the HTML header used for all outputs.
152*d10b5556SXylle     *
153*d10b5556SXylle     * @param string $header the HTML header.
154*d10b5556SXylle     *
155*d10b5556SXylle     * @return void
156*d10b5556SXylle     */
157*d10b5556SXylle    public function setHTMLHeader($header)
158*d10b5556SXylle    {
159*d10b5556SXylle        // Argument Validation
160*d10b5556SXylle        if (gettype($header) != 'string')
161*d10b5556SXylle            throw new CAS_TypeMismatchException($header, '$header', 'string');
162*d10b5556SXylle
163*d10b5556SXylle        $this->_output_header = $header;
164*d10b5556SXylle    }
165*d10b5556SXylle
166*d10b5556SXylle    /**
167*d10b5556SXylle     * This method set the HTML footer used for all outputs.
168*d10b5556SXylle     *
169*d10b5556SXylle     * @param string $footer the HTML footer.
170*d10b5556SXylle     *
171*d10b5556SXylle     * @return void
172*d10b5556SXylle     */
173*d10b5556SXylle    public function setHTMLFooter($footer)
174*d10b5556SXylle    {
175*d10b5556SXylle        // Argument Validation
176*d10b5556SXylle        if (gettype($footer) != 'string')
177*d10b5556SXylle            throw new CAS_TypeMismatchException($footer, '$footer', 'string');
178*d10b5556SXylle
179*d10b5556SXylle        $this->_output_footer = $footer;
180*d10b5556SXylle    }
181*d10b5556SXylle
182*d10b5556SXylle    /**
183*d10b5556SXylle     * Simple wrapper for printf function, that respects
184*d10b5556SXylle     * phpCAS verbosity setting.
185*d10b5556SXylle     *
186*d10b5556SXylle     * @param string $format
187*d10b5556SXylle     * @param string|int|float ...$values
188*d10b5556SXylle     *
189*d10b5556SXylle     * @see printf()
190*d10b5556SXylle     */
191*d10b5556SXylle    private function printf(string $format, ...$values): void
192*d10b5556SXylle    {
193*d10b5556SXylle        if (phpCAS::getVerbose()) {
194*d10b5556SXylle            printf($format, ...$values);
195*d10b5556SXylle        }
196*d10b5556SXylle    }
197*d10b5556SXylle
198*d10b5556SXylle    /** @} */
199*d10b5556SXylle
200*d10b5556SXylle
201*d10b5556SXylle    // ########################################################################
202*d10b5556SXylle    //  INTERNATIONALIZATION
203*d10b5556SXylle    // ########################################################################
204*d10b5556SXylle    /**
205*d10b5556SXylle    * @addtogroup internalLang
206*d10b5556SXylle    * @{
207*d10b5556SXylle    */
208*d10b5556SXylle    /**
209*d10b5556SXylle     * A string corresponding to the language used by phpCAS. Written by
210*d10b5556SXylle     * CAS_Client::setLang(), read by CAS_Client::getLang().
211*d10b5556SXylle
212*d10b5556SXylle     * @note debugging information is always in english (debug purposes only).
213*d10b5556SXylle     */
214*d10b5556SXylle    private $_lang = PHPCAS_LANG_DEFAULT;
215*d10b5556SXylle
216*d10b5556SXylle    /**
217*d10b5556SXylle     * This method is used to set the language used by phpCAS.
218*d10b5556SXylle     *
219*d10b5556SXylle     * @param string $lang representing the language.
220*d10b5556SXylle     *
221*d10b5556SXylle     * @return void
222*d10b5556SXylle     */
223*d10b5556SXylle    public function setLang($lang)
224*d10b5556SXylle    {
225*d10b5556SXylle        // Argument Validation
226*d10b5556SXylle        if (gettype($lang) != 'string')
227*d10b5556SXylle            throw new CAS_TypeMismatchException($lang, '$lang', 'string');
228*d10b5556SXylle
229*d10b5556SXylle        phpCAS::traceBegin();
230*d10b5556SXylle        $obj = new $lang();
231*d10b5556SXylle        if (!($obj instanceof CAS_Languages_LanguageInterface)) {
232*d10b5556SXylle            throw new CAS_InvalidArgumentException(
233*d10b5556SXylle                '$className must implement the CAS_Languages_LanguageInterface'
234*d10b5556SXylle            );
235*d10b5556SXylle        }
236*d10b5556SXylle        $this->_lang = $lang;
237*d10b5556SXylle        phpCAS::traceEnd();
238*d10b5556SXylle    }
239*d10b5556SXylle    /**
240*d10b5556SXylle     * Create the language
241*d10b5556SXylle     *
242*d10b5556SXylle     * @return CAS_Languages_LanguageInterface object implementing the class
243*d10b5556SXylle     */
244*d10b5556SXylle    public function getLangObj()
245*d10b5556SXylle    {
246*d10b5556SXylle        $classname = $this->_lang;
247*d10b5556SXylle        return new $classname();
248*d10b5556SXylle    }
249*d10b5556SXylle
250*d10b5556SXylle    /** @} */
251*d10b5556SXylle    // ########################################################################
252*d10b5556SXylle    //  CAS SERVER CONFIG
253*d10b5556SXylle    // ########################################################################
254*d10b5556SXylle    /**
255*d10b5556SXylle    * @addtogroup internalConfig
256*d10b5556SXylle    * @{
257*d10b5556SXylle    */
258*d10b5556SXylle
259*d10b5556SXylle    /**
260*d10b5556SXylle     * a record to store information about the CAS server.
261*d10b5556SXylle     * - $_server['version']: the version of the CAS server
262*d10b5556SXylle     * - $_server['hostname']: the hostname of the CAS server
263*d10b5556SXylle     * - $_server['port']: the port the CAS server is running on
264*d10b5556SXylle     * - $_server['uri']: the base URI the CAS server is responding on
265*d10b5556SXylle     * - $_server['base_url']: the base URL of the CAS server
266*d10b5556SXylle     * - $_server['login_url']: the login URL of the CAS server
267*d10b5556SXylle     * - $_server['service_validate_url']: the service validating URL of the
268*d10b5556SXylle     *   CAS server
269*d10b5556SXylle     * - $_server['proxy_url']: the proxy URL of the CAS server
270*d10b5556SXylle     * - $_server['proxy_validate_url']: the proxy validating URL of the CAS server
271*d10b5556SXylle     * - $_server['logout_url']: the logout URL of the CAS server
272*d10b5556SXylle     *
273*d10b5556SXylle     * $_server['version'], $_server['hostname'], $_server['port'] and
274*d10b5556SXylle     * $_server['uri'] are written by CAS_Client::CAS_Client(), read by
275*d10b5556SXylle     * CAS_Client::getServerVersion(), CAS_Client::_getServerHostname(),
276*d10b5556SXylle     * CAS_Client::_getServerPort() and CAS_Client::_getServerURI().
277*d10b5556SXylle     *
278*d10b5556SXylle     * The other fields are written and read by CAS_Client::_getServerBaseURL(),
279*d10b5556SXylle     * CAS_Client::getServerLoginURL(), CAS_Client::getServerServiceValidateURL(),
280*d10b5556SXylle     * CAS_Client::getServerProxyValidateURL() and CAS_Client::getServerLogoutURL().
281*d10b5556SXylle     *
282*d10b5556SXylle     * @hideinitializer
283*d10b5556SXylle     */
284*d10b5556SXylle    private $_server = array(
285*d10b5556SXylle        'version' => '',
286*d10b5556SXylle        'hostname' => 'none',
287*d10b5556SXylle        'port' => -1,
288*d10b5556SXylle        'uri' => 'none');
289*d10b5556SXylle
290*d10b5556SXylle    /**
291*d10b5556SXylle     * This method is used to retrieve the version of the CAS server.
292*d10b5556SXylle     *
293*d10b5556SXylle     * @return string the version of the CAS server.
294*d10b5556SXylle     */
295*d10b5556SXylle    public function getServerVersion()
296*d10b5556SXylle    {
297*d10b5556SXylle        return $this->_server['version'];
298*d10b5556SXylle    }
299*d10b5556SXylle
300*d10b5556SXylle    /**
301*d10b5556SXylle     * This method is used to retrieve the hostname of the CAS server.
302*d10b5556SXylle     *
303*d10b5556SXylle     * @return string the hostname of the CAS server.
304*d10b5556SXylle     */
305*d10b5556SXylle    private function _getServerHostname()
306*d10b5556SXylle    {
307*d10b5556SXylle        return $this->_server['hostname'];
308*d10b5556SXylle    }
309*d10b5556SXylle
310*d10b5556SXylle    /**
311*d10b5556SXylle     * This method is used to retrieve the port of the CAS server.
312*d10b5556SXylle     *
313*d10b5556SXylle     * @return int the port of the CAS server.
314*d10b5556SXylle     */
315*d10b5556SXylle    private function _getServerPort()
316*d10b5556SXylle    {
317*d10b5556SXylle        return $this->_server['port'];
318*d10b5556SXylle    }
319*d10b5556SXylle
320*d10b5556SXylle    /**
321*d10b5556SXylle     * This method is used to retrieve the URI of the CAS server.
322*d10b5556SXylle     *
323*d10b5556SXylle     * @return string a URI.
324*d10b5556SXylle     */
325*d10b5556SXylle    private function _getServerURI()
326*d10b5556SXylle    {
327*d10b5556SXylle        return $this->_server['uri'];
328*d10b5556SXylle    }
329*d10b5556SXylle
330*d10b5556SXylle    /**
331*d10b5556SXylle     * This method is used to retrieve the base URL of the CAS server.
332*d10b5556SXylle     *
333*d10b5556SXylle     * @return string a URL.
334*d10b5556SXylle     */
335*d10b5556SXylle    private function _getServerBaseURL()
336*d10b5556SXylle    {
337*d10b5556SXylle        // the URL is build only when needed
338*d10b5556SXylle        if ( empty($this->_server['base_url']) ) {
339*d10b5556SXylle            $this->_server['base_url'] = 'https://' . $this->_getServerHostname();
340*d10b5556SXylle            if ($this->_getServerPort()!=443) {
341*d10b5556SXylle                $this->_server['base_url'] .= ':'
342*d10b5556SXylle                .$this->_getServerPort();
343*d10b5556SXylle            }
344*d10b5556SXylle            $this->_server['base_url'] .= $this->_getServerURI();
345*d10b5556SXylle        }
346*d10b5556SXylle        return $this->_server['base_url'];
347*d10b5556SXylle    }
348*d10b5556SXylle
349*d10b5556SXylle    /**
350*d10b5556SXylle     * This method is used to retrieve the login URL of the CAS server.
351*d10b5556SXylle     *
352*d10b5556SXylle     * @param bool $gateway true to check authentication, false to force it
353*d10b5556SXylle     * @param bool $renew   true to force the authentication with the CAS server
354*d10b5556SXylle     *
355*d10b5556SXylle     * @return string a URL.
356*d10b5556SXylle     * @note It is recommended that CAS implementations ignore the "gateway"
357*d10b5556SXylle     * parameter if "renew" is set
358*d10b5556SXylle     */
359*d10b5556SXylle    public function getServerLoginURL($gateway=false,$renew=false)
360*d10b5556SXylle    {
361*d10b5556SXylle        phpCAS::traceBegin();
362*d10b5556SXylle        // the URL is build only when needed
363*d10b5556SXylle        if ( empty($this->_server['login_url']) ) {
364*d10b5556SXylle            $this->_server['login_url'] = $this->_buildQueryUrl($this->_getServerBaseURL().'login','service='.urlencode($this->getURL()));
365*d10b5556SXylle        }
366*d10b5556SXylle        $url = $this->_server['login_url'];
367*d10b5556SXylle        if ($renew) {
368*d10b5556SXylle            // It is recommended that when the "renew" parameter is set, its
369*d10b5556SXylle            // value be "true"
370*d10b5556SXylle            $url = $this->_buildQueryUrl($url, 'renew=true');
371*d10b5556SXylle        } elseif ($gateway) {
372*d10b5556SXylle            // It is recommended that when the "gateway" parameter is set, its
373*d10b5556SXylle            // value be "true"
374*d10b5556SXylle            $url = $this->_buildQueryUrl($url, 'gateway=true');
375*d10b5556SXylle        }
376*d10b5556SXylle        phpCAS::traceEnd($url);
377*d10b5556SXylle        return $url;
378*d10b5556SXylle    }
379*d10b5556SXylle
380*d10b5556SXylle    /**
381*d10b5556SXylle     * This method sets the login URL of the CAS server.
382*d10b5556SXylle     *
383*d10b5556SXylle     * @param string $url the login URL
384*d10b5556SXylle     *
385*d10b5556SXylle     * @return string login url
386*d10b5556SXylle     */
387*d10b5556SXylle    public function setServerLoginURL($url)
388*d10b5556SXylle    {
389*d10b5556SXylle        // Argument Validation
390*d10b5556SXylle        if (gettype($url) != 'string')
391*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
392*d10b5556SXylle
393*d10b5556SXylle        return $this->_server['login_url'] = $url;
394*d10b5556SXylle    }
395*d10b5556SXylle
396*d10b5556SXylle
397*d10b5556SXylle    /**
398*d10b5556SXylle     * This method sets the serviceValidate URL of the CAS server.
399*d10b5556SXylle     *
400*d10b5556SXylle     * @param string $url the serviceValidate URL
401*d10b5556SXylle     *
402*d10b5556SXylle     * @return string serviceValidate URL
403*d10b5556SXylle     */
404*d10b5556SXylle    public function setServerServiceValidateURL($url)
405*d10b5556SXylle    {
406*d10b5556SXylle        // Argument Validation
407*d10b5556SXylle        if (gettype($url) != 'string')
408*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
409*d10b5556SXylle
410*d10b5556SXylle        return $this->_server['service_validate_url'] = $url;
411*d10b5556SXylle    }
412*d10b5556SXylle
413*d10b5556SXylle
414*d10b5556SXylle    /**
415*d10b5556SXylle     * This method sets the proxyValidate URL of the CAS server.
416*d10b5556SXylle     *
417*d10b5556SXylle     * @param string $url the proxyValidate URL
418*d10b5556SXylle     *
419*d10b5556SXylle     * @return string proxyValidate URL
420*d10b5556SXylle     */
421*d10b5556SXylle    public function setServerProxyValidateURL($url)
422*d10b5556SXylle    {
423*d10b5556SXylle        // Argument Validation
424*d10b5556SXylle        if (gettype($url) != 'string')
425*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
426*d10b5556SXylle
427*d10b5556SXylle        return $this->_server['proxy_validate_url'] = $url;
428*d10b5556SXylle    }
429*d10b5556SXylle
430*d10b5556SXylle
431*d10b5556SXylle    /**
432*d10b5556SXylle     * This method sets the samlValidate URL of the CAS server.
433*d10b5556SXylle     *
434*d10b5556SXylle     * @param string $url the samlValidate URL
435*d10b5556SXylle     *
436*d10b5556SXylle     * @return string samlValidate URL
437*d10b5556SXylle     */
438*d10b5556SXylle    public function setServerSamlValidateURL($url)
439*d10b5556SXylle    {
440*d10b5556SXylle        // Argument Validation
441*d10b5556SXylle        if (gettype($url) != 'string')
442*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
443*d10b5556SXylle
444*d10b5556SXylle        return $this->_server['saml_validate_url'] = $url;
445*d10b5556SXylle    }
446*d10b5556SXylle
447*d10b5556SXylle
448*d10b5556SXylle    /**
449*d10b5556SXylle     * This method is used to retrieve the service validating URL of the CAS server.
450*d10b5556SXylle     *
451*d10b5556SXylle     * @return string serviceValidate URL.
452*d10b5556SXylle     */
453*d10b5556SXylle    public function getServerServiceValidateURL()
454*d10b5556SXylle    {
455*d10b5556SXylle        phpCAS::traceBegin();
456*d10b5556SXylle        // the URL is build only when needed
457*d10b5556SXylle        if ( empty($this->_server['service_validate_url']) ) {
458*d10b5556SXylle            switch ($this->getServerVersion()) {
459*d10b5556SXylle            case CAS_VERSION_1_0:
460*d10b5556SXylle                $this->_server['service_validate_url'] = $this->_getServerBaseURL()
461*d10b5556SXylle                .'validate';
462*d10b5556SXylle                break;
463*d10b5556SXylle            case CAS_VERSION_2_0:
464*d10b5556SXylle                $this->_server['service_validate_url'] = $this->_getServerBaseURL()
465*d10b5556SXylle                .'serviceValidate';
466*d10b5556SXylle                break;
467*d10b5556SXylle            case CAS_VERSION_3_0:
468*d10b5556SXylle                $this->_server['service_validate_url'] = $this->_getServerBaseURL()
469*d10b5556SXylle                .'p3/serviceValidate';
470*d10b5556SXylle                break;
471*d10b5556SXylle            }
472*d10b5556SXylle        }
473*d10b5556SXylle        $url = $this->_buildQueryUrl(
474*d10b5556SXylle            $this->_server['service_validate_url'],
475*d10b5556SXylle            'service='.urlencode($this->getURL())
476*d10b5556SXylle        );
477*d10b5556SXylle        phpCAS::traceEnd($url);
478*d10b5556SXylle        return $url;
479*d10b5556SXylle    }
480*d10b5556SXylle    /**
481*d10b5556SXylle     * This method is used to retrieve the SAML validating URL of the CAS server.
482*d10b5556SXylle     *
483*d10b5556SXylle     * @return string samlValidate URL.
484*d10b5556SXylle     */
485*d10b5556SXylle    public function getServerSamlValidateURL()
486*d10b5556SXylle    {
487*d10b5556SXylle        phpCAS::traceBegin();
488*d10b5556SXylle        // the URL is build only when needed
489*d10b5556SXylle        if ( empty($this->_server['saml_validate_url']) ) {
490*d10b5556SXylle            switch ($this->getServerVersion()) {
491*d10b5556SXylle            case SAML_VERSION_1_1:
492*d10b5556SXylle                $this->_server['saml_validate_url'] = $this->_getServerBaseURL().'samlValidate';
493*d10b5556SXylle                break;
494*d10b5556SXylle            }
495*d10b5556SXylle        }
496*d10b5556SXylle
497*d10b5556SXylle        $url = $this->_buildQueryUrl(
498*d10b5556SXylle            $this->_server['saml_validate_url'],
499*d10b5556SXylle            'TARGET='.urlencode($this->getURL())
500*d10b5556SXylle        );
501*d10b5556SXylle        phpCAS::traceEnd($url);
502*d10b5556SXylle        return $url;
503*d10b5556SXylle    }
504*d10b5556SXylle
505*d10b5556SXylle    /**
506*d10b5556SXylle     * This method is used to retrieve the proxy validating URL of the CAS server.
507*d10b5556SXylle     *
508*d10b5556SXylle     * @return string proxyValidate URL.
509*d10b5556SXylle     */
510*d10b5556SXylle    public function getServerProxyValidateURL()
511*d10b5556SXylle    {
512*d10b5556SXylle        phpCAS::traceBegin();
513*d10b5556SXylle        // the URL is build only when needed
514*d10b5556SXylle        if ( empty($this->_server['proxy_validate_url']) ) {
515*d10b5556SXylle            switch ($this->getServerVersion()) {
516*d10b5556SXylle            case CAS_VERSION_1_0:
517*d10b5556SXylle                $this->_server['proxy_validate_url'] = '';
518*d10b5556SXylle                break;
519*d10b5556SXylle            case CAS_VERSION_2_0:
520*d10b5556SXylle                $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'proxyValidate';
521*d10b5556SXylle                break;
522*d10b5556SXylle            case CAS_VERSION_3_0:
523*d10b5556SXylle                $this->_server['proxy_validate_url'] = $this->_getServerBaseURL().'p3/proxyValidate';
524*d10b5556SXylle                break;
525*d10b5556SXylle            }
526*d10b5556SXylle        }
527*d10b5556SXylle        $url = $this->_buildQueryUrl(
528*d10b5556SXylle            $this->_server['proxy_validate_url'],
529*d10b5556SXylle            'service='.urlencode($this->getURL())
530*d10b5556SXylle        );
531*d10b5556SXylle        phpCAS::traceEnd($url);
532*d10b5556SXylle        return $url;
533*d10b5556SXylle    }
534*d10b5556SXylle
535*d10b5556SXylle
536*d10b5556SXylle    /**
537*d10b5556SXylle     * This method is used to retrieve the proxy URL of the CAS server.
538*d10b5556SXylle     *
539*d10b5556SXylle     * @return  string proxy URL.
540*d10b5556SXylle     */
541*d10b5556SXylle    public function getServerProxyURL()
542*d10b5556SXylle    {
543*d10b5556SXylle        // the URL is build only when needed
544*d10b5556SXylle        if ( empty($this->_server['proxy_url']) ) {
545*d10b5556SXylle            switch ($this->getServerVersion()) {
546*d10b5556SXylle            case CAS_VERSION_1_0:
547*d10b5556SXylle                $this->_server['proxy_url'] = '';
548*d10b5556SXylle                break;
549*d10b5556SXylle            case CAS_VERSION_2_0:
550*d10b5556SXylle            case CAS_VERSION_3_0:
551*d10b5556SXylle                $this->_server['proxy_url'] = $this->_getServerBaseURL().'proxy';
552*d10b5556SXylle                break;
553*d10b5556SXylle            }
554*d10b5556SXylle        }
555*d10b5556SXylle        return $this->_server['proxy_url'];
556*d10b5556SXylle    }
557*d10b5556SXylle
558*d10b5556SXylle    /**
559*d10b5556SXylle     * This method is used to retrieve the logout URL of the CAS server.
560*d10b5556SXylle     *
561*d10b5556SXylle     * @return string logout URL.
562*d10b5556SXylle     */
563*d10b5556SXylle    public function getServerLogoutURL()
564*d10b5556SXylle    {
565*d10b5556SXylle        // the URL is build only when needed
566*d10b5556SXylle        if ( empty($this->_server['logout_url']) ) {
567*d10b5556SXylle            $this->_server['logout_url'] = $this->_getServerBaseURL().'logout';
568*d10b5556SXylle        }
569*d10b5556SXylle        return $this->_server['logout_url'];
570*d10b5556SXylle    }
571*d10b5556SXylle
572*d10b5556SXylle    /**
573*d10b5556SXylle     * This method sets the logout URL of the CAS server.
574*d10b5556SXylle     *
575*d10b5556SXylle     * @param string $url the logout URL
576*d10b5556SXylle     *
577*d10b5556SXylle     * @return string logout url
578*d10b5556SXylle     */
579*d10b5556SXylle    public function setServerLogoutURL($url)
580*d10b5556SXylle    {
581*d10b5556SXylle        // Argument Validation
582*d10b5556SXylle        if (gettype($url) != 'string')
583*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
584*d10b5556SXylle
585*d10b5556SXylle        return $this->_server['logout_url'] = $url;
586*d10b5556SXylle    }
587*d10b5556SXylle
588*d10b5556SXylle    /**
589*d10b5556SXylle     * An array to store extra curl options.
590*d10b5556SXylle     */
591*d10b5556SXylle    private $_curl_options = array();
592*d10b5556SXylle
593*d10b5556SXylle    /**
594*d10b5556SXylle     * This method is used to set additional user curl options.
595*d10b5556SXylle     *
596*d10b5556SXylle     * @param string $key   name of the curl option
597*d10b5556SXylle     * @param string $value value of the curl option
598*d10b5556SXylle     *
599*d10b5556SXylle     * @return void
600*d10b5556SXylle     */
601*d10b5556SXylle    public function setExtraCurlOption($key, $value)
602*d10b5556SXylle    {
603*d10b5556SXylle        $this->_curl_options[$key] = $value;
604*d10b5556SXylle    }
605*d10b5556SXylle
606*d10b5556SXylle    /** @} */
607*d10b5556SXylle
608*d10b5556SXylle    // ########################################################################
609*d10b5556SXylle    //  Change the internal behaviour of phpcas
610*d10b5556SXylle    // ########################################################################
611*d10b5556SXylle
612*d10b5556SXylle    /**
613*d10b5556SXylle     * @addtogroup internalBehave
614*d10b5556SXylle     * @{
615*d10b5556SXylle     */
616*d10b5556SXylle
617*d10b5556SXylle    /**
618*d10b5556SXylle     * The class to instantiate for making web requests in readUrl().
619*d10b5556SXylle     * The class specified must implement the CAS_Request_RequestInterface.
620*d10b5556SXylle     * By default CAS_Request_CurlRequest is used, but this may be overridden to
621*d10b5556SXylle     * supply alternate request mechanisms for testing.
622*d10b5556SXylle     */
623*d10b5556SXylle    private $_requestImplementation = 'CAS_Request_CurlRequest';
624*d10b5556SXylle
625*d10b5556SXylle    /**
626*d10b5556SXylle     * Override the default implementation used to make web requests in readUrl().
627*d10b5556SXylle     * This class must implement the CAS_Request_RequestInterface.
628*d10b5556SXylle     *
629*d10b5556SXylle     * @param string $className name of the RequestImplementation class
630*d10b5556SXylle     *
631*d10b5556SXylle     * @return void
632*d10b5556SXylle     */
633*d10b5556SXylle    public function setRequestImplementation ($className)
634*d10b5556SXylle    {
635*d10b5556SXylle        $obj = new $className;
636*d10b5556SXylle        if (!($obj instanceof CAS_Request_RequestInterface)) {
637*d10b5556SXylle            throw new CAS_InvalidArgumentException(
638*d10b5556SXylle                '$className must implement the CAS_Request_RequestInterface'
639*d10b5556SXylle            );
640*d10b5556SXylle        }
641*d10b5556SXylle        $this->_requestImplementation = $className;
642*d10b5556SXylle    }
643*d10b5556SXylle
644*d10b5556SXylle    /**
645*d10b5556SXylle     * @var boolean $_clearTicketsFromUrl; If true, phpCAS will clear session
646*d10b5556SXylle     * tickets from the URL after a successful authentication.
647*d10b5556SXylle     */
648*d10b5556SXylle    private $_clearTicketsFromUrl = true;
649*d10b5556SXylle
650*d10b5556SXylle    /**
651*d10b5556SXylle     * Configure the client to not send redirect headers and call exit() on
652*d10b5556SXylle     * authentication success. The normal redirect is used to remove the service
653*d10b5556SXylle     * ticket from the client's URL, but for running unit tests we need to
654*d10b5556SXylle     * continue without exiting.
655*d10b5556SXylle     *
656*d10b5556SXylle     * Needed for testing authentication
657*d10b5556SXylle     *
658*d10b5556SXylle     * @return void
659*d10b5556SXylle     */
660*d10b5556SXylle    public function setNoClearTicketsFromUrl ()
661*d10b5556SXylle    {
662*d10b5556SXylle        $this->_clearTicketsFromUrl = false;
663*d10b5556SXylle    }
664*d10b5556SXylle
665*d10b5556SXylle    /**
666*d10b5556SXylle     * @var callback $_attributeParserCallbackFunction;
667*d10b5556SXylle     */
668*d10b5556SXylle    private $_casAttributeParserCallbackFunction = null;
669*d10b5556SXylle
670*d10b5556SXylle    /**
671*d10b5556SXylle     * @var array $_attributeParserCallbackArgs;
672*d10b5556SXylle     */
673*d10b5556SXylle    private $_casAttributeParserCallbackArgs = array();
674*d10b5556SXylle
675*d10b5556SXylle    /**
676*d10b5556SXylle     * Set a callback function to be run when parsing CAS attributes
677*d10b5556SXylle     *
678*d10b5556SXylle     * The callback function will be passed a XMLNode as its first parameter,
679*d10b5556SXylle     * followed by any $additionalArgs you pass.
680*d10b5556SXylle     *
681*d10b5556SXylle     * @param string $function       callback function to call
682*d10b5556SXylle     * @param array  $additionalArgs optional array of arguments
683*d10b5556SXylle     *
684*d10b5556SXylle     * @return void
685*d10b5556SXylle     */
686*d10b5556SXylle    public function setCasAttributeParserCallback($function, array $additionalArgs = array())
687*d10b5556SXylle    {
688*d10b5556SXylle        $this->_casAttributeParserCallbackFunction = $function;
689*d10b5556SXylle        $this->_casAttributeParserCallbackArgs = $additionalArgs;
690*d10b5556SXylle    }
691*d10b5556SXylle
692*d10b5556SXylle    /** @var callable $_postAuthenticateCallbackFunction;
693*d10b5556SXylle     */
694*d10b5556SXylle    private $_postAuthenticateCallbackFunction = null;
695*d10b5556SXylle
696*d10b5556SXylle    /**
697*d10b5556SXylle     * @var array $_postAuthenticateCallbackArgs;
698*d10b5556SXylle     */
699*d10b5556SXylle    private $_postAuthenticateCallbackArgs = array();
700*d10b5556SXylle
701*d10b5556SXylle    /**
702*d10b5556SXylle     * Set a callback function to be run when a user authenticates.
703*d10b5556SXylle     *
704*d10b5556SXylle     * The callback function will be passed a $logoutTicket as its first parameter,
705*d10b5556SXylle     * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
706*d10b5556SXylle     * opaque string that can be used to map a session-id to the logout request
707*d10b5556SXylle     * in order to support single-signout in applications that manage their own
708*d10b5556SXylle     * sessions (rather than letting phpCAS start the session).
709*d10b5556SXylle     *
710*d10b5556SXylle     * phpCAS::forceAuthentication() will always exit and forward client unless
711*d10b5556SXylle     * they are already authenticated. To perform an action at the moment the user
712*d10b5556SXylle     * logs in (such as registering an account, performing logging, etc), register
713*d10b5556SXylle     * a callback function here.
714*d10b5556SXylle     *
715*d10b5556SXylle     * @param callable $function       callback function to call
716*d10b5556SXylle     * @param array  $additionalArgs optional array of arguments
717*d10b5556SXylle     *
718*d10b5556SXylle     * @return void
719*d10b5556SXylle     */
720*d10b5556SXylle    public function setPostAuthenticateCallback ($function, array $additionalArgs = array())
721*d10b5556SXylle    {
722*d10b5556SXylle        $this->_postAuthenticateCallbackFunction = $function;
723*d10b5556SXylle        $this->_postAuthenticateCallbackArgs = $additionalArgs;
724*d10b5556SXylle    }
725*d10b5556SXylle
726*d10b5556SXylle    /**
727*d10b5556SXylle     * @var callable $_signoutCallbackFunction;
728*d10b5556SXylle     */
729*d10b5556SXylle    private $_signoutCallbackFunction = null;
730*d10b5556SXylle
731*d10b5556SXylle    /**
732*d10b5556SXylle     * @var array $_signoutCallbackArgs;
733*d10b5556SXylle     */
734*d10b5556SXylle    private $_signoutCallbackArgs = array();
735*d10b5556SXylle
736*d10b5556SXylle    /**
737*d10b5556SXylle     * Set a callback function to be run when a single-signout request is received.
738*d10b5556SXylle     *
739*d10b5556SXylle     * The callback function will be passed a $logoutTicket as its first parameter,
740*d10b5556SXylle     * followed by any $additionalArgs you pass. The $logoutTicket parameter is an
741*d10b5556SXylle     * opaque string that can be used to map a session-id to the logout request in
742*d10b5556SXylle     * order to support single-signout in applications that manage their own sessions
743*d10b5556SXylle     * (rather than letting phpCAS start and destroy the session).
744*d10b5556SXylle     *
745*d10b5556SXylle     * @param callable $function       callback function to call
746*d10b5556SXylle     * @param array  $additionalArgs optional array of arguments
747*d10b5556SXylle     *
748*d10b5556SXylle     * @return void
749*d10b5556SXylle     */
750*d10b5556SXylle    public function setSingleSignoutCallback ($function, array $additionalArgs = array())
751*d10b5556SXylle    {
752*d10b5556SXylle        $this->_signoutCallbackFunction = $function;
753*d10b5556SXylle        $this->_signoutCallbackArgs = $additionalArgs;
754*d10b5556SXylle    }
755*d10b5556SXylle
756*d10b5556SXylle    // ########################################################################
757*d10b5556SXylle    //  Methods for supplying code-flow feedback to integrators.
758*d10b5556SXylle    // ########################################################################
759*d10b5556SXylle
760*d10b5556SXylle    /**
761*d10b5556SXylle     * Ensure that this is actually a proxy object or fail with an exception
762*d10b5556SXylle     *
763*d10b5556SXylle     * @throws CAS_OutOfSequenceBeforeProxyException
764*d10b5556SXylle     *
765*d10b5556SXylle     * @return void
766*d10b5556SXylle     */
767*d10b5556SXylle    public function ensureIsProxy()
768*d10b5556SXylle    {
769*d10b5556SXylle        if (!$this->isProxy()) {
770*d10b5556SXylle            throw new CAS_OutOfSequenceBeforeProxyException();
771*d10b5556SXylle        }
772*d10b5556SXylle    }
773*d10b5556SXylle
774*d10b5556SXylle    /**
775*d10b5556SXylle     * Mark the caller of authentication. This will help client integraters determine
776*d10b5556SXylle     * problems with their code flow if they call a function such as getUser() before
777*d10b5556SXylle     * authentication has occurred.
778*d10b5556SXylle     *
779*d10b5556SXylle     * @param bool $auth True if authentication was successful, false otherwise.
780*d10b5556SXylle     *
781*d10b5556SXylle     * @return null
782*d10b5556SXylle     */
783*d10b5556SXylle    public function markAuthenticationCall ($auth)
784*d10b5556SXylle    {
785*d10b5556SXylle        // store where the authentication has been checked and the result
786*d10b5556SXylle        $dbg = debug_backtrace();
787*d10b5556SXylle        $this->_authentication_caller = array (
788*d10b5556SXylle            'file' => $dbg[1]['file'],
789*d10b5556SXylle            'line' => $dbg[1]['line'],
790*d10b5556SXylle            'method' => $dbg[1]['class'] . '::' . $dbg[1]['function'],
791*d10b5556SXylle            'result' => (boolean)$auth
792*d10b5556SXylle        );
793*d10b5556SXylle    }
794*d10b5556SXylle    private $_authentication_caller;
795*d10b5556SXylle
796*d10b5556SXylle    /**
797*d10b5556SXylle     * Answer true if authentication has been checked.
798*d10b5556SXylle     *
799*d10b5556SXylle     * @return bool
800*d10b5556SXylle     */
801*d10b5556SXylle    public function wasAuthenticationCalled ()
802*d10b5556SXylle    {
803*d10b5556SXylle        return !empty($this->_authentication_caller);
804*d10b5556SXylle    }
805*d10b5556SXylle
806*d10b5556SXylle    /**
807*d10b5556SXylle     * Ensure that authentication was checked. Terminate with exception if no
808*d10b5556SXylle     * authentication was performed
809*d10b5556SXylle     *
810*d10b5556SXylle     * @throws CAS_OutOfSequenceBeforeAuthenticationCallException
811*d10b5556SXylle     *
812*d10b5556SXylle     * @return void
813*d10b5556SXylle     */
814*d10b5556SXylle    private function _ensureAuthenticationCalled()
815*d10b5556SXylle    {
816*d10b5556SXylle        if (!$this->wasAuthenticationCalled()) {
817*d10b5556SXylle            throw new CAS_OutOfSequenceBeforeAuthenticationCallException();
818*d10b5556SXylle        }
819*d10b5556SXylle    }
820*d10b5556SXylle
821*d10b5556SXylle    /**
822*d10b5556SXylle     * Answer the result of the authentication call.
823*d10b5556SXylle     *
824*d10b5556SXylle     * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
825*d10b5556SXylle     * and markAuthenticationCall() didn't happen.
826*d10b5556SXylle     *
827*d10b5556SXylle     * @return bool
828*d10b5556SXylle     */
829*d10b5556SXylle    public function wasAuthenticationCallSuccessful ()
830*d10b5556SXylle    {
831*d10b5556SXylle        $this->_ensureAuthenticationCalled();
832*d10b5556SXylle        return $this->_authentication_caller['result'];
833*d10b5556SXylle    }
834*d10b5556SXylle
835*d10b5556SXylle
836*d10b5556SXylle    /**
837*d10b5556SXylle     * Ensure that authentication was checked. Terminate with exception if no
838*d10b5556SXylle     * authentication was performed
839*d10b5556SXylle     *
840*d10b5556SXylle     * @throws CAS_OutOfSequenceException
841*d10b5556SXylle     *
842*d10b5556SXylle     * @return void
843*d10b5556SXylle     */
844*d10b5556SXylle    public function ensureAuthenticationCallSuccessful()
845*d10b5556SXylle    {
846*d10b5556SXylle        $this->_ensureAuthenticationCalled();
847*d10b5556SXylle        if (!$this->_authentication_caller['result']) {
848*d10b5556SXylle            throw new CAS_OutOfSequenceException(
849*d10b5556SXylle                'authentication was checked (by '
850*d10b5556SXylle                . $this->getAuthenticationCallerMethod()
851*d10b5556SXylle                . '() at ' . $this->getAuthenticationCallerFile()
852*d10b5556SXylle                . ':' . $this->getAuthenticationCallerLine()
853*d10b5556SXylle                . ') but the method returned false'
854*d10b5556SXylle            );
855*d10b5556SXylle        }
856*d10b5556SXylle    }
857*d10b5556SXylle
858*d10b5556SXylle    /**
859*d10b5556SXylle     * Answer information about the authentication caller.
860*d10b5556SXylle     *
861*d10b5556SXylle     * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
862*d10b5556SXylle     * and markAuthenticationCall() didn't happen.
863*d10b5556SXylle     *
864*d10b5556SXylle     * @return string the file that called authentication
865*d10b5556SXylle     */
866*d10b5556SXylle    public function getAuthenticationCallerFile ()
867*d10b5556SXylle    {
868*d10b5556SXylle        $this->_ensureAuthenticationCalled();
869*d10b5556SXylle        return $this->_authentication_caller['file'];
870*d10b5556SXylle    }
871*d10b5556SXylle
872*d10b5556SXylle    /**
873*d10b5556SXylle     * Answer information about the authentication caller.
874*d10b5556SXylle     *
875*d10b5556SXylle     * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
876*d10b5556SXylle     * and markAuthenticationCall() didn't happen.
877*d10b5556SXylle     *
878*d10b5556SXylle     * @return int the line that called authentication
879*d10b5556SXylle     */
880*d10b5556SXylle    public function getAuthenticationCallerLine ()
881*d10b5556SXylle    {
882*d10b5556SXylle        $this->_ensureAuthenticationCalled();
883*d10b5556SXylle        return $this->_authentication_caller['line'];
884*d10b5556SXylle    }
885*d10b5556SXylle
886*d10b5556SXylle    /**
887*d10b5556SXylle     * Answer information about the authentication caller.
888*d10b5556SXylle     *
889*d10b5556SXylle     * Throws a CAS_OutOfSequenceException if wasAuthenticationCalled() is false
890*d10b5556SXylle     * and markAuthenticationCall() didn't happen.
891*d10b5556SXylle     *
892*d10b5556SXylle     * @return string the method that called authentication
893*d10b5556SXylle     */
894*d10b5556SXylle    public function getAuthenticationCallerMethod ()
895*d10b5556SXylle    {
896*d10b5556SXylle        $this->_ensureAuthenticationCalled();
897*d10b5556SXylle        return $this->_authentication_caller['method'];
898*d10b5556SXylle    }
899*d10b5556SXylle
900*d10b5556SXylle    /** @} */
901*d10b5556SXylle
902*d10b5556SXylle    // ########################################################################
903*d10b5556SXylle    //  CONSTRUCTOR
904*d10b5556SXylle    // ########################################################################
905*d10b5556SXylle    /**
906*d10b5556SXylle    * @addtogroup internalConfig
907*d10b5556SXylle    * @{
908*d10b5556SXylle    */
909*d10b5556SXylle
910*d10b5556SXylle    /**
911*d10b5556SXylle     * CAS_Client constructor.
912*d10b5556SXylle     *
913*d10b5556SXylle     * @param string                   $server_version  the version of the CAS server
914*d10b5556SXylle     * @param bool                     $proxy           true if the CAS client is a CAS proxy
915*d10b5556SXylle     * @param string                   $server_hostname the hostname of the CAS server
916*d10b5556SXylle     * @param int                      $server_port     the port the CAS server is running on
917*d10b5556SXylle     * @param string                   $server_uri      the URI the CAS server is responding on
918*d10b5556SXylle     * @param bool                     $changeSessionID Allow phpCAS to change the session_id
919*d10b5556SXylle     *                                                  (Single Sign Out/handleLogoutRequests
920*d10b5556SXylle     *                                                  is based on that change)
921*d10b5556SXylle     * @param string|string[]|CAS_ServiceBaseUrl_Interface
922*d10b5556SXylle     *                                 $service_base_url the base URL (protocol, host and the
923*d10b5556SXylle     *                                                  optional port) of the CAS client; pass
924*d10b5556SXylle     *                                                  in an array to use auto discovery with
925*d10b5556SXylle     *                                                  an allowlist; pass in
926*d10b5556SXylle     *                                                  CAS_ServiceBaseUrl_Interface for custom
927*d10b5556SXylle     *                                                  behavior. Added in 1.6.0. Similar to
928*d10b5556SXylle     *                                                  serverName config in other CAS clients.
929*d10b5556SXylle     * @param \SessionHandlerInterface $sessionHandler  the session handler
930*d10b5556SXylle     *
931*d10b5556SXylle     * @return self a newly created CAS_Client object
932*d10b5556SXylle     */
933*d10b5556SXylle    public function __construct(
934*d10b5556SXylle        $server_version,
935*d10b5556SXylle        $proxy,
936*d10b5556SXylle        $server_hostname,
937*d10b5556SXylle        $server_port,
938*d10b5556SXylle        $server_uri,
939*d10b5556SXylle        $service_base_url,
940*d10b5556SXylle        $changeSessionID = true,
941*d10b5556SXylle        \SessionHandlerInterface $sessionHandler = null
942*d10b5556SXylle    ) {
943*d10b5556SXylle        // Argument validation
944*d10b5556SXylle        if (gettype($server_version) != 'string')
945*d10b5556SXylle            throw new CAS_TypeMismatchException($server_version, '$server_version', 'string');
946*d10b5556SXylle        if (gettype($proxy) != 'boolean')
947*d10b5556SXylle            throw new CAS_TypeMismatchException($proxy, '$proxy', 'boolean');
948*d10b5556SXylle        if (gettype($server_hostname) != 'string')
949*d10b5556SXylle            throw new CAS_TypeMismatchException($server_hostname, '$server_hostname', 'string');
950*d10b5556SXylle        if (gettype($server_port) != 'integer')
951*d10b5556SXylle            throw new CAS_TypeMismatchException($server_port, '$server_port', 'integer');
952*d10b5556SXylle        if (gettype($server_uri) != 'string')
953*d10b5556SXylle            throw new CAS_TypeMismatchException($server_uri, '$server_uri', 'string');
954*d10b5556SXylle        if (gettype($changeSessionID) != 'boolean')
955*d10b5556SXylle            throw new CAS_TypeMismatchException($changeSessionID, '$changeSessionID', 'boolean');
956*d10b5556SXylle
957*d10b5556SXylle        $this->_setServiceBaseUrl($service_base_url);
958*d10b5556SXylle
959*d10b5556SXylle        if (empty($sessionHandler)) {
960*d10b5556SXylle            $sessionHandler = new CAS_Session_PhpSession;
961*d10b5556SXylle        }
962*d10b5556SXylle
963*d10b5556SXylle        phpCAS::traceBegin();
964*d10b5556SXylle        // true : allow to change the session_id(), false session_id won't be
965*d10b5556SXylle        // changed and logout won't be handled because of that
966*d10b5556SXylle        $this->_setChangeSessionID($changeSessionID);
967*d10b5556SXylle
968*d10b5556SXylle        $this->setSessionHandler($sessionHandler);
969*d10b5556SXylle
970*d10b5556SXylle        if (!$this->_isLogoutRequest()) {
971*d10b5556SXylle            if (session_id() === "") {
972*d10b5556SXylle                // skip Session Handling for logout requests and if don't want it
973*d10b5556SXylle                session_start();
974*d10b5556SXylle                phpCAS :: trace("Starting a new session " . session_id());
975*d10b5556SXylle            }
976*d10b5556SXylle        }
977*d10b5556SXylle
978*d10b5556SXylle        // Only for debug purposes
979*d10b5556SXylle        if ($this->isSessionAuthenticated()){
980*d10b5556SXylle            phpCAS :: trace("Session is authenticated as: " . $this->getSessionValue('user'));
981*d10b5556SXylle        } else {
982*d10b5556SXylle            phpCAS :: trace("Session is not authenticated");
983*d10b5556SXylle        }
984*d10b5556SXylle        // are we in proxy mode ?
985*d10b5556SXylle        $this->_proxy = $proxy;
986*d10b5556SXylle
987*d10b5556SXylle        // Make cookie handling available.
988*d10b5556SXylle        if ($this->isProxy()) {
989*d10b5556SXylle            if (!$this->hasSessionValue('service_cookies')) {
990*d10b5556SXylle                $this->setSessionValue('service_cookies', array());
991*d10b5556SXylle            }
992*d10b5556SXylle            // TODO remove explicit call to $_SESSION
993*d10b5556SXylle            $this->_serviceCookieJar = new CAS_CookieJar(
994*d10b5556SXylle                $_SESSION[static::PHPCAS_SESSION_PREFIX]['service_cookies']
995*d10b5556SXylle            );
996*d10b5556SXylle        }
997*d10b5556SXylle
998*d10b5556SXylle        // check version
999*d10b5556SXylle        $supportedProtocols = phpCAS::getSupportedProtocols();
1000*d10b5556SXylle        if (isset($supportedProtocols[$server_version]) === false) {
1001*d10b5556SXylle            phpCAS::error(
1002*d10b5556SXylle                'this version of CAS (`'.$server_version
1003*d10b5556SXylle                .'\') is not supported by phpCAS '.phpCAS::getVersion()
1004*d10b5556SXylle            );
1005*d10b5556SXylle        }
1006*d10b5556SXylle
1007*d10b5556SXylle        if ($server_version === CAS_VERSION_1_0 && $this->isProxy()) {
1008*d10b5556SXylle            phpCAS::error(
1009*d10b5556SXylle                'CAS proxies are not supported in CAS '.$server_version
1010*d10b5556SXylle            );
1011*d10b5556SXylle        }
1012*d10b5556SXylle
1013*d10b5556SXylle        $this->_server['version'] = $server_version;
1014*d10b5556SXylle
1015*d10b5556SXylle        // check hostname
1016*d10b5556SXylle        if ( empty($server_hostname)
1017*d10b5556SXylle            || !preg_match('/[\.\d\-a-z]*/', $server_hostname)
1018*d10b5556SXylle        ) {
1019*d10b5556SXylle            phpCAS::error('bad CAS server hostname (`'.$server_hostname.'\')');
1020*d10b5556SXylle        }
1021*d10b5556SXylle        $this->_server['hostname'] = $server_hostname;
1022*d10b5556SXylle
1023*d10b5556SXylle        // check port
1024*d10b5556SXylle        if ( $server_port == 0
1025*d10b5556SXylle            || !is_int($server_port)
1026*d10b5556SXylle        ) {
1027*d10b5556SXylle            phpCAS::error('bad CAS server port (`'.$server_hostname.'\')');
1028*d10b5556SXylle        }
1029*d10b5556SXylle        $this->_server['port'] = $server_port;
1030*d10b5556SXylle
1031*d10b5556SXylle        // check URI
1032*d10b5556SXylle        if ( !preg_match('/[\.\d\-_a-z\/]*/', $server_uri) ) {
1033*d10b5556SXylle            phpCAS::error('bad CAS server URI (`'.$server_uri.'\')');
1034*d10b5556SXylle        }
1035*d10b5556SXylle        // add leading and trailing `/' and remove doubles
1036*d10b5556SXylle        if(strstr($server_uri, '?') === false) $server_uri .= '/';
1037*d10b5556SXylle        $server_uri = preg_replace('/\/\//', '/', '/'.$server_uri);
1038*d10b5556SXylle        $this->_server['uri'] = $server_uri;
1039*d10b5556SXylle
1040*d10b5556SXylle        // set to callback mode if PgtIou and PgtId CGI GET parameters are provided
1041*d10b5556SXylle        if ( $this->isProxy() ) {
1042*d10b5556SXylle            if(!empty($_GET['pgtIou'])&&!empty($_GET['pgtId'])) {
1043*d10b5556SXylle                $this->_setCallbackMode(true);
1044*d10b5556SXylle                $this->_setCallbackModeUsingPost(false);
1045*d10b5556SXylle            } elseif (!empty($_POST['pgtIou'])&&!empty($_POST['pgtId'])) {
1046*d10b5556SXylle                $this->_setCallbackMode(true);
1047*d10b5556SXylle                $this->_setCallbackModeUsingPost(true);
1048*d10b5556SXylle            } else {
1049*d10b5556SXylle                $this->_setCallbackMode(false);
1050*d10b5556SXylle                $this->_setCallbackModeUsingPost(false);
1051*d10b5556SXylle            }
1052*d10b5556SXylle
1053*d10b5556SXylle
1054*d10b5556SXylle        }
1055*d10b5556SXylle
1056*d10b5556SXylle        if ( $this->_isCallbackMode() ) {
1057*d10b5556SXylle            //callback mode: check that phpCAS is secured
1058*d10b5556SXylle            if ( !$this->getServiceBaseUrl()->isHttps() ) {
1059*d10b5556SXylle                phpCAS::error(
1060*d10b5556SXylle                    'CAS proxies must be secured to use phpCAS; PGT\'s will not be received from the CAS server'
1061*d10b5556SXylle                );
1062*d10b5556SXylle            }
1063*d10b5556SXylle        } else {
1064*d10b5556SXylle            //normal mode: get ticket and remove it from CGI parameters for
1065*d10b5556SXylle            // developers
1066*d10b5556SXylle            $ticket = (isset($_GET['ticket']) ? $_GET['ticket'] : '');
1067*d10b5556SXylle            if (preg_match('/^[SP]T-/', $ticket) ) {
1068*d10b5556SXylle                phpCAS::trace('Ticket \''.$ticket.'\' found');
1069*d10b5556SXylle                $this->setTicket($ticket);
1070*d10b5556SXylle                unset($_GET['ticket']);
1071*d10b5556SXylle            } else if ( !empty($ticket) ) {
1072*d10b5556SXylle                //ill-formed ticket, halt
1073*d10b5556SXylle                phpCAS::error(
1074*d10b5556SXylle                    'ill-formed ticket found in the URL (ticket=`'
1075*d10b5556SXylle                    .htmlentities($ticket).'\')'
1076*d10b5556SXylle                );
1077*d10b5556SXylle            }
1078*d10b5556SXylle
1079*d10b5556SXylle        }
1080*d10b5556SXylle        phpCAS::traceEnd();
1081*d10b5556SXylle    }
1082*d10b5556SXylle
1083*d10b5556SXylle    /** @} */
1084*d10b5556SXylle
1085*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1086*d10b5556SXylle    // XX                                                                    XX
1087*d10b5556SXylle    // XX                           Session Handling                         XX
1088*d10b5556SXylle    // XX                                                                    XX
1089*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1090*d10b5556SXylle
1091*d10b5556SXylle    /**
1092*d10b5556SXylle     * @addtogroup internalConfig
1093*d10b5556SXylle     * @{
1094*d10b5556SXylle     */
1095*d10b5556SXylle
1096*d10b5556SXylle    /** The session prefix for phpCAS values */
1097*d10b5556SXylle    const PHPCAS_SESSION_PREFIX = 'phpCAS';
1098*d10b5556SXylle
1099*d10b5556SXylle    /**
1100*d10b5556SXylle     * @var bool A variable to whether phpcas will use its own session handling. Default = true
1101*d10b5556SXylle     * @hideinitializer
1102*d10b5556SXylle     */
1103*d10b5556SXylle    private $_change_session_id = true;
1104*d10b5556SXylle
1105*d10b5556SXylle    /**
1106*d10b5556SXylle     * @var SessionHandlerInterface
1107*d10b5556SXylle     */
1108*d10b5556SXylle    private $_sessionHandler;
1109*d10b5556SXylle
1110*d10b5556SXylle    /**
1111*d10b5556SXylle     * Set a parameter whether to allow phpCAS to change session_id
1112*d10b5556SXylle     *
1113*d10b5556SXylle     * @param bool $allowed allow phpCAS to change session_id
1114*d10b5556SXylle     *
1115*d10b5556SXylle     * @return void
1116*d10b5556SXylle     */
1117*d10b5556SXylle    private function _setChangeSessionID($allowed)
1118*d10b5556SXylle    {
1119*d10b5556SXylle        $this->_change_session_id = $allowed;
1120*d10b5556SXylle    }
1121*d10b5556SXylle
1122*d10b5556SXylle    /**
1123*d10b5556SXylle     * Get whether phpCAS is allowed to change session_id
1124*d10b5556SXylle     *
1125*d10b5556SXylle     * @return bool
1126*d10b5556SXylle     */
1127*d10b5556SXylle    public function getChangeSessionID()
1128*d10b5556SXylle    {
1129*d10b5556SXylle        return $this->_change_session_id;
1130*d10b5556SXylle    }
1131*d10b5556SXylle
1132*d10b5556SXylle    /**
1133*d10b5556SXylle     * Set the session handler.
1134*d10b5556SXylle     *
1135*d10b5556SXylle     * @param \SessionHandlerInterface $sessionHandler
1136*d10b5556SXylle     *
1137*d10b5556SXylle     * @return bool
1138*d10b5556SXylle     */
1139*d10b5556SXylle    public function setSessionHandler(\SessionHandlerInterface $sessionHandler)
1140*d10b5556SXylle    {
1141*d10b5556SXylle        $this->_sessionHandler = $sessionHandler;
1142*d10b5556SXylle        if (session_status() !== PHP_SESSION_ACTIVE) {
1143*d10b5556SXylle            return session_set_save_handler($this->_sessionHandler, true);
1144*d10b5556SXylle        }
1145*d10b5556SXylle        return true;
1146*d10b5556SXylle    }
1147*d10b5556SXylle
1148*d10b5556SXylle    /**
1149*d10b5556SXylle     * Get a session value using the given key.
1150*d10b5556SXylle     *
1151*d10b5556SXylle     * @param string $key
1152*d10b5556SXylle     * @param mixed  $default default value if the key is not set
1153*d10b5556SXylle     *
1154*d10b5556SXylle     * @return mixed
1155*d10b5556SXylle     */
1156*d10b5556SXylle    protected function getSessionValue($key, $default = null)
1157*d10b5556SXylle    {
1158*d10b5556SXylle        $this->validateSession($key);
1159*d10b5556SXylle
1160*d10b5556SXylle        if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) {
1161*d10b5556SXylle            return $_SESSION[static::PHPCAS_SESSION_PREFIX][$key];
1162*d10b5556SXylle        }
1163*d10b5556SXylle
1164*d10b5556SXylle        return $default;
1165*d10b5556SXylle    }
1166*d10b5556SXylle
1167*d10b5556SXylle    /**
1168*d10b5556SXylle     * Determine whether a session value is set or not.
1169*d10b5556SXylle     *
1170*d10b5556SXylle     * To check if a session value is empty or not please use
1171*d10b5556SXylle     * !!(getSessionValue($key)).
1172*d10b5556SXylle     *
1173*d10b5556SXylle     * @param string $key
1174*d10b5556SXylle     *
1175*d10b5556SXylle     * @return bool
1176*d10b5556SXylle     */
1177*d10b5556SXylle    protected function hasSessionValue($key)
1178*d10b5556SXylle    {
1179*d10b5556SXylle        $this->validateSession($key);
1180*d10b5556SXylle
1181*d10b5556SXylle        return isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]);
1182*d10b5556SXylle    }
1183*d10b5556SXylle
1184*d10b5556SXylle    /**
1185*d10b5556SXylle     * Set a session value using the given key and value.
1186*d10b5556SXylle     *
1187*d10b5556SXylle     * @param string $key
1188*d10b5556SXylle     * @param mixed $value
1189*d10b5556SXylle     *
1190*d10b5556SXylle     * @return string
1191*d10b5556SXylle     */
1192*d10b5556SXylle    protected function setSessionValue($key, $value)
1193*d10b5556SXylle    {
1194*d10b5556SXylle        $this->validateSession($key);
1195*d10b5556SXylle
1196*d10b5556SXylle        $this->ensureSessionArray();
1197*d10b5556SXylle        $_SESSION[static::PHPCAS_SESSION_PREFIX][$key] = $value;
1198*d10b5556SXylle    }
1199*d10b5556SXylle
1200*d10b5556SXylle    /**
1201*d10b5556SXylle     * Ensure that the session array is initialized before writing to it.
1202*d10b5556SXylle     */
1203*d10b5556SXylle    protected function ensureSessionArray() {
1204*d10b5556SXylle      // init phpCAS session array
1205*d10b5556SXylle      if (!isset($_SESSION[static::PHPCAS_SESSION_PREFIX])
1206*d10b5556SXylle          || !is_array($_SESSION[static::PHPCAS_SESSION_PREFIX])) {
1207*d10b5556SXylle          $_SESSION[static::PHPCAS_SESSION_PREFIX] = array();
1208*d10b5556SXylle      }
1209*d10b5556SXylle    }
1210*d10b5556SXylle
1211*d10b5556SXylle    /**
1212*d10b5556SXylle     * Remove a session value with the given key.
1213*d10b5556SXylle     *
1214*d10b5556SXylle     * @param string $key
1215*d10b5556SXylle     */
1216*d10b5556SXylle    protected function removeSessionValue($key)
1217*d10b5556SXylle    {
1218*d10b5556SXylle        $this->validateSession($key);
1219*d10b5556SXylle
1220*d10b5556SXylle        if (isset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key])) {
1221*d10b5556SXylle            unset($_SESSION[static::PHPCAS_SESSION_PREFIX][$key]);
1222*d10b5556SXylle            return true;
1223*d10b5556SXylle        }
1224*d10b5556SXylle
1225*d10b5556SXylle        return false;
1226*d10b5556SXylle    }
1227*d10b5556SXylle
1228*d10b5556SXylle    /**
1229*d10b5556SXylle     * Remove all phpCAS session values.
1230*d10b5556SXylle     */
1231*d10b5556SXylle    protected function clearSessionValues()
1232*d10b5556SXylle    {
1233*d10b5556SXylle        unset($_SESSION[static::PHPCAS_SESSION_PREFIX]);
1234*d10b5556SXylle    }
1235*d10b5556SXylle
1236*d10b5556SXylle    /**
1237*d10b5556SXylle     * Ensure $key is a string for session utils input
1238*d10b5556SXylle     *
1239*d10b5556SXylle     * @param string $key
1240*d10b5556SXylle     *
1241*d10b5556SXylle     * @return bool
1242*d10b5556SXylle     */
1243*d10b5556SXylle    protected function validateSession($key)
1244*d10b5556SXylle    {
1245*d10b5556SXylle        if (!is_string($key)) {
1246*d10b5556SXylle            throw new InvalidArgumentException('Session key must be a string.');
1247*d10b5556SXylle        }
1248*d10b5556SXylle
1249*d10b5556SXylle        return true;
1250*d10b5556SXylle    }
1251*d10b5556SXylle
1252*d10b5556SXylle    /**
1253*d10b5556SXylle     * Renaming the session
1254*d10b5556SXylle     *
1255*d10b5556SXylle     * @param string $ticket name of the ticket
1256*d10b5556SXylle     *
1257*d10b5556SXylle     * @return void
1258*d10b5556SXylle     */
1259*d10b5556SXylle    protected function _renameSession($ticket)
1260*d10b5556SXylle    {
1261*d10b5556SXylle        phpCAS::traceBegin();
1262*d10b5556SXylle        if ($this->getChangeSessionID()) {
1263*d10b5556SXylle            if (!empty($this->_user)) {
1264*d10b5556SXylle                $old_session = $_SESSION;
1265*d10b5556SXylle                phpCAS :: trace("Killing session: ". session_id());
1266*d10b5556SXylle                session_destroy();
1267*d10b5556SXylle                // set up a new session, of name based on the ticket
1268*d10b5556SXylle                $session_id = $this->_sessionIdForTicket($ticket);
1269*d10b5556SXylle                phpCAS :: trace("Starting session: ". $session_id);
1270*d10b5556SXylle                session_id($session_id);
1271*d10b5556SXylle                session_start();
1272*d10b5556SXylle                phpCAS :: trace("Restoring old session vars");
1273*d10b5556SXylle                $_SESSION = $old_session;
1274*d10b5556SXylle            } else {
1275*d10b5556SXylle                phpCAS :: trace (
1276*d10b5556SXylle                    'Session should only be renamed after successfull authentication'
1277*d10b5556SXylle                );
1278*d10b5556SXylle            }
1279*d10b5556SXylle        } else {
1280*d10b5556SXylle            phpCAS :: trace(
1281*d10b5556SXylle                "Skipping session rename since phpCAS is not handling the session."
1282*d10b5556SXylle            );
1283*d10b5556SXylle        }
1284*d10b5556SXylle        phpCAS::traceEnd();
1285*d10b5556SXylle    }
1286*d10b5556SXylle
1287*d10b5556SXylle    /** @} */
1288*d10b5556SXylle
1289*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1290*d10b5556SXylle    // XX                                                                    XX
1291*d10b5556SXylle    // XX                           AUTHENTICATION                           XX
1292*d10b5556SXylle    // XX                                                                    XX
1293*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
1294*d10b5556SXylle
1295*d10b5556SXylle    /**
1296*d10b5556SXylle     * @addtogroup internalAuthentication
1297*d10b5556SXylle     * @{
1298*d10b5556SXylle     */
1299*d10b5556SXylle
1300*d10b5556SXylle    /**
1301*d10b5556SXylle     * The Authenticated user. Written by CAS_Client::_setUser(), read by
1302*d10b5556SXylle     * CAS_Client::getUser().
1303*d10b5556SXylle     *
1304*d10b5556SXylle     * @hideinitializer
1305*d10b5556SXylle     */
1306*d10b5556SXylle    private $_user = '';
1307*d10b5556SXylle
1308*d10b5556SXylle    /**
1309*d10b5556SXylle     * This method sets the CAS user's login name.
1310*d10b5556SXylle     *
1311*d10b5556SXylle     * @param string $user the login name of the authenticated user.
1312*d10b5556SXylle     *
1313*d10b5556SXylle     * @return void
1314*d10b5556SXylle     */
1315*d10b5556SXylle    private function _setUser($user)
1316*d10b5556SXylle    {
1317*d10b5556SXylle        $this->_user = $user;
1318*d10b5556SXylle    }
1319*d10b5556SXylle
1320*d10b5556SXylle    /**
1321*d10b5556SXylle     * This method returns the CAS user's login name.
1322*d10b5556SXylle     *
1323*d10b5556SXylle     * @return string the login name of the authenticated user
1324*d10b5556SXylle     *
1325*d10b5556SXylle     * @warning should be called only after CAS_Client::forceAuthentication() or
1326*d10b5556SXylle     * CAS_Client::isAuthenticated(), otherwise halt with an error.
1327*d10b5556SXylle     */
1328*d10b5556SXylle    public function getUser()
1329*d10b5556SXylle    {
1330*d10b5556SXylle        // Sequence validation
1331*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
1332*d10b5556SXylle
1333*d10b5556SXylle        return $this->_getUser();
1334*d10b5556SXylle    }
1335*d10b5556SXylle
1336*d10b5556SXylle    /**
1337*d10b5556SXylle     * This method returns the CAS user's login name.
1338*d10b5556SXylle     *
1339*d10b5556SXylle     * @return string the login name of the authenticated user
1340*d10b5556SXylle     *
1341*d10b5556SXylle     * @warning should be called only after CAS_Client::forceAuthentication() or
1342*d10b5556SXylle     * CAS_Client::isAuthenticated(), otherwise halt with an error.
1343*d10b5556SXylle     */
1344*d10b5556SXylle    private function _getUser()
1345*d10b5556SXylle    {
1346*d10b5556SXylle        // This is likely a duplicate check that could be removed....
1347*d10b5556SXylle        if ( empty($this->_user) ) {
1348*d10b5556SXylle            phpCAS::error(
1349*d10b5556SXylle                'this method should be used only after '.__CLASS__
1350*d10b5556SXylle                .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
1351*d10b5556SXylle            );
1352*d10b5556SXylle        }
1353*d10b5556SXylle        return $this->_user;
1354*d10b5556SXylle    }
1355*d10b5556SXylle
1356*d10b5556SXylle    /**
1357*d10b5556SXylle     * The Authenticated users attributes. Written by
1358*d10b5556SXylle     * CAS_Client::setAttributes(), read by CAS_Client::getAttributes().
1359*d10b5556SXylle     * @attention client applications should use phpCAS::getAttributes().
1360*d10b5556SXylle     *
1361*d10b5556SXylle     * @hideinitializer
1362*d10b5556SXylle     */
1363*d10b5556SXylle    private $_attributes = array();
1364*d10b5556SXylle
1365*d10b5556SXylle    /**
1366*d10b5556SXylle     * Set an array of attributes
1367*d10b5556SXylle     *
1368*d10b5556SXylle     * @param array $attributes a key value array of attributes
1369*d10b5556SXylle     *
1370*d10b5556SXylle     * @return void
1371*d10b5556SXylle     */
1372*d10b5556SXylle    public function setAttributes($attributes)
1373*d10b5556SXylle    {
1374*d10b5556SXylle        $this->_attributes = $attributes;
1375*d10b5556SXylle    }
1376*d10b5556SXylle
1377*d10b5556SXylle    /**
1378*d10b5556SXylle     * Get an key values arry of attributes
1379*d10b5556SXylle     *
1380*d10b5556SXylle     * @return array of attributes
1381*d10b5556SXylle     */
1382*d10b5556SXylle    public function getAttributes()
1383*d10b5556SXylle    {
1384*d10b5556SXylle        // Sequence validation
1385*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
1386*d10b5556SXylle        // This is likely a duplicate check that could be removed....
1387*d10b5556SXylle        if ( empty($this->_user) ) {
1388*d10b5556SXylle            // if no user is set, there shouldn't be any attributes also...
1389*d10b5556SXylle            phpCAS::error(
1390*d10b5556SXylle                'this method should be used only after '.__CLASS__
1391*d10b5556SXylle                .'::forceAuthentication() or '.__CLASS__.'::isAuthenticated()'
1392*d10b5556SXylle            );
1393*d10b5556SXylle        }
1394*d10b5556SXylle        return $this->_attributes;
1395*d10b5556SXylle    }
1396*d10b5556SXylle
1397*d10b5556SXylle    /**
1398*d10b5556SXylle     * Check whether attributes are available
1399*d10b5556SXylle     *
1400*d10b5556SXylle     * @return bool attributes available
1401*d10b5556SXylle     */
1402*d10b5556SXylle    public function hasAttributes()
1403*d10b5556SXylle    {
1404*d10b5556SXylle        // Sequence validation
1405*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
1406*d10b5556SXylle
1407*d10b5556SXylle        return !empty($this->_attributes);
1408*d10b5556SXylle    }
1409*d10b5556SXylle    /**
1410*d10b5556SXylle     * Check whether a specific attribute with a name is available
1411*d10b5556SXylle     *
1412*d10b5556SXylle     * @param string $key name of attribute
1413*d10b5556SXylle     *
1414*d10b5556SXylle     * @return bool is attribute available
1415*d10b5556SXylle     */
1416*d10b5556SXylle    public function hasAttribute($key)
1417*d10b5556SXylle    {
1418*d10b5556SXylle        // Sequence validation
1419*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
1420*d10b5556SXylle
1421*d10b5556SXylle        return $this->_hasAttribute($key);
1422*d10b5556SXylle    }
1423*d10b5556SXylle
1424*d10b5556SXylle    /**
1425*d10b5556SXylle     * Check whether a specific attribute with a name is available
1426*d10b5556SXylle     *
1427*d10b5556SXylle     * @param string $key name of attribute
1428*d10b5556SXylle     *
1429*d10b5556SXylle     * @return bool is attribute available
1430*d10b5556SXylle     */
1431*d10b5556SXylle    private function _hasAttribute($key)
1432*d10b5556SXylle    {
1433*d10b5556SXylle        return (is_array($this->_attributes)
1434*d10b5556SXylle            && array_key_exists($key, $this->_attributes));
1435*d10b5556SXylle    }
1436*d10b5556SXylle
1437*d10b5556SXylle    /**
1438*d10b5556SXylle     * Get a specific attribute by name
1439*d10b5556SXylle     *
1440*d10b5556SXylle     * @param string $key name of attribute
1441*d10b5556SXylle     *
1442*d10b5556SXylle     * @return string attribute values
1443*d10b5556SXylle     */
1444*d10b5556SXylle    public function getAttribute($key)
1445*d10b5556SXylle    {
1446*d10b5556SXylle        // Sequence validation
1447*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
1448*d10b5556SXylle
1449*d10b5556SXylle        if ($this->_hasAttribute($key)) {
1450*d10b5556SXylle            return $this->_attributes[$key];
1451*d10b5556SXylle        }
1452*d10b5556SXylle    }
1453*d10b5556SXylle
1454*d10b5556SXylle    /**
1455*d10b5556SXylle     * This method is called to renew the authentication of the user
1456*d10b5556SXylle     * If the user is authenticated, renew the connection
1457*d10b5556SXylle     * If not, redirect to CAS
1458*d10b5556SXylle     *
1459*d10b5556SXylle     * @return bool true when the user is authenticated; otherwise halt.
1460*d10b5556SXylle     */
1461*d10b5556SXylle    public function renewAuthentication()
1462*d10b5556SXylle    {
1463*d10b5556SXylle        phpCAS::traceBegin();
1464*d10b5556SXylle        // Either way, the user is authenticated by CAS
1465*d10b5556SXylle        $this->removeSessionValue('auth_checked');
1466*d10b5556SXylle        if ( $this->isAuthenticated(true) ) {
1467*d10b5556SXylle            phpCAS::trace('user already authenticated');
1468*d10b5556SXylle            $res = true;
1469*d10b5556SXylle        } else {
1470*d10b5556SXylle            $this->redirectToCas(false, true);
1471*d10b5556SXylle            // never reached
1472*d10b5556SXylle            $res = false;
1473*d10b5556SXylle        }
1474*d10b5556SXylle        phpCAS::traceEnd();
1475*d10b5556SXylle        return $res;
1476*d10b5556SXylle    }
1477*d10b5556SXylle
1478*d10b5556SXylle    /**
1479*d10b5556SXylle     * This method is called to be sure that the user is authenticated. When not
1480*d10b5556SXylle     * authenticated, halt by redirecting to the CAS server; otherwise return true.
1481*d10b5556SXylle     *
1482*d10b5556SXylle     * @return bool true when the user is authenticated; otherwise halt.
1483*d10b5556SXylle     */
1484*d10b5556SXylle    public function forceAuthentication()
1485*d10b5556SXylle    {
1486*d10b5556SXylle        phpCAS::traceBegin();
1487*d10b5556SXylle
1488*d10b5556SXylle        if ( $this->isAuthenticated() ) {
1489*d10b5556SXylle            // the user is authenticated, nothing to be done.
1490*d10b5556SXylle            phpCAS::trace('no need to authenticate');
1491*d10b5556SXylle            $res = true;
1492*d10b5556SXylle        } else {
1493*d10b5556SXylle            // the user is not authenticated, redirect to the CAS server
1494*d10b5556SXylle            $this->removeSessionValue('auth_checked');
1495*d10b5556SXylle            $this->redirectToCas(false/* no gateway */);
1496*d10b5556SXylle            // never reached
1497*d10b5556SXylle            $res = false;
1498*d10b5556SXylle        }
1499*d10b5556SXylle        phpCAS::traceEnd($res);
1500*d10b5556SXylle        return $res;
1501*d10b5556SXylle    }
1502*d10b5556SXylle
1503*d10b5556SXylle    /**
1504*d10b5556SXylle     * An integer that gives the number of times authentication will be cached
1505*d10b5556SXylle     * before rechecked.
1506*d10b5556SXylle     *
1507*d10b5556SXylle     * @hideinitializer
1508*d10b5556SXylle     */
1509*d10b5556SXylle    private $_cache_times_for_auth_recheck = 0;
1510*d10b5556SXylle
1511*d10b5556SXylle    /**
1512*d10b5556SXylle     * Set the number of times authentication will be cached before rechecked.
1513*d10b5556SXylle     *
1514*d10b5556SXylle     * @param int $n number of times to wait for a recheck
1515*d10b5556SXylle     *
1516*d10b5556SXylle     * @return void
1517*d10b5556SXylle     */
1518*d10b5556SXylle    public function setCacheTimesForAuthRecheck($n)
1519*d10b5556SXylle    {
1520*d10b5556SXylle        if (gettype($n) != 'integer')
1521*d10b5556SXylle            throw new CAS_TypeMismatchException($n, '$n', 'string');
1522*d10b5556SXylle
1523*d10b5556SXylle        $this->_cache_times_for_auth_recheck = $n;
1524*d10b5556SXylle    }
1525*d10b5556SXylle
1526*d10b5556SXylle    /**
1527*d10b5556SXylle     * This method is called to check whether the user is authenticated or not.
1528*d10b5556SXylle     *
1529*d10b5556SXylle     * @return bool true when the user is authenticated, false when a previous
1530*d10b5556SXylle     * gateway login failed or  the function will not return if the user is
1531*d10b5556SXylle     * redirected to the cas server for a gateway login attempt
1532*d10b5556SXylle     */
1533*d10b5556SXylle    public function checkAuthentication()
1534*d10b5556SXylle    {
1535*d10b5556SXylle        phpCAS::traceBegin();
1536*d10b5556SXylle        $res = false; // default
1537*d10b5556SXylle        if ( $this->isAuthenticated() ) {
1538*d10b5556SXylle            phpCAS::trace('user is authenticated');
1539*d10b5556SXylle            /* The 'auth_checked' variable is removed just in case it's set. */
1540*d10b5556SXylle            $this->removeSessionValue('auth_checked');
1541*d10b5556SXylle            $res = true;
1542*d10b5556SXylle        } else if ($this->getSessionValue('auth_checked')) {
1543*d10b5556SXylle            // the previous request has redirected the client to the CAS server
1544*d10b5556SXylle            // with gateway=true
1545*d10b5556SXylle            $this->removeSessionValue('auth_checked');
1546*d10b5556SXylle        } else {
1547*d10b5556SXylle            // avoid a check against CAS on every request
1548*d10b5556SXylle            // we need to write this back to session later
1549*d10b5556SXylle            $unauth_count = $this->getSessionValue('unauth_count', -2);
1550*d10b5556SXylle
1551*d10b5556SXylle            if (($unauth_count != -2
1552*d10b5556SXylle                && $this->_cache_times_for_auth_recheck == -1)
1553*d10b5556SXylle                || ($unauth_count >= 0
1554*d10b5556SXylle                && $unauth_count < $this->_cache_times_for_auth_recheck)
1555*d10b5556SXylle            ) {
1556*d10b5556SXylle                if ($this->_cache_times_for_auth_recheck != -1) {
1557*d10b5556SXylle                    $unauth_count++;
1558*d10b5556SXylle                    phpCAS::trace(
1559*d10b5556SXylle                        'user is not authenticated (cached for '
1560*d10b5556SXylle                        .$unauth_count.' times of '
1561*d10b5556SXylle                        .$this->_cache_times_for_auth_recheck.')'
1562*d10b5556SXylle                    );
1563*d10b5556SXylle                } else {
1564*d10b5556SXylle                    phpCAS::trace(
1565*d10b5556SXylle                        'user is not authenticated (cached for until login pressed)'
1566*d10b5556SXylle                    );
1567*d10b5556SXylle                }
1568*d10b5556SXylle                $this->setSessionValue('unauth_count', $unauth_count);
1569*d10b5556SXylle            } else {
1570*d10b5556SXylle                $this->setSessionValue('unauth_count', 0);
1571*d10b5556SXylle                $this->setSessionValue('auth_checked', true);
1572*d10b5556SXylle                phpCAS::trace('user is not authenticated (cache reset)');
1573*d10b5556SXylle                $this->redirectToCas(true/* gateway */);
1574*d10b5556SXylle                // never reached
1575*d10b5556SXylle            }
1576*d10b5556SXylle        }
1577*d10b5556SXylle        phpCAS::traceEnd($res);
1578*d10b5556SXylle        return $res;
1579*d10b5556SXylle    }
1580*d10b5556SXylle
1581*d10b5556SXylle    /**
1582*d10b5556SXylle     * This method is called to check if the user is authenticated (previously or by
1583*d10b5556SXylle     * tickets given in the URL).
1584*d10b5556SXylle     *
1585*d10b5556SXylle     * @param bool $renew true to force the authentication with the CAS server
1586*d10b5556SXylle     *
1587*d10b5556SXylle     * @return bool true when the user is authenticated. Also may redirect to the
1588*d10b5556SXylle     * same URL without the ticket.
1589*d10b5556SXylle     */
1590*d10b5556SXylle    public function isAuthenticated($renew=false)
1591*d10b5556SXylle    {
1592*d10b5556SXylle        phpCAS::traceBegin();
1593*d10b5556SXylle        $res = false;
1594*d10b5556SXylle
1595*d10b5556SXylle        if ( $this->_wasPreviouslyAuthenticated() ) {
1596*d10b5556SXylle            if ($this->hasTicket()) {
1597*d10b5556SXylle                // User has a additional ticket but was already authenticated
1598*d10b5556SXylle                phpCAS::trace(
1599*d10b5556SXylle                    'ticket was present and will be discarded, use renewAuthenticate()'
1600*d10b5556SXylle                );
1601*d10b5556SXylle                if ($this->_clearTicketsFromUrl) {
1602*d10b5556SXylle                    phpCAS::trace("Prepare redirect to : ".$this->getURL());
1603*d10b5556SXylle                    session_write_close();
1604*d10b5556SXylle                    header('Location: '.$this->getURL());
1605*d10b5556SXylle                    flush();
1606*d10b5556SXylle                    phpCAS::traceExit();
1607*d10b5556SXylle                    throw new CAS_GracefullTerminationException();
1608*d10b5556SXylle                } else {
1609*d10b5556SXylle                    phpCAS::trace(
1610*d10b5556SXylle                        'Already authenticated, but skipping ticket clearing since setNoClearTicketsFromUrl() was used.'
1611*d10b5556SXylle                    );
1612*d10b5556SXylle                    $res = true;
1613*d10b5556SXylle                }
1614*d10b5556SXylle            } else {
1615*d10b5556SXylle                // the user has already (previously during the session) been
1616*d10b5556SXylle                // authenticated, nothing to be done.
1617*d10b5556SXylle                phpCAS::trace(
1618*d10b5556SXylle                    'user was already authenticated, no need to look for tickets'
1619*d10b5556SXylle                );
1620*d10b5556SXylle                $res = true;
1621*d10b5556SXylle            }
1622*d10b5556SXylle
1623*d10b5556SXylle            // Mark the auth-check as complete to allow post-authentication
1624*d10b5556SXylle            // callbacks to make use of phpCAS::getUser() and similar methods
1625*d10b5556SXylle            $this->markAuthenticationCall($res);
1626*d10b5556SXylle        } else {
1627*d10b5556SXylle            if ($this->hasTicket()) {
1628*d10b5556SXylle                $validate_url = '';
1629*d10b5556SXylle                $text_response = '';
1630*d10b5556SXylle                $tree_response = '';
1631*d10b5556SXylle
1632*d10b5556SXylle                switch ($this->getServerVersion()) {
1633*d10b5556SXylle                case CAS_VERSION_1_0:
1634*d10b5556SXylle                    // if a Service Ticket was given, validate it
1635*d10b5556SXylle                    phpCAS::trace(
1636*d10b5556SXylle                        'CAS 1.0 ticket `'.$this->getTicket().'\' is present'
1637*d10b5556SXylle                    );
1638*d10b5556SXylle                    $this->validateCAS10(
1639*d10b5556SXylle                        $validate_url, $text_response, $tree_response, $renew
1640*d10b5556SXylle                    ); // if it fails, it halts
1641*d10b5556SXylle                    phpCAS::trace(
1642*d10b5556SXylle                        'CAS 1.0 ticket `'.$this->getTicket().'\' was validated'
1643*d10b5556SXylle                    );
1644*d10b5556SXylle                    $this->setSessionValue('user', $this->_getUser());
1645*d10b5556SXylle                    $res = true;
1646*d10b5556SXylle                    $logoutTicket = $this->getTicket();
1647*d10b5556SXylle                    break;
1648*d10b5556SXylle                case CAS_VERSION_2_0:
1649*d10b5556SXylle                case CAS_VERSION_3_0:
1650*d10b5556SXylle                    // if a Proxy Ticket was given, validate it
1651*d10b5556SXylle                    phpCAS::trace(
1652*d10b5556SXylle                        'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' is present'
1653*d10b5556SXylle                    );
1654*d10b5556SXylle                    $this->validateCAS20(
1655*d10b5556SXylle                        $validate_url, $text_response, $tree_response, $renew
1656*d10b5556SXylle                    ); // note: if it fails, it halts
1657*d10b5556SXylle                    phpCAS::trace(
1658*d10b5556SXylle                        'CAS '.$this->getServerVersion().' ticket `'.$this->getTicket().'\' was validated'
1659*d10b5556SXylle                    );
1660*d10b5556SXylle                    if ( $this->isProxy() ) {
1661*d10b5556SXylle                        $this->_validatePGT(
1662*d10b5556SXylle                            $validate_url, $text_response, $tree_response
1663*d10b5556SXylle                        ); // idem
1664*d10b5556SXylle                        phpCAS::trace('PGT `'.$this->_getPGT().'\' was validated');
1665*d10b5556SXylle                        $this->setSessionValue('pgt', $this->_getPGT());
1666*d10b5556SXylle                    }
1667*d10b5556SXylle                    $this->setSessionValue('user', $this->_getUser());
1668*d10b5556SXylle                    if (!empty($this->_attributes)) {
1669*d10b5556SXylle                        $this->setSessionValue('attributes', $this->_attributes);
1670*d10b5556SXylle                    }
1671*d10b5556SXylle                    $proxies = $this->getProxies();
1672*d10b5556SXylle                    if (!empty($proxies)) {
1673*d10b5556SXylle                        $this->setSessionValue('proxies', $this->getProxies());
1674*d10b5556SXylle                    }
1675*d10b5556SXylle                    $res = true;
1676*d10b5556SXylle                    $logoutTicket = $this->getTicket();
1677*d10b5556SXylle                    break;
1678*d10b5556SXylle                case SAML_VERSION_1_1:
1679*d10b5556SXylle                    // if we have a SAML ticket, validate it.
1680*d10b5556SXylle                    phpCAS::trace(
1681*d10b5556SXylle                        'SAML 1.1 ticket `'.$this->getTicket().'\' is present'
1682*d10b5556SXylle                    );
1683*d10b5556SXylle                    $this->validateSA(
1684*d10b5556SXylle                        $validate_url, $text_response, $tree_response, $renew
1685*d10b5556SXylle                    ); // if it fails, it halts
1686*d10b5556SXylle                    phpCAS::trace(
1687*d10b5556SXylle                        'SAML 1.1 ticket `'.$this->getTicket().'\' was validated'
1688*d10b5556SXylle                    );
1689*d10b5556SXylle                    $this->setSessionValue('user', $this->_getUser());
1690*d10b5556SXylle                    $this->setSessionValue('attributes', $this->_attributes);
1691*d10b5556SXylle                    $res = true;
1692*d10b5556SXylle                    $logoutTicket = $this->getTicket();
1693*d10b5556SXylle                    break;
1694*d10b5556SXylle                default:
1695*d10b5556SXylle                    phpCAS::trace('Protocol error');
1696*d10b5556SXylle                    break;
1697*d10b5556SXylle                }
1698*d10b5556SXylle            } else {
1699*d10b5556SXylle                // no ticket given, not authenticated
1700*d10b5556SXylle                phpCAS::trace('no ticket found');
1701*d10b5556SXylle            }
1702*d10b5556SXylle
1703*d10b5556SXylle            // Mark the auth-check as complete to allow post-authentication
1704*d10b5556SXylle            // callbacks to make use of phpCAS::getUser() and similar methods
1705*d10b5556SXylle            $this->markAuthenticationCall($res);
1706*d10b5556SXylle
1707*d10b5556SXylle            if ($res) {
1708*d10b5556SXylle                // call the post-authenticate callback if registered.
1709*d10b5556SXylle                if ($this->_postAuthenticateCallbackFunction) {
1710*d10b5556SXylle                    $args = $this->_postAuthenticateCallbackArgs;
1711*d10b5556SXylle                    array_unshift($args, $logoutTicket);
1712*d10b5556SXylle                    call_user_func_array(
1713*d10b5556SXylle                        $this->_postAuthenticateCallbackFunction, $args
1714*d10b5556SXylle                    );
1715*d10b5556SXylle                }
1716*d10b5556SXylle
1717*d10b5556SXylle                // if called with a ticket parameter, we need to redirect to the
1718*d10b5556SXylle                // app without the ticket so that CAS-ification is transparent
1719*d10b5556SXylle                // to the browser (for later POSTS) most of the checks and
1720*d10b5556SXylle                // errors should have been made now, so we're safe for redirect
1721*d10b5556SXylle                // without masking error messages. remove the ticket as a
1722*d10b5556SXylle                // security precaution to prevent a ticket in the HTTP_REFERRER
1723*d10b5556SXylle                if ($this->_clearTicketsFromUrl) {
1724*d10b5556SXylle                    phpCAS::trace("Prepare redirect to : ".$this->getURL());
1725*d10b5556SXylle                    session_write_close();
1726*d10b5556SXylle                    header('Location: '.$this->getURL());
1727*d10b5556SXylle                    flush();
1728*d10b5556SXylle                    phpCAS::traceExit();
1729*d10b5556SXylle                    throw new CAS_GracefullTerminationException();
1730*d10b5556SXylle                }
1731*d10b5556SXylle            }
1732*d10b5556SXylle        }
1733*d10b5556SXylle        phpCAS::traceEnd($res);
1734*d10b5556SXylle        return $res;
1735*d10b5556SXylle    }
1736*d10b5556SXylle
1737*d10b5556SXylle    /**
1738*d10b5556SXylle     * This method tells if the current session is authenticated.
1739*d10b5556SXylle     *
1740*d10b5556SXylle     * @return bool true if authenticated based soley on $_SESSION variable
1741*d10b5556SXylle     */
1742*d10b5556SXylle    public function isSessionAuthenticated ()
1743*d10b5556SXylle    {
1744*d10b5556SXylle        return !!$this->getSessionValue('user');
1745*d10b5556SXylle    }
1746*d10b5556SXylle
1747*d10b5556SXylle    /**
1748*d10b5556SXylle     * This method tells if the user has already been (previously) authenticated
1749*d10b5556SXylle     * by looking into the session variables.
1750*d10b5556SXylle     *
1751*d10b5556SXylle     * @note This function switches to callback mode when needed.
1752*d10b5556SXylle     *
1753*d10b5556SXylle     * @return bool true when the user has already been authenticated; false otherwise.
1754*d10b5556SXylle     */
1755*d10b5556SXylle    private function _wasPreviouslyAuthenticated()
1756*d10b5556SXylle    {
1757*d10b5556SXylle        phpCAS::traceBegin();
1758*d10b5556SXylle
1759*d10b5556SXylle        if ( $this->_isCallbackMode() ) {
1760*d10b5556SXylle            // Rebroadcast the pgtIou and pgtId to all nodes
1761*d10b5556SXylle            if ($this->_rebroadcast&&!isset($_POST['rebroadcast'])) {
1762*d10b5556SXylle                $this->_rebroadcast(self::PGTIOU);
1763*d10b5556SXylle            }
1764*d10b5556SXylle            $this->_callback();
1765*d10b5556SXylle        }
1766*d10b5556SXylle
1767*d10b5556SXylle        $auth = false;
1768*d10b5556SXylle
1769*d10b5556SXylle        if ( $this->isProxy() ) {
1770*d10b5556SXylle            // CAS proxy: username and PGT must be present
1771*d10b5556SXylle            if ( $this->isSessionAuthenticated()
1772*d10b5556SXylle                && $this->getSessionValue('pgt')
1773*d10b5556SXylle            ) {
1774*d10b5556SXylle                // authentication already done
1775*d10b5556SXylle                $this->_setUser($this->getSessionValue('user'));
1776*d10b5556SXylle                if ($this->hasSessionValue('attributes')) {
1777*d10b5556SXylle                    $this->setAttributes($this->getSessionValue('attributes'));
1778*d10b5556SXylle                }
1779*d10b5556SXylle                $this->_setPGT($this->getSessionValue('pgt'));
1780*d10b5556SXylle                phpCAS::trace(
1781*d10b5556SXylle                    'user = `'.$this->getSessionValue('user').'\', PGT = `'
1782*d10b5556SXylle                    .$this->getSessionValue('pgt').'\''
1783*d10b5556SXylle                );
1784*d10b5556SXylle
1785*d10b5556SXylle                // Include the list of proxies
1786*d10b5556SXylle                if ($this->hasSessionValue('proxies')) {
1787*d10b5556SXylle                    $this->_setProxies($this->getSessionValue('proxies'));
1788*d10b5556SXylle                    phpCAS::trace(
1789*d10b5556SXylle                        'proxies = "'
1790*d10b5556SXylle                        .implode('", "', $this->getSessionValue('proxies')).'"'
1791*d10b5556SXylle                    );
1792*d10b5556SXylle                }
1793*d10b5556SXylle
1794*d10b5556SXylle                $auth = true;
1795*d10b5556SXylle            } elseif ( $this->isSessionAuthenticated()
1796*d10b5556SXylle                && !$this->getSessionValue('pgt')
1797*d10b5556SXylle            ) {
1798*d10b5556SXylle                // these two variables should be empty or not empty at the same time
1799*d10b5556SXylle                phpCAS::trace(
1800*d10b5556SXylle                    'username found (`'.$this->getSessionValue('user')
1801*d10b5556SXylle                    .'\') but PGT is empty'
1802*d10b5556SXylle                );
1803*d10b5556SXylle                // unset all tickets to enforce authentication
1804*d10b5556SXylle                $this->clearSessionValues();
1805*d10b5556SXylle                $this->setTicket('');
1806*d10b5556SXylle            } elseif ( !$this->isSessionAuthenticated()
1807*d10b5556SXylle                && $this->getSessionValue('pgt')
1808*d10b5556SXylle            ) {
1809*d10b5556SXylle                // these two variables should be empty or not empty at the same time
1810*d10b5556SXylle                phpCAS::trace(
1811*d10b5556SXylle                    'PGT found (`'.$this->getSessionValue('pgt')
1812*d10b5556SXylle                    .'\') but username is empty'
1813*d10b5556SXylle                );
1814*d10b5556SXylle                // unset all tickets to enforce authentication
1815*d10b5556SXylle                $this->clearSessionValues();
1816*d10b5556SXylle                $this->setTicket('');
1817*d10b5556SXylle            } else {
1818*d10b5556SXylle                phpCAS::trace('neither user nor PGT found');
1819*d10b5556SXylle            }
1820*d10b5556SXylle        } else {
1821*d10b5556SXylle            // `simple' CAS client (not a proxy): username must be present
1822*d10b5556SXylle            if ( $this->isSessionAuthenticated() ) {
1823*d10b5556SXylle                // authentication already done
1824*d10b5556SXylle                $this->_setUser($this->getSessionValue('user'));
1825*d10b5556SXylle                if ($this->hasSessionValue('attributes')) {
1826*d10b5556SXylle                    $this->setAttributes($this->getSessionValue('attributes'));
1827*d10b5556SXylle                }
1828*d10b5556SXylle                phpCAS::trace('user = `'.$this->getSessionValue('user').'\'');
1829*d10b5556SXylle
1830*d10b5556SXylle                // Include the list of proxies
1831*d10b5556SXylle                if ($this->hasSessionValue('proxies')) {
1832*d10b5556SXylle                    $this->_setProxies($this->getSessionValue('proxies'));
1833*d10b5556SXylle                    phpCAS::trace(
1834*d10b5556SXylle                        'proxies = "'
1835*d10b5556SXylle                        .implode('", "', $this->getSessionValue('proxies')).'"'
1836*d10b5556SXylle                    );
1837*d10b5556SXylle                }
1838*d10b5556SXylle
1839*d10b5556SXylle                $auth = true;
1840*d10b5556SXylle            } else {
1841*d10b5556SXylle                phpCAS::trace('no user found');
1842*d10b5556SXylle            }
1843*d10b5556SXylle        }
1844*d10b5556SXylle
1845*d10b5556SXylle        phpCAS::traceEnd($auth);
1846*d10b5556SXylle        return $auth;
1847*d10b5556SXylle    }
1848*d10b5556SXylle
1849*d10b5556SXylle    /**
1850*d10b5556SXylle     * This method is used to redirect the client to the CAS server.
1851*d10b5556SXylle     * It is used by CAS_Client::forceAuthentication() and
1852*d10b5556SXylle     * CAS_Client::checkAuthentication().
1853*d10b5556SXylle     *
1854*d10b5556SXylle     * @param bool $gateway true to check authentication, false to force it
1855*d10b5556SXylle     * @param bool $renew   true to force the authentication with the CAS server
1856*d10b5556SXylle     *
1857*d10b5556SXylle     * @return void
1858*d10b5556SXylle     */
1859*d10b5556SXylle    public function redirectToCas($gateway=false,$renew=false)
1860*d10b5556SXylle    {
1861*d10b5556SXylle        phpCAS::traceBegin();
1862*d10b5556SXylle        $cas_url = $this->getServerLoginURL($gateway, $renew);
1863*d10b5556SXylle        session_write_close();
1864*d10b5556SXylle        if (php_sapi_name() === 'cli') {
1865*d10b5556SXylle            @header('Location: '.$cas_url);
1866*d10b5556SXylle        } else {
1867*d10b5556SXylle            header('Location: '.$cas_url);
1868*d10b5556SXylle        }
1869*d10b5556SXylle        phpCAS::trace("Redirect to : ".$cas_url);
1870*d10b5556SXylle        $lang = $this->getLangObj();
1871*d10b5556SXylle        $this->printHTMLHeader($lang->getAuthenticationWanted());
1872*d10b5556SXylle        $this->printf('<p>'. $lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1873*d10b5556SXylle        $this->printHTMLFooter();
1874*d10b5556SXylle        phpCAS::traceExit();
1875*d10b5556SXylle        throw new CAS_GracefullTerminationException();
1876*d10b5556SXylle    }
1877*d10b5556SXylle
1878*d10b5556SXylle
1879*d10b5556SXylle    /**
1880*d10b5556SXylle     * This method is used to logout from CAS.
1881*d10b5556SXylle     *
1882*d10b5556SXylle     * @param array $params an array that contains the optional url and service
1883*d10b5556SXylle     * parameters that will be passed to the CAS server
1884*d10b5556SXylle     *
1885*d10b5556SXylle     * @return void
1886*d10b5556SXylle     */
1887*d10b5556SXylle    public function logout($params)
1888*d10b5556SXylle    {
1889*d10b5556SXylle        phpCAS::traceBegin();
1890*d10b5556SXylle        $cas_url = $this->getServerLogoutURL();
1891*d10b5556SXylle        $paramSeparator = '?';
1892*d10b5556SXylle        if (isset($params['url'])) {
1893*d10b5556SXylle            $cas_url = $cas_url . $paramSeparator . "url="
1894*d10b5556SXylle                . urlencode($params['url']);
1895*d10b5556SXylle            $paramSeparator = '&';
1896*d10b5556SXylle        }
1897*d10b5556SXylle        if (isset($params['service'])) {
1898*d10b5556SXylle            $cas_url = $cas_url . $paramSeparator . "service="
1899*d10b5556SXylle                . urlencode($params['service']);
1900*d10b5556SXylle        }
1901*d10b5556SXylle        header('Location: '.$cas_url);
1902*d10b5556SXylle        phpCAS::trace("Prepare redirect to : ".$cas_url);
1903*d10b5556SXylle
1904*d10b5556SXylle        phpCAS::trace("Destroying session : ".session_id());
1905*d10b5556SXylle        session_unset();
1906*d10b5556SXylle        session_destroy();
1907*d10b5556SXylle        if (session_status() === PHP_SESSION_NONE) {
1908*d10b5556SXylle            phpCAS::trace("Session terminated");
1909*d10b5556SXylle        } else {
1910*d10b5556SXylle            phpCAS::error("Session was not terminated");
1911*d10b5556SXylle            phpCAS::trace("Session was not terminated");
1912*d10b5556SXylle        }
1913*d10b5556SXylle        $lang = $this->getLangObj();
1914*d10b5556SXylle        $this->printHTMLHeader($lang->getLogout());
1915*d10b5556SXylle        $this->printf('<p>'.$lang->getShouldHaveBeenRedirected(). '</p>', $cas_url);
1916*d10b5556SXylle        $this->printHTMLFooter();
1917*d10b5556SXylle        phpCAS::traceExit();
1918*d10b5556SXylle        throw new CAS_GracefullTerminationException();
1919*d10b5556SXylle    }
1920*d10b5556SXylle
1921*d10b5556SXylle    /**
1922*d10b5556SXylle     * Check of the current request is a logout request
1923*d10b5556SXylle     *
1924*d10b5556SXylle     * @return bool is logout request.
1925*d10b5556SXylle     */
1926*d10b5556SXylle    private function _isLogoutRequest()
1927*d10b5556SXylle    {
1928*d10b5556SXylle        return !empty($_POST['logoutRequest']);
1929*d10b5556SXylle    }
1930*d10b5556SXylle
1931*d10b5556SXylle    /**
1932*d10b5556SXylle     * This method handles logout requests.
1933*d10b5556SXylle     *
1934*d10b5556SXylle     * @param bool $check_client    true to check the client bofore handling
1935*d10b5556SXylle     * the request, false not to perform any access control. True by default.
1936*d10b5556SXylle     * @param array $allowed_clients an array of host names allowed to send
1937*d10b5556SXylle     * logout requests.
1938*d10b5556SXylle     *
1939*d10b5556SXylle     * @return void
1940*d10b5556SXylle     */
1941*d10b5556SXylle    public function handleLogoutRequests($check_client=true, $allowed_clients=array())
1942*d10b5556SXylle    {
1943*d10b5556SXylle        phpCAS::traceBegin();
1944*d10b5556SXylle        if (!$this->_isLogoutRequest()) {
1945*d10b5556SXylle            phpCAS::trace("Not a logout request");
1946*d10b5556SXylle            phpCAS::traceEnd();
1947*d10b5556SXylle            return;
1948*d10b5556SXylle        }
1949*d10b5556SXylle        if (!$this->getChangeSessionID()
1950*d10b5556SXylle            && is_null($this->_signoutCallbackFunction)
1951*d10b5556SXylle        ) {
1952*d10b5556SXylle            phpCAS::trace(
1953*d10b5556SXylle                "phpCAS can't handle logout requests if it is not allowed to change session_id."
1954*d10b5556SXylle            );
1955*d10b5556SXylle        }
1956*d10b5556SXylle        phpCAS::trace("Logout requested");
1957*d10b5556SXylle        $decoded_logout_rq = urldecode($_POST['logoutRequest']);
1958*d10b5556SXylle        phpCAS::trace("SAML REQUEST: ".$decoded_logout_rq);
1959*d10b5556SXylle        $allowed = false;
1960*d10b5556SXylle        if ($check_client) {
1961*d10b5556SXylle            if ($allowed_clients === array()) {
1962*d10b5556SXylle                $allowed_clients = array( $this->_getServerHostname() );
1963*d10b5556SXylle            }
1964*d10b5556SXylle            $client_ip = $_SERVER['REMOTE_ADDR'];
1965*d10b5556SXylle            $client = gethostbyaddr($client_ip);
1966*d10b5556SXylle            phpCAS::trace("Client: ".$client."/".$client_ip);
1967*d10b5556SXylle            foreach ($allowed_clients as $allowed_client) {
1968*d10b5556SXylle                if (($client == $allowed_client)
1969*d10b5556SXylle                    || ($client_ip == $allowed_client)
1970*d10b5556SXylle                ) {
1971*d10b5556SXylle                    phpCAS::trace(
1972*d10b5556SXylle                        "Allowed client '".$allowed_client
1973*d10b5556SXylle                        ."' matches, logout request is allowed"
1974*d10b5556SXylle                    );
1975*d10b5556SXylle                    $allowed = true;
1976*d10b5556SXylle                    break;
1977*d10b5556SXylle                } else {
1978*d10b5556SXylle                    phpCAS::trace(
1979*d10b5556SXylle                        "Allowed client '".$allowed_client."' does not match"
1980*d10b5556SXylle                    );
1981*d10b5556SXylle                }
1982*d10b5556SXylle            }
1983*d10b5556SXylle        } else {
1984*d10b5556SXylle            phpCAS::trace("No access control set");
1985*d10b5556SXylle            $allowed = true;
1986*d10b5556SXylle        }
1987*d10b5556SXylle        // If Logout command is permitted proceed with the logout
1988*d10b5556SXylle        if ($allowed) {
1989*d10b5556SXylle            phpCAS::trace("Logout command allowed");
1990*d10b5556SXylle            // Rebroadcast the logout request
1991*d10b5556SXylle            if ($this->_rebroadcast && !isset($_POST['rebroadcast'])) {
1992*d10b5556SXylle                $this->_rebroadcast(self::LOGOUT);
1993*d10b5556SXylle            }
1994*d10b5556SXylle            // Extract the ticket from the SAML Request
1995*d10b5556SXylle            preg_match(
1996*d10b5556SXylle                "|<samlp:SessionIndex>(.*)</samlp:SessionIndex>|",
1997*d10b5556SXylle                $decoded_logout_rq, $tick, PREG_OFFSET_CAPTURE, 3
1998*d10b5556SXylle            );
1999*d10b5556SXylle            $wrappedSamlSessionIndex = preg_replace(
2000*d10b5556SXylle                '|<samlp:SessionIndex>|', '', $tick[0][0]
2001*d10b5556SXylle            );
2002*d10b5556SXylle            $ticket2logout = preg_replace(
2003*d10b5556SXylle                '|</samlp:SessionIndex>|', '', $wrappedSamlSessionIndex
2004*d10b5556SXylle            );
2005*d10b5556SXylle            phpCAS::trace("Ticket to logout: ".$ticket2logout);
2006*d10b5556SXylle
2007*d10b5556SXylle            // call the post-authenticate callback if registered.
2008*d10b5556SXylle            if ($this->_signoutCallbackFunction) {
2009*d10b5556SXylle                $args = $this->_signoutCallbackArgs;
2010*d10b5556SXylle                array_unshift($args, $ticket2logout);
2011*d10b5556SXylle                call_user_func_array($this->_signoutCallbackFunction, $args);
2012*d10b5556SXylle            }
2013*d10b5556SXylle
2014*d10b5556SXylle            // If phpCAS is managing the session_id, destroy session thanks to
2015*d10b5556SXylle            // session_id.
2016*d10b5556SXylle            if ($this->getChangeSessionID()) {
2017*d10b5556SXylle                $session_id = $this->_sessionIdForTicket($ticket2logout);
2018*d10b5556SXylle                phpCAS::trace("Session id: ".$session_id);
2019*d10b5556SXylle
2020*d10b5556SXylle                // destroy a possible application session created before phpcas
2021*d10b5556SXylle                if (session_id() !== "") {
2022*d10b5556SXylle                    session_unset();
2023*d10b5556SXylle                    session_destroy();
2024*d10b5556SXylle                }
2025*d10b5556SXylle                // fix session ID
2026*d10b5556SXylle                session_id($session_id);
2027*d10b5556SXylle                $_COOKIE[session_name()]=$session_id;
2028*d10b5556SXylle                $_GET[session_name()]=$session_id;
2029*d10b5556SXylle
2030*d10b5556SXylle                // Overwrite session
2031*d10b5556SXylle                session_start();
2032*d10b5556SXylle                session_unset();
2033*d10b5556SXylle                session_destroy();
2034*d10b5556SXylle                phpCAS::trace("Session ". $session_id . " destroyed");
2035*d10b5556SXylle            }
2036*d10b5556SXylle        } else {
2037*d10b5556SXylle            phpCAS::error("Unauthorized logout request from client '".$client."'");
2038*d10b5556SXylle            phpCAS::trace("Unauthorized logout request from client '".$client."'");
2039*d10b5556SXylle        }
2040*d10b5556SXylle        flush();
2041*d10b5556SXylle        phpCAS::traceExit();
2042*d10b5556SXylle        throw new CAS_GracefullTerminationException();
2043*d10b5556SXylle
2044*d10b5556SXylle    }
2045*d10b5556SXylle
2046*d10b5556SXylle    /** @} */
2047*d10b5556SXylle
2048*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2049*d10b5556SXylle    // XX                                                                    XX
2050*d10b5556SXylle    // XX                  BASIC CLIENT FEATURES (CAS 1.0)                   XX
2051*d10b5556SXylle    // XX                                                                    XX
2052*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2053*d10b5556SXylle
2054*d10b5556SXylle    // ########################################################################
2055*d10b5556SXylle    //  ST
2056*d10b5556SXylle    // ########################################################################
2057*d10b5556SXylle    /**
2058*d10b5556SXylle    * @addtogroup internalBasic
2059*d10b5556SXylle    * @{
2060*d10b5556SXylle    */
2061*d10b5556SXylle
2062*d10b5556SXylle    /**
2063*d10b5556SXylle     * The Ticket provided in the URL of the request if present
2064*d10b5556SXylle     * (empty otherwise). Written by CAS_Client::CAS_Client(), read by
2065*d10b5556SXylle     * CAS_Client::getTicket() and CAS_Client::_hasPGT().
2066*d10b5556SXylle     *
2067*d10b5556SXylle     * @hideinitializer
2068*d10b5556SXylle     */
2069*d10b5556SXylle    private $_ticket = '';
2070*d10b5556SXylle
2071*d10b5556SXylle    /**
2072*d10b5556SXylle     * This method returns the Service Ticket provided in the URL of the request.
2073*d10b5556SXylle     *
2074*d10b5556SXylle     * @return string service ticket.
2075*d10b5556SXylle     */
2076*d10b5556SXylle    public  function getTicket()
2077*d10b5556SXylle    {
2078*d10b5556SXylle        return $this->_ticket;
2079*d10b5556SXylle    }
2080*d10b5556SXylle
2081*d10b5556SXylle    /**
2082*d10b5556SXylle     * This method stores the Service Ticket.
2083*d10b5556SXylle     *
2084*d10b5556SXylle     * @param string $st The Service Ticket.
2085*d10b5556SXylle     *
2086*d10b5556SXylle     * @return void
2087*d10b5556SXylle     */
2088*d10b5556SXylle    public function setTicket($st)
2089*d10b5556SXylle    {
2090*d10b5556SXylle        $this->_ticket = $st;
2091*d10b5556SXylle    }
2092*d10b5556SXylle
2093*d10b5556SXylle    /**
2094*d10b5556SXylle     * This method tells if a Service Ticket was stored.
2095*d10b5556SXylle     *
2096*d10b5556SXylle     * @return bool if a Service Ticket has been stored.
2097*d10b5556SXylle     */
2098*d10b5556SXylle    public function hasTicket()
2099*d10b5556SXylle    {
2100*d10b5556SXylle        return !empty($this->_ticket);
2101*d10b5556SXylle    }
2102*d10b5556SXylle
2103*d10b5556SXylle    /** @} */
2104*d10b5556SXylle
2105*d10b5556SXylle    // ########################################################################
2106*d10b5556SXylle    //  ST VALIDATION
2107*d10b5556SXylle    // ########################################################################
2108*d10b5556SXylle    /**
2109*d10b5556SXylle    * @addtogroup internalBasic
2110*d10b5556SXylle    * @{
2111*d10b5556SXylle    */
2112*d10b5556SXylle
2113*d10b5556SXylle    /**
2114*d10b5556SXylle     * @var  string the certificate of the CAS server CA.
2115*d10b5556SXylle     *
2116*d10b5556SXylle     * @hideinitializer
2117*d10b5556SXylle     */
2118*d10b5556SXylle    private $_cas_server_ca_cert = null;
2119*d10b5556SXylle
2120*d10b5556SXylle
2121*d10b5556SXylle    /**
2122*d10b5556SXylle
2123*d10b5556SXylle     * validate CN of the CAS server certificate
2124*d10b5556SXylle
2125*d10b5556SXylle     *
2126*d10b5556SXylle
2127*d10b5556SXylle     * @hideinitializer
2128*d10b5556SXylle
2129*d10b5556SXylle     */
2130*d10b5556SXylle
2131*d10b5556SXylle    private $_cas_server_cn_validate = true;
2132*d10b5556SXylle
2133*d10b5556SXylle    /**
2134*d10b5556SXylle     * Set to true not to validate the CAS server.
2135*d10b5556SXylle     *
2136*d10b5556SXylle     * @hideinitializer
2137*d10b5556SXylle     */
2138*d10b5556SXylle    private $_no_cas_server_validation = false;
2139*d10b5556SXylle
2140*d10b5556SXylle
2141*d10b5556SXylle    /**
2142*d10b5556SXylle     * Set the CA certificate of the CAS server.
2143*d10b5556SXylle     *
2144*d10b5556SXylle     * @param string $cert        the PEM certificate file name of the CA that emited
2145*d10b5556SXylle     * the cert of the server
2146*d10b5556SXylle     * @param bool   $validate_cn valiate CN of the CAS server certificate
2147*d10b5556SXylle     *
2148*d10b5556SXylle     * @return void
2149*d10b5556SXylle     */
2150*d10b5556SXylle    public function setCasServerCACert($cert, $validate_cn)
2151*d10b5556SXylle    {
2152*d10b5556SXylle    // Argument validation
2153*d10b5556SXylle        if (gettype($cert) != 'string') {
2154*d10b5556SXylle            throw new CAS_TypeMismatchException($cert, '$cert', 'string');
2155*d10b5556SXylle        }
2156*d10b5556SXylle        if (gettype($validate_cn) != 'boolean') {
2157*d10b5556SXylle            throw new CAS_TypeMismatchException($validate_cn, '$validate_cn', 'boolean');
2158*d10b5556SXylle        }
2159*d10b5556SXylle        if (!file_exists($cert)) {
2160*d10b5556SXylle            throw new CAS_InvalidArgumentException("Certificate file does not exist " . $this->_requestImplementation);
2161*d10b5556SXylle        }
2162*d10b5556SXylle        $this->_cas_server_ca_cert = $cert;
2163*d10b5556SXylle        $this->_cas_server_cn_validate = $validate_cn;
2164*d10b5556SXylle    }
2165*d10b5556SXylle
2166*d10b5556SXylle    /**
2167*d10b5556SXylle     * Set no SSL validation for the CAS server.
2168*d10b5556SXylle     *
2169*d10b5556SXylle     * @return void
2170*d10b5556SXylle     */
2171*d10b5556SXylle    public function setNoCasServerValidation()
2172*d10b5556SXylle    {
2173*d10b5556SXylle        $this->_no_cas_server_validation = true;
2174*d10b5556SXylle    }
2175*d10b5556SXylle
2176*d10b5556SXylle    /**
2177*d10b5556SXylle     * This method is used to validate a CAS 1,0 ticket; halt on failure, and
2178*d10b5556SXylle     * sets $validate_url, $text_reponse and $tree_response on success.
2179*d10b5556SXylle     *
2180*d10b5556SXylle     * @param string &$validate_url  reference to the the URL of the request to
2181*d10b5556SXylle     * the CAS server.
2182*d10b5556SXylle     * @param string &$text_response reference to the response of the CAS
2183*d10b5556SXylle     * server, as is (XML text).
2184*d10b5556SXylle     * @param string &$tree_response reference to the response of the CAS
2185*d10b5556SXylle     * server, as a DOM XML tree.
2186*d10b5556SXylle     * @param bool   $renew          true to force the authentication with the CAS server
2187*d10b5556SXylle     *
2188*d10b5556SXylle     * @return bool true when successfull and issue a CAS_AuthenticationException
2189*d10b5556SXylle     * and false on an error
2190*d10b5556SXylle     * @throws  CAS_AuthenticationException
2191*d10b5556SXylle     */
2192*d10b5556SXylle    public function validateCAS10(&$validate_url,&$text_response,&$tree_response,$renew=false)
2193*d10b5556SXylle    {
2194*d10b5556SXylle        phpCAS::traceBegin();
2195*d10b5556SXylle        // build the URL to validate the ticket
2196*d10b5556SXylle        $validate_url = $this->getServerServiceValidateURL()
2197*d10b5556SXylle            .'&ticket='.urlencode($this->getTicket());
2198*d10b5556SXylle
2199*d10b5556SXylle        if ( $renew ) {
2200*d10b5556SXylle            // pass the renew
2201*d10b5556SXylle            $validate_url .= '&renew=true';
2202*d10b5556SXylle        }
2203*d10b5556SXylle
2204*d10b5556SXylle        $headers = '';
2205*d10b5556SXylle        $err_msg = '';
2206*d10b5556SXylle        // open and read the URL
2207*d10b5556SXylle        if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
2208*d10b5556SXylle            phpCAS::trace(
2209*d10b5556SXylle                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
2210*d10b5556SXylle            );
2211*d10b5556SXylle            throw new CAS_AuthenticationException(
2212*d10b5556SXylle                $this, 'CAS 1.0 ticket not validated', $validate_url,
2213*d10b5556SXylle                true/*$no_response*/
2214*d10b5556SXylle            );
2215*d10b5556SXylle        }
2216*d10b5556SXylle
2217*d10b5556SXylle        if (preg_match('/^no\n/', $text_response)) {
2218*d10b5556SXylle            phpCAS::trace('Ticket has not been validated');
2219*d10b5556SXylle            throw new CAS_AuthenticationException(
2220*d10b5556SXylle                $this, 'ST not validated', $validate_url, false/*$no_response*/,
2221*d10b5556SXylle                false/*$bad_response*/, $text_response
2222*d10b5556SXylle            );
2223*d10b5556SXylle        } else if (!preg_match('/^yes\n/', $text_response)) {
2224*d10b5556SXylle            phpCAS::trace('ill-formed response');
2225*d10b5556SXylle            throw new CAS_AuthenticationException(
2226*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
2227*d10b5556SXylle                false/*$no_response*/, true/*$bad_response*/, $text_response
2228*d10b5556SXylle            );
2229*d10b5556SXylle        }
2230*d10b5556SXylle        // ticket has been validated, extract the user name
2231*d10b5556SXylle        $arr = preg_split('/\n/', $text_response);
2232*d10b5556SXylle        $this->_setUser(trim($arr[1]));
2233*d10b5556SXylle
2234*d10b5556SXylle        $this->_renameSession($this->getTicket());
2235*d10b5556SXylle
2236*d10b5556SXylle        // at this step, ticket has been validated and $this->_user has been set,
2237*d10b5556SXylle        phpCAS::traceEnd(true);
2238*d10b5556SXylle        return true;
2239*d10b5556SXylle    }
2240*d10b5556SXylle
2241*d10b5556SXylle    /** @} */
2242*d10b5556SXylle
2243*d10b5556SXylle
2244*d10b5556SXylle    // ########################################################################
2245*d10b5556SXylle    //  SAML VALIDATION
2246*d10b5556SXylle    // ########################################################################
2247*d10b5556SXylle    /**
2248*d10b5556SXylle    * @addtogroup internalSAML
2249*d10b5556SXylle    * @{
2250*d10b5556SXylle    */
2251*d10b5556SXylle
2252*d10b5556SXylle    /**
2253*d10b5556SXylle     * This method is used to validate a SAML TICKET; halt on failure, and sets
2254*d10b5556SXylle     * $validate_url, $text_reponse and $tree_response on success. These
2255*d10b5556SXylle     * parameters are used later by CAS_Client::_validatePGT() for CAS proxies.
2256*d10b5556SXylle     *
2257*d10b5556SXylle     * @param string &$validate_url  reference to the the URL of the request to
2258*d10b5556SXylle     * the CAS server.
2259*d10b5556SXylle     * @param string &$text_response reference to the response of the CAS
2260*d10b5556SXylle     * server, as is (XML text).
2261*d10b5556SXylle     * @param string &$tree_response reference to the response of the CAS
2262*d10b5556SXylle     * server, as a DOM XML tree.
2263*d10b5556SXylle     * @param bool   $renew          true to force the authentication with the CAS server
2264*d10b5556SXylle     *
2265*d10b5556SXylle     * @return bool true when successfull and issue a CAS_AuthenticationException
2266*d10b5556SXylle     * and false on an error
2267*d10b5556SXylle     *
2268*d10b5556SXylle     * @throws  CAS_AuthenticationException
2269*d10b5556SXylle     */
2270*d10b5556SXylle    public function validateSA(&$validate_url,&$text_response,&$tree_response,$renew=false)
2271*d10b5556SXylle    {
2272*d10b5556SXylle        phpCAS::traceBegin();
2273*d10b5556SXylle        $result = false;
2274*d10b5556SXylle        // build the URL to validate the ticket
2275*d10b5556SXylle        $validate_url = $this->getServerSamlValidateURL();
2276*d10b5556SXylle
2277*d10b5556SXylle        if ( $renew ) {
2278*d10b5556SXylle            // pass the renew
2279*d10b5556SXylle            $validate_url .= '&renew=true';
2280*d10b5556SXylle        }
2281*d10b5556SXylle
2282*d10b5556SXylle        $headers = '';
2283*d10b5556SXylle        $err_msg = '';
2284*d10b5556SXylle        // open and read the URL
2285*d10b5556SXylle        if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
2286*d10b5556SXylle            phpCAS::trace(
2287*d10b5556SXylle                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
2288*d10b5556SXylle            );
2289*d10b5556SXylle            throw new CAS_AuthenticationException(
2290*d10b5556SXylle                $this, 'SA not validated', $validate_url, true/*$no_response*/
2291*d10b5556SXylle            );
2292*d10b5556SXylle        }
2293*d10b5556SXylle
2294*d10b5556SXylle        phpCAS::trace('server version: '.$this->getServerVersion());
2295*d10b5556SXylle
2296*d10b5556SXylle        // analyze the result depending on the version
2297*d10b5556SXylle        switch ($this->getServerVersion()) {
2298*d10b5556SXylle        case SAML_VERSION_1_1:
2299*d10b5556SXylle            // create new DOMDocument Object
2300*d10b5556SXylle            $dom = new DOMDocument();
2301*d10b5556SXylle            // Fix possible whitspace problems
2302*d10b5556SXylle            $dom->preserveWhiteSpace = false;
2303*d10b5556SXylle            // read the response of the CAS server into a DOM object
2304*d10b5556SXylle            if (!($dom->loadXML($text_response))) {
2305*d10b5556SXylle                phpCAS::trace('dom->loadXML() failed');
2306*d10b5556SXylle                throw new CAS_AuthenticationException(
2307*d10b5556SXylle                    $this, 'SA not validated', $validate_url,
2308*d10b5556SXylle                    false/*$no_response*/, true/*$bad_response*/,
2309*d10b5556SXylle                    $text_response
2310*d10b5556SXylle                );
2311*d10b5556SXylle            }
2312*d10b5556SXylle            // read the root node of the XML tree
2313*d10b5556SXylle            if (!($tree_response = $dom->documentElement)) {
2314*d10b5556SXylle                phpCAS::trace('documentElement() failed');
2315*d10b5556SXylle                throw new CAS_AuthenticationException(
2316*d10b5556SXylle                    $this, 'SA not validated', $validate_url,
2317*d10b5556SXylle                    false/*$no_response*/, true/*$bad_response*/,
2318*d10b5556SXylle                    $text_response
2319*d10b5556SXylle                );
2320*d10b5556SXylle            } else if ( $tree_response->localName != 'Envelope' ) {
2321*d10b5556SXylle                // insure that tag name is 'Envelope'
2322*d10b5556SXylle                phpCAS::trace(
2323*d10b5556SXylle                    'bad XML root node (should be `Envelope\' instead of `'
2324*d10b5556SXylle                    .$tree_response->localName.'\''
2325*d10b5556SXylle                );
2326*d10b5556SXylle                throw new CAS_AuthenticationException(
2327*d10b5556SXylle                    $this, 'SA not validated', $validate_url,
2328*d10b5556SXylle                    false/*$no_response*/, true/*$bad_response*/,
2329*d10b5556SXylle                    $text_response
2330*d10b5556SXylle                );
2331*d10b5556SXylle            } else if ($tree_response->getElementsByTagName("NameIdentifier")->length != 0) {
2332*d10b5556SXylle                // check for the NameIdentifier tag in the SAML response
2333*d10b5556SXylle                $success_elements = $tree_response->getElementsByTagName("NameIdentifier");
2334*d10b5556SXylle                phpCAS::trace('NameIdentifier found');
2335*d10b5556SXylle                $user = trim($success_elements->item(0)->nodeValue);
2336*d10b5556SXylle                phpCAS::trace('user = `'.$user.'`');
2337*d10b5556SXylle                $this->_setUser($user);
2338*d10b5556SXylle                $this->_setSessionAttributes($text_response);
2339*d10b5556SXylle                $result = true;
2340*d10b5556SXylle            } else {
2341*d10b5556SXylle                phpCAS::trace('no <NameIdentifier> tag found in SAML payload');
2342*d10b5556SXylle                throw new CAS_AuthenticationException(
2343*d10b5556SXylle                    $this, 'SA not validated', $validate_url,
2344*d10b5556SXylle                    false/*$no_response*/, true/*$bad_response*/,
2345*d10b5556SXylle                    $text_response
2346*d10b5556SXylle                );
2347*d10b5556SXylle            }
2348*d10b5556SXylle        }
2349*d10b5556SXylle        if ($result) {
2350*d10b5556SXylle            $this->_renameSession($this->getTicket());
2351*d10b5556SXylle        }
2352*d10b5556SXylle        // at this step, ST has been validated and $this->_user has been set,
2353*d10b5556SXylle        phpCAS::traceEnd($result);
2354*d10b5556SXylle        return $result;
2355*d10b5556SXylle    }
2356*d10b5556SXylle
2357*d10b5556SXylle    /**
2358*d10b5556SXylle     * This method will parse the DOM and pull out the attributes from the SAML
2359*d10b5556SXylle     * payload and put them into an array, then put the array into the session.
2360*d10b5556SXylle     *
2361*d10b5556SXylle     * @param string $text_response the SAML payload.
2362*d10b5556SXylle     *
2363*d10b5556SXylle     * @return bool true when successfull and false if no attributes a found
2364*d10b5556SXylle     */
2365*d10b5556SXylle    private function _setSessionAttributes($text_response)
2366*d10b5556SXylle    {
2367*d10b5556SXylle        phpCAS::traceBegin();
2368*d10b5556SXylle
2369*d10b5556SXylle        $result = false;
2370*d10b5556SXylle
2371*d10b5556SXylle        $attr_array = array();
2372*d10b5556SXylle
2373*d10b5556SXylle        // create new DOMDocument Object
2374*d10b5556SXylle        $dom = new DOMDocument();
2375*d10b5556SXylle        // Fix possible whitspace problems
2376*d10b5556SXylle        $dom->preserveWhiteSpace = false;
2377*d10b5556SXylle        if (($dom->loadXML($text_response))) {
2378*d10b5556SXylle            $xPath = new DOMXPath($dom);
2379*d10b5556SXylle            $xPath->registerNamespace('samlp', 'urn:oasis:names:tc:SAML:1.0:protocol');
2380*d10b5556SXylle            $xPath->registerNamespace('saml', 'urn:oasis:names:tc:SAML:1.0:assertion');
2381*d10b5556SXylle            $nodelist = $xPath->query("//saml:Attribute");
2382*d10b5556SXylle
2383*d10b5556SXylle            if ($nodelist) {
2384*d10b5556SXylle                foreach ($nodelist as $node) {
2385*d10b5556SXylle                    $xres = $xPath->query("saml:AttributeValue", $node);
2386*d10b5556SXylle                    $name = $node->getAttribute("AttributeName");
2387*d10b5556SXylle                    $value_array = array();
2388*d10b5556SXylle                    foreach ($xres as $node2) {
2389*d10b5556SXylle                        $value_array[] = $node2->nodeValue;
2390*d10b5556SXylle                    }
2391*d10b5556SXylle                    $attr_array[$name] = $value_array;
2392*d10b5556SXylle                }
2393*d10b5556SXylle                // UGent addition...
2394*d10b5556SXylle                foreach ($attr_array as $attr_key => $attr_value) {
2395*d10b5556SXylle                    if (count($attr_value) > 1) {
2396*d10b5556SXylle                        $this->_attributes[$attr_key] = $attr_value;
2397*d10b5556SXylle                        phpCAS::trace("* " . $attr_key . "=" . print_r($attr_value, true));
2398*d10b5556SXylle                    } else {
2399*d10b5556SXylle                        $this->_attributes[$attr_key] = $attr_value[0];
2400*d10b5556SXylle                        phpCAS::trace("* " . $attr_key . "=" . $attr_value[0]);
2401*d10b5556SXylle                    }
2402*d10b5556SXylle                }
2403*d10b5556SXylle                $result = true;
2404*d10b5556SXylle            } else {
2405*d10b5556SXylle                phpCAS::trace("SAML Attributes are empty");
2406*d10b5556SXylle                $result = false;
2407*d10b5556SXylle            }
2408*d10b5556SXylle        }
2409*d10b5556SXylle        phpCAS::traceEnd($result);
2410*d10b5556SXylle        return $result;
2411*d10b5556SXylle    }
2412*d10b5556SXylle
2413*d10b5556SXylle    /** @} */
2414*d10b5556SXylle
2415*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2416*d10b5556SXylle    // XX                                                                    XX
2417*d10b5556SXylle    // XX                     PROXY FEATURES (CAS 2.0)                       XX
2418*d10b5556SXylle    // XX                                                                    XX
2419*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
2420*d10b5556SXylle
2421*d10b5556SXylle    // ########################################################################
2422*d10b5556SXylle    //  PROXYING
2423*d10b5556SXylle    // ########################################################################
2424*d10b5556SXylle    /**
2425*d10b5556SXylle    * @addtogroup internalProxy
2426*d10b5556SXylle    * @{
2427*d10b5556SXylle    */
2428*d10b5556SXylle
2429*d10b5556SXylle    /**
2430*d10b5556SXylle     * @var  bool is the client a proxy
2431*d10b5556SXylle     * A boolean telling if the client is a CAS proxy or not. Written by
2432*d10b5556SXylle     * CAS_Client::CAS_Client(), read by CAS_Client::isProxy().
2433*d10b5556SXylle     */
2434*d10b5556SXylle    private $_proxy;
2435*d10b5556SXylle
2436*d10b5556SXylle    /**
2437*d10b5556SXylle     * @var  CAS_CookieJar Handler for managing service cookies.
2438*d10b5556SXylle     */
2439*d10b5556SXylle    private $_serviceCookieJar;
2440*d10b5556SXylle
2441*d10b5556SXylle    /**
2442*d10b5556SXylle     * Tells if a CAS client is a CAS proxy or not
2443*d10b5556SXylle     *
2444*d10b5556SXylle     * @return bool true when the CAS client is a CAS proxy, false otherwise
2445*d10b5556SXylle     */
2446*d10b5556SXylle    public function isProxy()
2447*d10b5556SXylle    {
2448*d10b5556SXylle        return $this->_proxy;
2449*d10b5556SXylle    }
2450*d10b5556SXylle
2451*d10b5556SXylle
2452*d10b5556SXylle    /** @} */
2453*d10b5556SXylle    // ########################################################################
2454*d10b5556SXylle    //  PGT
2455*d10b5556SXylle    // ########################################################################
2456*d10b5556SXylle    /**
2457*d10b5556SXylle    * @addtogroup internalProxy
2458*d10b5556SXylle    * @{
2459*d10b5556SXylle    */
2460*d10b5556SXylle
2461*d10b5556SXylle    /**
2462*d10b5556SXylle     * the Proxy Grnting Ticket given by the CAS server (empty otherwise).
2463*d10b5556SXylle     * Written by CAS_Client::_setPGT(), read by CAS_Client::_getPGT() and
2464*d10b5556SXylle     * CAS_Client::_hasPGT().
2465*d10b5556SXylle     *
2466*d10b5556SXylle     * @hideinitializer
2467*d10b5556SXylle     */
2468*d10b5556SXylle    private $_pgt = '';
2469*d10b5556SXylle
2470*d10b5556SXylle    /**
2471*d10b5556SXylle     * This method returns the Proxy Granting Ticket given by the CAS server.
2472*d10b5556SXylle     *
2473*d10b5556SXylle     * @return string the Proxy Granting Ticket.
2474*d10b5556SXylle     */
2475*d10b5556SXylle    private function _getPGT()
2476*d10b5556SXylle    {
2477*d10b5556SXylle        return $this->_pgt;
2478*d10b5556SXylle    }
2479*d10b5556SXylle
2480*d10b5556SXylle    /**
2481*d10b5556SXylle     * This method stores the Proxy Granting Ticket.
2482*d10b5556SXylle     *
2483*d10b5556SXylle     * @param string $pgt The Proxy Granting Ticket.
2484*d10b5556SXylle     *
2485*d10b5556SXylle     * @return void
2486*d10b5556SXylle     */
2487*d10b5556SXylle    private function _setPGT($pgt)
2488*d10b5556SXylle    {
2489*d10b5556SXylle        $this->_pgt = $pgt;
2490*d10b5556SXylle    }
2491*d10b5556SXylle
2492*d10b5556SXylle    /**
2493*d10b5556SXylle     * This method tells if a Proxy Granting Ticket was stored.
2494*d10b5556SXylle     *
2495*d10b5556SXylle     * @return bool true if a Proxy Granting Ticket has been stored.
2496*d10b5556SXylle     */
2497*d10b5556SXylle    private function _hasPGT()
2498*d10b5556SXylle    {
2499*d10b5556SXylle        return !empty($this->_pgt);
2500*d10b5556SXylle    }
2501*d10b5556SXylle
2502*d10b5556SXylle    /** @} */
2503*d10b5556SXylle
2504*d10b5556SXylle    // ########################################################################
2505*d10b5556SXylle    //  CALLBACK MODE
2506*d10b5556SXylle    // ########################################################################
2507*d10b5556SXylle    /**
2508*d10b5556SXylle    * @addtogroup internalCallback
2509*d10b5556SXylle    * @{
2510*d10b5556SXylle    */
2511*d10b5556SXylle    /**
2512*d10b5556SXylle     * each PHP script using phpCAS in proxy mode is its own callback to get the
2513*d10b5556SXylle     * PGT back from the CAS server. callback_mode is detected by the constructor
2514*d10b5556SXylle     * thanks to the GET parameters.
2515*d10b5556SXylle     */
2516*d10b5556SXylle
2517*d10b5556SXylle    /**
2518*d10b5556SXylle     * @var bool a boolean to know if the CAS client is running in callback mode. Written by
2519*d10b5556SXylle     * CAS_Client::setCallBackMode(), read by CAS_Client::_isCallbackMode().
2520*d10b5556SXylle     *
2521*d10b5556SXylle     * @hideinitializer
2522*d10b5556SXylle     */
2523*d10b5556SXylle    private $_callback_mode = false;
2524*d10b5556SXylle
2525*d10b5556SXylle    /**
2526*d10b5556SXylle     * This method sets/unsets callback mode.
2527*d10b5556SXylle     *
2528*d10b5556SXylle     * @param bool $callback_mode true to set callback mode, false otherwise.
2529*d10b5556SXylle     *
2530*d10b5556SXylle     * @return void
2531*d10b5556SXylle     */
2532*d10b5556SXylle    private function _setCallbackMode($callback_mode)
2533*d10b5556SXylle    {
2534*d10b5556SXylle        $this->_callback_mode = $callback_mode;
2535*d10b5556SXylle    }
2536*d10b5556SXylle
2537*d10b5556SXylle    /**
2538*d10b5556SXylle     * This method returns true when the CAS client is running in callback mode,
2539*d10b5556SXylle     * false otherwise.
2540*d10b5556SXylle     *
2541*d10b5556SXylle     * @return bool A boolean.
2542*d10b5556SXylle     */
2543*d10b5556SXylle    private function _isCallbackMode()
2544*d10b5556SXylle    {
2545*d10b5556SXylle        return $this->_callback_mode;
2546*d10b5556SXylle    }
2547*d10b5556SXylle
2548*d10b5556SXylle    /**
2549*d10b5556SXylle     * @var bool a boolean to know if the CAS client is using POST parameters when in callback mode.
2550*d10b5556SXylle     * Written by CAS_Client::_setCallbackModeUsingPost(), read by CAS_Client::_isCallbackModeUsingPost().
2551*d10b5556SXylle     *
2552*d10b5556SXylle     * @hideinitializer
2553*d10b5556SXylle     */
2554*d10b5556SXylle    private $_callback_mode_using_post = false;
2555*d10b5556SXylle
2556*d10b5556SXylle    /**
2557*d10b5556SXylle     * This method sets/unsets usage of POST parameters in callback mode (default/false is GET parameters)
2558*d10b5556SXylle     *
2559*d10b5556SXylle     * @param bool $callback_mode_using_post true to use POST, false to use GET (default).
2560*d10b5556SXylle     *
2561*d10b5556SXylle     * @return void
2562*d10b5556SXylle     */
2563*d10b5556SXylle    private function _setCallbackModeUsingPost($callback_mode_using_post)
2564*d10b5556SXylle    {
2565*d10b5556SXylle        $this->_callback_mode_using_post = $callback_mode_using_post;
2566*d10b5556SXylle    }
2567*d10b5556SXylle
2568*d10b5556SXylle    /**
2569*d10b5556SXylle     * This method returns true when the callback mode is using POST, false otherwise.
2570*d10b5556SXylle     *
2571*d10b5556SXylle     * @return bool A boolean.
2572*d10b5556SXylle     */
2573*d10b5556SXylle    private function _isCallbackModeUsingPost()
2574*d10b5556SXylle    {
2575*d10b5556SXylle        return $this->_callback_mode_using_post;
2576*d10b5556SXylle    }
2577*d10b5556SXylle
2578*d10b5556SXylle    /**
2579*d10b5556SXylle     * the URL that should be used for the PGT callback (in fact the URL of the
2580*d10b5556SXylle     * current request without any CGI parameter). Written and read by
2581*d10b5556SXylle     * CAS_Client::_getCallbackURL().
2582*d10b5556SXylle     *
2583*d10b5556SXylle     * @hideinitializer
2584*d10b5556SXylle     */
2585*d10b5556SXylle    private $_callback_url = '';
2586*d10b5556SXylle
2587*d10b5556SXylle    /**
2588*d10b5556SXylle     * This method returns the URL that should be used for the PGT callback (in
2589*d10b5556SXylle     * fact the URL of the current request without any CGI parameter, except if
2590*d10b5556SXylle     * phpCAS::setFixedCallbackURL() was used).
2591*d10b5556SXylle     *
2592*d10b5556SXylle     * @return string The callback URL
2593*d10b5556SXylle     */
2594*d10b5556SXylle    private function _getCallbackURL()
2595*d10b5556SXylle    {
2596*d10b5556SXylle        // the URL is built when needed only
2597*d10b5556SXylle        if ( empty($this->_callback_url) ) {
2598*d10b5556SXylle            // remove the ticket if present in the URL
2599*d10b5556SXylle            $final_uri = $this->getServiceBaseUrl()->get();
2600*d10b5556SXylle            $request_uri = $_SERVER['REQUEST_URI'];
2601*d10b5556SXylle            $request_uri = preg_replace('/\?.*$/', '', $request_uri);
2602*d10b5556SXylle            $final_uri .= $request_uri;
2603*d10b5556SXylle            $this->_callback_url = $final_uri;
2604*d10b5556SXylle        }
2605*d10b5556SXylle        return $this->_callback_url;
2606*d10b5556SXylle    }
2607*d10b5556SXylle
2608*d10b5556SXylle    /**
2609*d10b5556SXylle     * This method sets the callback url.
2610*d10b5556SXylle     *
2611*d10b5556SXylle     * @param string $url url to set callback
2612*d10b5556SXylle     *
2613*d10b5556SXylle     * @return string the callback url
2614*d10b5556SXylle     */
2615*d10b5556SXylle    public function setCallbackURL($url)
2616*d10b5556SXylle    {
2617*d10b5556SXylle        // Sequence validation
2618*d10b5556SXylle        $this->ensureIsProxy();
2619*d10b5556SXylle        // Argument Validation
2620*d10b5556SXylle        if (gettype($url) != 'string')
2621*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
2622*d10b5556SXylle
2623*d10b5556SXylle        return $this->_callback_url = $url;
2624*d10b5556SXylle    }
2625*d10b5556SXylle
2626*d10b5556SXylle    /**
2627*d10b5556SXylle     * This method is called by CAS_Client::CAS_Client() when running in callback
2628*d10b5556SXylle     * mode. It stores the PGT and its PGT Iou, prints its output and halts.
2629*d10b5556SXylle     *
2630*d10b5556SXylle     * @return void
2631*d10b5556SXylle     */
2632*d10b5556SXylle    private function _callback()
2633*d10b5556SXylle    {
2634*d10b5556SXylle        phpCAS::traceBegin();
2635*d10b5556SXylle        if ($this->_isCallbackModeUsingPost()) {
2636*d10b5556SXylle            $pgtId = $_POST['pgtId'];
2637*d10b5556SXylle            $pgtIou = $_POST['pgtIou'];
2638*d10b5556SXylle        } else {
2639*d10b5556SXylle            $pgtId = $_GET['pgtId'];
2640*d10b5556SXylle            $pgtIou = $_GET['pgtIou'];
2641*d10b5556SXylle        }
2642*d10b5556SXylle        if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgtIou)) {
2643*d10b5556SXylle            if (preg_match('/^[PT]GT-[\.\-\w]+$/', $pgtId)) {
2644*d10b5556SXylle                phpCAS::trace('Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\')');
2645*d10b5556SXylle                $this->_storePGT($pgtId, $pgtIou);
2646*d10b5556SXylle                if ($this->isXmlResponse()) {
2647*d10b5556SXylle                    echo '<?xml version="1.0" encoding="UTF-8"?>' . "\r\n";
2648*d10b5556SXylle                    echo '<proxySuccess xmlns="http://www.yale.edu/tp/cas" />';
2649*d10b5556SXylle                    phpCAS::traceExit("XML response sent");
2650*d10b5556SXylle                } else {
2651*d10b5556SXylle                    $this->printHTMLHeader('phpCAS callback');
2652*d10b5556SXylle                    echo '<p>Storing PGT `'.$pgtId.'\' (id=`'.$pgtIou.'\').</p>';
2653*d10b5556SXylle                    $this->printHTMLFooter();
2654*d10b5556SXylle                    phpCAS::traceExit("HTML response sent");
2655*d10b5556SXylle                }
2656*d10b5556SXylle                phpCAS::traceExit("Successfull Callback");
2657*d10b5556SXylle            } else {
2658*d10b5556SXylle                phpCAS::error('PGT format invalid' . $pgtId);
2659*d10b5556SXylle                phpCAS::traceExit('PGT format invalid' . $pgtId);
2660*d10b5556SXylle            }
2661*d10b5556SXylle        } else {
2662*d10b5556SXylle            phpCAS::error('PGTiou format invalid' . $pgtIou);
2663*d10b5556SXylle            phpCAS::traceExit('PGTiou format invalid' . $pgtIou);
2664*d10b5556SXylle        }
2665*d10b5556SXylle
2666*d10b5556SXylle        // Flush the buffer to prevent from sending anything other then a 200
2667*d10b5556SXylle        // Success Status back to the CAS Server. The Exception would normally
2668*d10b5556SXylle        // report as a 500 error.
2669*d10b5556SXylle        flush();
2670*d10b5556SXylle        throw new CAS_GracefullTerminationException();
2671*d10b5556SXylle    }
2672*d10b5556SXylle
2673*d10b5556SXylle    /**
2674*d10b5556SXylle     * Check if application/xml or text/xml is pressent in HTTP_ACCEPT header values
2675*d10b5556SXylle     * when return value is complex and contains attached q parameters.
2676*d10b5556SXylle     * Example:  HTTP_ACCEPT = text/html,application/xhtml+xml,application/xml;q=0.9
2677*d10b5556SXylle     * @return bool
2678*d10b5556SXylle     */
2679*d10b5556SXylle    private function isXmlResponse()
2680*d10b5556SXylle    {
2681*d10b5556SXylle        if (!array_key_exists('HTTP_ACCEPT', $_SERVER)) {
2682*d10b5556SXylle            return false;
2683*d10b5556SXylle        }
2684*d10b5556SXylle        if (strpos($_SERVER['HTTP_ACCEPT'], 'application/xml') === false && strpos($_SERVER['HTTP_ACCEPT'], 'text/xml') === false) {
2685*d10b5556SXylle            return false;
2686*d10b5556SXylle        }
2687*d10b5556SXylle
2688*d10b5556SXylle        return true;
2689*d10b5556SXylle    }
2690*d10b5556SXylle
2691*d10b5556SXylle    /** @} */
2692*d10b5556SXylle
2693*d10b5556SXylle    // ########################################################################
2694*d10b5556SXylle    //  PGT STORAGE
2695*d10b5556SXylle    // ########################################################################
2696*d10b5556SXylle    /**
2697*d10b5556SXylle    * @addtogroup internalPGTStorage
2698*d10b5556SXylle    * @{
2699*d10b5556SXylle    */
2700*d10b5556SXylle
2701*d10b5556SXylle    /**
2702*d10b5556SXylle     * @var  CAS_PGTStorage_AbstractStorage
2703*d10b5556SXylle     * an instance of a class inheriting of PGTStorage, used to deal with PGT
2704*d10b5556SXylle     * storage. Created by CAS_Client::setPGTStorageFile(), used
2705*d10b5556SXylle     * by CAS_Client::setPGTStorageFile() and CAS_Client::_initPGTStorage().
2706*d10b5556SXylle     *
2707*d10b5556SXylle     * @hideinitializer
2708*d10b5556SXylle     */
2709*d10b5556SXylle    private $_pgt_storage = null;
2710*d10b5556SXylle
2711*d10b5556SXylle    /**
2712*d10b5556SXylle     * This method is used to initialize the storage of PGT's.
2713*d10b5556SXylle     * Halts on error.
2714*d10b5556SXylle     *
2715*d10b5556SXylle     * @return void
2716*d10b5556SXylle     */
2717*d10b5556SXylle    private function _initPGTStorage()
2718*d10b5556SXylle    {
2719*d10b5556SXylle        // if no SetPGTStorageXxx() has been used, default to file
2720*d10b5556SXylle        if ( !is_object($this->_pgt_storage) ) {
2721*d10b5556SXylle            $this->setPGTStorageFile();
2722*d10b5556SXylle        }
2723*d10b5556SXylle
2724*d10b5556SXylle        // initializes the storage
2725*d10b5556SXylle        $this->_pgt_storage->init();
2726*d10b5556SXylle    }
2727*d10b5556SXylle
2728*d10b5556SXylle    /**
2729*d10b5556SXylle     * This method stores a PGT. Halts on error.
2730*d10b5556SXylle     *
2731*d10b5556SXylle     * @param string $pgt     the PGT to store
2732*d10b5556SXylle     * @param string $pgt_iou its corresponding Iou
2733*d10b5556SXylle     *
2734*d10b5556SXylle     * @return void
2735*d10b5556SXylle     */
2736*d10b5556SXylle    private function _storePGT($pgt,$pgt_iou)
2737*d10b5556SXylle    {
2738*d10b5556SXylle        // ensure that storage is initialized
2739*d10b5556SXylle        $this->_initPGTStorage();
2740*d10b5556SXylle        // writes the PGT
2741*d10b5556SXylle        $this->_pgt_storage->write($pgt, $pgt_iou);
2742*d10b5556SXylle    }
2743*d10b5556SXylle
2744*d10b5556SXylle    /**
2745*d10b5556SXylle     * This method reads a PGT from its Iou and deletes the corresponding
2746*d10b5556SXylle     * storage entry.
2747*d10b5556SXylle     *
2748*d10b5556SXylle     * @param string $pgt_iou the PGT Iou
2749*d10b5556SXylle     *
2750*d10b5556SXylle     * @return string mul The PGT corresponding to the Iou, false when not found.
2751*d10b5556SXylle     */
2752*d10b5556SXylle    private function _loadPGT($pgt_iou)
2753*d10b5556SXylle    {
2754*d10b5556SXylle        // ensure that storage is initialized
2755*d10b5556SXylle        $this->_initPGTStorage();
2756*d10b5556SXylle        // read the PGT
2757*d10b5556SXylle        return $this->_pgt_storage->read($pgt_iou);
2758*d10b5556SXylle    }
2759*d10b5556SXylle
2760*d10b5556SXylle    /**
2761*d10b5556SXylle     * This method can be used to set a custom PGT storage object.
2762*d10b5556SXylle     *
2763*d10b5556SXylle     * @param CAS_PGTStorage_AbstractStorage $storage a PGT storage object that
2764*d10b5556SXylle     * inherits from the CAS_PGTStorage_AbstractStorage class
2765*d10b5556SXylle     *
2766*d10b5556SXylle     * @return void
2767*d10b5556SXylle     */
2768*d10b5556SXylle    public function setPGTStorage($storage)
2769*d10b5556SXylle    {
2770*d10b5556SXylle        // Sequence validation
2771*d10b5556SXylle        $this->ensureIsProxy();
2772*d10b5556SXylle
2773*d10b5556SXylle        // check that the storage has not already been set
2774*d10b5556SXylle        if ( is_object($this->_pgt_storage) ) {
2775*d10b5556SXylle            phpCAS::error('PGT storage already defined');
2776*d10b5556SXylle        }
2777*d10b5556SXylle
2778*d10b5556SXylle        // check to make sure a valid storage object was specified
2779*d10b5556SXylle        if ( !($storage instanceof CAS_PGTStorage_AbstractStorage) )
2780*d10b5556SXylle            throw new CAS_TypeMismatchException($storage, '$storage', 'CAS_PGTStorage_AbstractStorage object');
2781*d10b5556SXylle
2782*d10b5556SXylle        // store the PGTStorage object
2783*d10b5556SXylle        $this->_pgt_storage = $storage;
2784*d10b5556SXylle    }
2785*d10b5556SXylle
2786*d10b5556SXylle    /**
2787*d10b5556SXylle     * This method is used to tell phpCAS to store the response of the
2788*d10b5556SXylle     * CAS server to PGT requests in a database.
2789*d10b5556SXylle     *
2790*d10b5556SXylle     * @param string|PDO $dsn_or_pdo     a dsn string to use for creating a PDO
2791*d10b5556SXylle     * object or a PDO object
2792*d10b5556SXylle     * @param string $username       the username to use when connecting to the
2793*d10b5556SXylle     * database
2794*d10b5556SXylle     * @param string $password       the password to use when connecting to the
2795*d10b5556SXylle     * database
2796*d10b5556SXylle     * @param string $table          the table to use for storing and retrieving
2797*d10b5556SXylle     * PGTs
2798*d10b5556SXylle     * @param string $driver_options any driver options to use when connecting
2799*d10b5556SXylle     * to the database
2800*d10b5556SXylle     *
2801*d10b5556SXylle     * @return void
2802*d10b5556SXylle     */
2803*d10b5556SXylle    public function setPGTStorageDb(
2804*d10b5556SXylle        $dsn_or_pdo, $username='', $password='', $table='', $driver_options=null
2805*d10b5556SXylle    ) {
2806*d10b5556SXylle        // Sequence validation
2807*d10b5556SXylle        $this->ensureIsProxy();
2808*d10b5556SXylle
2809*d10b5556SXylle        // Argument validation
2810*d10b5556SXylle        if (!(is_object($dsn_or_pdo) && $dsn_or_pdo instanceof PDO) && !is_string($dsn_or_pdo))
2811*d10b5556SXylle            throw new CAS_TypeMismatchException($dsn_or_pdo, '$dsn_or_pdo', 'string or PDO object');
2812*d10b5556SXylle        if (gettype($username) != 'string')
2813*d10b5556SXylle            throw new CAS_TypeMismatchException($username, '$username', 'string');
2814*d10b5556SXylle        if (gettype($password) != 'string')
2815*d10b5556SXylle            throw new CAS_TypeMismatchException($password, '$password', 'string');
2816*d10b5556SXylle        if (gettype($table) != 'string')
2817*d10b5556SXylle            throw new CAS_TypeMismatchException($table, '$password', 'string');
2818*d10b5556SXylle
2819*d10b5556SXylle        // create the storage object
2820*d10b5556SXylle        $this->setPGTStorage(
2821*d10b5556SXylle            new CAS_PGTStorage_Db(
2822*d10b5556SXylle                $this, $dsn_or_pdo, $username, $password, $table, $driver_options
2823*d10b5556SXylle            )
2824*d10b5556SXylle        );
2825*d10b5556SXylle    }
2826*d10b5556SXylle
2827*d10b5556SXylle    /**
2828*d10b5556SXylle     * This method is used to tell phpCAS to store the response of the
2829*d10b5556SXylle     * CAS server to PGT requests onto the filesystem.
2830*d10b5556SXylle     *
2831*d10b5556SXylle     * @param string $path the path where the PGT's should be stored
2832*d10b5556SXylle     *
2833*d10b5556SXylle     * @return void
2834*d10b5556SXylle     */
2835*d10b5556SXylle    public function setPGTStorageFile($path='')
2836*d10b5556SXylle    {
2837*d10b5556SXylle        // Sequence validation
2838*d10b5556SXylle        $this->ensureIsProxy();
2839*d10b5556SXylle
2840*d10b5556SXylle        // Argument validation
2841*d10b5556SXylle        if (gettype($path) != 'string')
2842*d10b5556SXylle            throw new CAS_TypeMismatchException($path, '$path', 'string');
2843*d10b5556SXylle
2844*d10b5556SXylle        // create the storage object
2845*d10b5556SXylle        $this->setPGTStorage(new CAS_PGTStorage_File($this, $path));
2846*d10b5556SXylle    }
2847*d10b5556SXylle
2848*d10b5556SXylle
2849*d10b5556SXylle    // ########################################################################
2850*d10b5556SXylle    //  PGT VALIDATION
2851*d10b5556SXylle    // ########################################################################
2852*d10b5556SXylle    /**
2853*d10b5556SXylle    * This method is used to validate a PGT; halt on failure.
2854*d10b5556SXylle    *
2855*d10b5556SXylle    * @param string &$validate_url the URL of the request to the CAS server.
2856*d10b5556SXylle    * @param string $text_response the response of the CAS server, as is
2857*d10b5556SXylle    *                              (XML text); result of
2858*d10b5556SXylle    *                              CAS_Client::validateCAS10() or
2859*d10b5556SXylle    *                              CAS_Client::validateCAS20().
2860*d10b5556SXylle    * @param DOMElement $tree_response the response of the CAS server, as a DOM XML
2861*d10b5556SXylle    * tree; result of CAS_Client::validateCAS10() or CAS_Client::validateCAS20().
2862*d10b5556SXylle    *
2863*d10b5556SXylle    * @return bool true when successfull and issue a CAS_AuthenticationException
2864*d10b5556SXylle    * and false on an error
2865*d10b5556SXylle    *
2866*d10b5556SXylle    * @throws CAS_AuthenticationException
2867*d10b5556SXylle    */
2868*d10b5556SXylle    private function _validatePGT(&$validate_url,$text_response,$tree_response)
2869*d10b5556SXylle    {
2870*d10b5556SXylle        phpCAS::traceBegin();
2871*d10b5556SXylle        if ( $tree_response->getElementsByTagName("proxyGrantingTicket")->length == 0) {
2872*d10b5556SXylle            phpCAS::trace('<proxyGrantingTicket> not found');
2873*d10b5556SXylle            // authentication succeded, but no PGT Iou was transmitted
2874*d10b5556SXylle            throw new CAS_AuthenticationException(
2875*d10b5556SXylle                $this, 'Ticket validated but no PGT Iou transmitted',
2876*d10b5556SXylle                $validate_url, false/*$no_response*/, false/*$bad_response*/,
2877*d10b5556SXylle                $text_response
2878*d10b5556SXylle            );
2879*d10b5556SXylle        } else {
2880*d10b5556SXylle            // PGT Iou transmitted, extract it
2881*d10b5556SXylle            $pgt_iou = trim(
2882*d10b5556SXylle                $tree_response->getElementsByTagName("proxyGrantingTicket")->item(0)->nodeValue
2883*d10b5556SXylle            );
2884*d10b5556SXylle            if (preg_match('/^PGTIOU-[\.\-\w]+$/', $pgt_iou)) {
2885*d10b5556SXylle                $pgt = $this->_loadPGT($pgt_iou);
2886*d10b5556SXylle                if ( $pgt == false ) {
2887*d10b5556SXylle                    phpCAS::trace('could not load PGT');
2888*d10b5556SXylle                    throw new CAS_AuthenticationException(
2889*d10b5556SXylle                        $this,
2890*d10b5556SXylle                        'PGT Iou was transmitted but PGT could not be retrieved',
2891*d10b5556SXylle                        $validate_url, false/*$no_response*/,
2892*d10b5556SXylle                        false/*$bad_response*/, $text_response
2893*d10b5556SXylle                    );
2894*d10b5556SXylle                }
2895*d10b5556SXylle                $this->_setPGT($pgt);
2896*d10b5556SXylle            } else {
2897*d10b5556SXylle                phpCAS::trace('PGTiou format error');
2898*d10b5556SXylle                throw new CAS_AuthenticationException(
2899*d10b5556SXylle                    $this, 'PGT Iou was transmitted but has wrong format',
2900*d10b5556SXylle                    $validate_url, false/*$no_response*/, false/*$bad_response*/,
2901*d10b5556SXylle                    $text_response
2902*d10b5556SXylle                );
2903*d10b5556SXylle            }
2904*d10b5556SXylle        }
2905*d10b5556SXylle        phpCAS::traceEnd(true);
2906*d10b5556SXylle        return true;
2907*d10b5556SXylle    }
2908*d10b5556SXylle
2909*d10b5556SXylle    // ########################################################################
2910*d10b5556SXylle    //  PGT VALIDATION
2911*d10b5556SXylle    // ########################################################################
2912*d10b5556SXylle
2913*d10b5556SXylle    /**
2914*d10b5556SXylle     * This method is used to retrieve PT's from the CAS server thanks to a PGT.
2915*d10b5556SXylle     *
2916*d10b5556SXylle     * @param string $target_service the service to ask for with the PT.
2917*d10b5556SXylle     * @param int &$err_code      an error code (PHPCAS_SERVICE_OK on success).
2918*d10b5556SXylle     * @param string &$err_msg       an error message (empty on success).
2919*d10b5556SXylle     *
2920*d10b5556SXylle     * @return string|false a Proxy Ticket, or false on error.
2921*d10b5556SXylle     */
2922*d10b5556SXylle    public function retrievePT($target_service,&$err_code,&$err_msg)
2923*d10b5556SXylle    {
2924*d10b5556SXylle        // Argument validation
2925*d10b5556SXylle        if (gettype($target_service) != 'string')
2926*d10b5556SXylle            throw new CAS_TypeMismatchException($target_service, '$target_service', 'string');
2927*d10b5556SXylle
2928*d10b5556SXylle        phpCAS::traceBegin();
2929*d10b5556SXylle
2930*d10b5556SXylle        // by default, $err_msg is set empty and $pt to true. On error, $pt is
2931*d10b5556SXylle        // set to false and $err_msg to an error message. At the end, if $pt is false
2932*d10b5556SXylle        // and $error_msg is still empty, it is set to 'invalid response' (the most
2933*d10b5556SXylle        // commonly encountered error).
2934*d10b5556SXylle        $err_msg = '';
2935*d10b5556SXylle
2936*d10b5556SXylle        // build the URL to retrieve the PT
2937*d10b5556SXylle        $cas_url = $this->getServerProxyURL().'?targetService='
2938*d10b5556SXylle            .urlencode($target_service).'&pgt='.$this->_getPGT();
2939*d10b5556SXylle
2940*d10b5556SXylle        $headers = '';
2941*d10b5556SXylle        $cas_response = '';
2942*d10b5556SXylle        // open and read the URL
2943*d10b5556SXylle        if ( !$this->_readURL($cas_url, $headers, $cas_response, $err_msg) ) {
2944*d10b5556SXylle            phpCAS::trace(
2945*d10b5556SXylle                'could not open URL \''.$cas_url.'\' to validate ('.$err_msg.')'
2946*d10b5556SXylle            );
2947*d10b5556SXylle            $err_code = PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE;
2948*d10b5556SXylle            $err_msg = 'could not retrieve PT (no response from the CAS server)';
2949*d10b5556SXylle            phpCAS::traceEnd(false);
2950*d10b5556SXylle            return false;
2951*d10b5556SXylle        }
2952*d10b5556SXylle
2953*d10b5556SXylle        $bad_response = false;
2954*d10b5556SXylle
2955*d10b5556SXylle        // create new DOMDocument object
2956*d10b5556SXylle        $dom = new DOMDocument();
2957*d10b5556SXylle        // Fix possible whitspace problems
2958*d10b5556SXylle        $dom->preserveWhiteSpace = false;
2959*d10b5556SXylle        // read the response of the CAS server into a DOM object
2960*d10b5556SXylle        if ( !($dom->loadXML($cas_response))) {
2961*d10b5556SXylle            phpCAS::trace('dom->loadXML() failed');
2962*d10b5556SXylle            // read failed
2963*d10b5556SXylle            $bad_response = true;
2964*d10b5556SXylle        }
2965*d10b5556SXylle
2966*d10b5556SXylle        if ( !$bad_response ) {
2967*d10b5556SXylle            // read the root node of the XML tree
2968*d10b5556SXylle            if ( !($root = $dom->documentElement) ) {
2969*d10b5556SXylle                phpCAS::trace('documentElement failed');
2970*d10b5556SXylle                // read failed
2971*d10b5556SXylle                $bad_response = true;
2972*d10b5556SXylle            }
2973*d10b5556SXylle        }
2974*d10b5556SXylle
2975*d10b5556SXylle        if ( !$bad_response ) {
2976*d10b5556SXylle            // insure that tag name is 'serviceResponse'
2977*d10b5556SXylle            if ( $root->localName != 'serviceResponse' ) {
2978*d10b5556SXylle                phpCAS::trace('localName failed');
2979*d10b5556SXylle                // bad root node
2980*d10b5556SXylle                $bad_response = true;
2981*d10b5556SXylle            }
2982*d10b5556SXylle        }
2983*d10b5556SXylle
2984*d10b5556SXylle        if ( !$bad_response ) {
2985*d10b5556SXylle            // look for a proxySuccess tag
2986*d10b5556SXylle            if ( $root->getElementsByTagName("proxySuccess")->length != 0) {
2987*d10b5556SXylle                $proxy_success_list = $root->getElementsByTagName("proxySuccess");
2988*d10b5556SXylle
2989*d10b5556SXylle                // authentication succeded, look for a proxyTicket tag
2990*d10b5556SXylle                if ( $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->length != 0) {
2991*d10b5556SXylle                    $err_code = PHPCAS_SERVICE_OK;
2992*d10b5556SXylle                    $err_msg = '';
2993*d10b5556SXylle                    $pt = trim(
2994*d10b5556SXylle                        $proxy_success_list->item(0)->getElementsByTagName("proxyTicket")->item(0)->nodeValue
2995*d10b5556SXylle                    );
2996*d10b5556SXylle                    phpCAS::trace('original PT: '.trim($pt));
2997*d10b5556SXylle                    phpCAS::traceEnd($pt);
2998*d10b5556SXylle                    return $pt;
2999*d10b5556SXylle                } else {
3000*d10b5556SXylle                    phpCAS::trace('<proxySuccess> was found, but not <proxyTicket>');
3001*d10b5556SXylle                }
3002*d10b5556SXylle            } else if ($root->getElementsByTagName("proxyFailure")->length != 0) {
3003*d10b5556SXylle                // look for a proxyFailure tag
3004*d10b5556SXylle                $proxy_failure_list = $root->getElementsByTagName("proxyFailure");
3005*d10b5556SXylle
3006*d10b5556SXylle                // authentication failed, extract the error
3007*d10b5556SXylle                $err_code = PHPCAS_SERVICE_PT_FAILURE;
3008*d10b5556SXylle                $err_msg = 'PT retrieving failed (code=`'
3009*d10b5556SXylle                .$proxy_failure_list->item(0)->getAttribute('code')
3010*d10b5556SXylle                .'\', message=`'
3011*d10b5556SXylle                .trim($proxy_failure_list->item(0)->nodeValue)
3012*d10b5556SXylle                .'\')';
3013*d10b5556SXylle                phpCAS::traceEnd(false);
3014*d10b5556SXylle                return false;
3015*d10b5556SXylle            } else {
3016*d10b5556SXylle                phpCAS::trace('neither <proxySuccess> nor <proxyFailure> found');
3017*d10b5556SXylle            }
3018*d10b5556SXylle        }
3019*d10b5556SXylle
3020*d10b5556SXylle        // at this step, we are sure that the response of the CAS server was
3021*d10b5556SXylle        // illformed
3022*d10b5556SXylle        $err_code = PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE;
3023*d10b5556SXylle        $err_msg = 'Invalid response from the CAS server (response=`'
3024*d10b5556SXylle            .$cas_response.'\')';
3025*d10b5556SXylle
3026*d10b5556SXylle        phpCAS::traceEnd(false);
3027*d10b5556SXylle        return false;
3028*d10b5556SXylle    }
3029*d10b5556SXylle
3030*d10b5556SXylle    /** @} */
3031*d10b5556SXylle
3032*d10b5556SXylle    // ########################################################################
3033*d10b5556SXylle    // READ CAS SERVER ANSWERS
3034*d10b5556SXylle    // ########################################################################
3035*d10b5556SXylle
3036*d10b5556SXylle    /**
3037*d10b5556SXylle     * @addtogroup internalMisc
3038*d10b5556SXylle     * @{
3039*d10b5556SXylle     */
3040*d10b5556SXylle
3041*d10b5556SXylle    /**
3042*d10b5556SXylle     * This method is used to acces a remote URL.
3043*d10b5556SXylle     *
3044*d10b5556SXylle     * @param string $url      the URL to access.
3045*d10b5556SXylle     * @param string &$headers an array containing the HTTP header lines of the
3046*d10b5556SXylle     * response (an empty array on failure).
3047*d10b5556SXylle     * @param string &$body    the body of the response, as a string (empty on
3048*d10b5556SXylle     * failure).
3049*d10b5556SXylle     * @param string &$err_msg an error message, filled on failure.
3050*d10b5556SXylle     *
3051*d10b5556SXylle     * @return bool true on success, false otherwise (in this later case, $err_msg
3052*d10b5556SXylle     * contains an error message).
3053*d10b5556SXylle     */
3054*d10b5556SXylle    private function _readURL($url, &$headers, &$body, &$err_msg)
3055*d10b5556SXylle    {
3056*d10b5556SXylle        phpCAS::traceBegin();
3057*d10b5556SXylle        $className = $this->_requestImplementation;
3058*d10b5556SXylle        $request = new $className();
3059*d10b5556SXylle
3060*d10b5556SXylle        if (count($this->_curl_options)) {
3061*d10b5556SXylle            $request->setCurlOptions($this->_curl_options);
3062*d10b5556SXylle        }
3063*d10b5556SXylle
3064*d10b5556SXylle        $request->setUrl($url);
3065*d10b5556SXylle
3066*d10b5556SXylle        if (empty($this->_cas_server_ca_cert) && !$this->_no_cas_server_validation) {
3067*d10b5556SXylle            phpCAS::error(
3068*d10b5556SXylle                'one of the methods phpCAS::setCasServerCACert() or phpCAS::setNoCasServerValidation() must be called.'
3069*d10b5556SXylle            );
3070*d10b5556SXylle        }
3071*d10b5556SXylle        if ($this->_cas_server_ca_cert != '') {
3072*d10b5556SXylle            $request->setSslCaCert(
3073*d10b5556SXylle                $this->_cas_server_ca_cert, $this->_cas_server_cn_validate
3074*d10b5556SXylle            );
3075*d10b5556SXylle        }
3076*d10b5556SXylle
3077*d10b5556SXylle        // add extra stuff if SAML
3078*d10b5556SXylle        if ($this->getServerVersion() == SAML_VERSION_1_1) {
3079*d10b5556SXylle            $request->addHeader("soapaction: http://www.oasis-open.org/committees/security");
3080*d10b5556SXylle            $request->addHeader("cache-control: no-cache");
3081*d10b5556SXylle            $request->addHeader("pragma: no-cache");
3082*d10b5556SXylle            $request->addHeader("accept: text/xml");
3083*d10b5556SXylle            $request->addHeader("connection: keep-alive");
3084*d10b5556SXylle            $request->addHeader("content-type: text/xml");
3085*d10b5556SXylle            $request->makePost();
3086*d10b5556SXylle            $request->setPostBody($this->_buildSAMLPayload());
3087*d10b5556SXylle        }
3088*d10b5556SXylle
3089*d10b5556SXylle        if ($request->send()) {
3090*d10b5556SXylle            $headers = $request->getResponseHeaders();
3091*d10b5556SXylle            $body = $request->getResponseBody();
3092*d10b5556SXylle            $err_msg = '';
3093*d10b5556SXylle            phpCAS::traceEnd(true);
3094*d10b5556SXylle            return true;
3095*d10b5556SXylle        } else {
3096*d10b5556SXylle            $headers = '';
3097*d10b5556SXylle            $body = '';
3098*d10b5556SXylle            $err_msg = $request->getErrorMessage();
3099*d10b5556SXylle            phpCAS::traceEnd(false);
3100*d10b5556SXylle            return false;
3101*d10b5556SXylle        }
3102*d10b5556SXylle    }
3103*d10b5556SXylle
3104*d10b5556SXylle    /**
3105*d10b5556SXylle     * This method is used to build the SAML POST body sent to /samlValidate URL.
3106*d10b5556SXylle     *
3107*d10b5556SXylle     * @return string the SOAP-encased SAMLP artifact (the ticket).
3108*d10b5556SXylle     */
3109*d10b5556SXylle    private function _buildSAMLPayload()
3110*d10b5556SXylle    {
3111*d10b5556SXylle        phpCAS::traceBegin();
3112*d10b5556SXylle
3113*d10b5556SXylle        //get the ticket
3114*d10b5556SXylle        $sa = urlencode($this->getTicket());
3115*d10b5556SXylle
3116*d10b5556SXylle        $body = SAML_SOAP_ENV.SAML_SOAP_BODY.SAMLP_REQUEST
3117*d10b5556SXylle            .SAML_ASSERTION_ARTIFACT.$sa.SAML_ASSERTION_ARTIFACT_CLOSE
3118*d10b5556SXylle            .SAMLP_REQUEST_CLOSE.SAML_SOAP_BODY_CLOSE.SAML_SOAP_ENV_CLOSE;
3119*d10b5556SXylle
3120*d10b5556SXylle        phpCAS::traceEnd($body);
3121*d10b5556SXylle        return ($body);
3122*d10b5556SXylle    }
3123*d10b5556SXylle
3124*d10b5556SXylle    /** @} **/
3125*d10b5556SXylle
3126*d10b5556SXylle    // ########################################################################
3127*d10b5556SXylle    // ACCESS TO EXTERNAL SERVICES
3128*d10b5556SXylle    // ########################################################################
3129*d10b5556SXylle
3130*d10b5556SXylle    /**
3131*d10b5556SXylle     * @addtogroup internalProxyServices
3132*d10b5556SXylle     * @{
3133*d10b5556SXylle     */
3134*d10b5556SXylle
3135*d10b5556SXylle
3136*d10b5556SXylle    /**
3137*d10b5556SXylle     * Answer a proxy-authenticated service handler.
3138*d10b5556SXylle     *
3139*d10b5556SXylle     * @param string $type The service type. One of:
3140*d10b5556SXylle     * PHPCAS_PROXIED_SERVICE_HTTP_GET, PHPCAS_PROXIED_SERVICE_HTTP_POST,
3141*d10b5556SXylle     * PHPCAS_PROXIED_SERVICE_IMAP
3142*d10b5556SXylle     *
3143*d10b5556SXylle     * @return CAS_ProxiedService
3144*d10b5556SXylle     * @throws InvalidArgumentException If the service type is unknown.
3145*d10b5556SXylle     */
3146*d10b5556SXylle    public function getProxiedService ($type)
3147*d10b5556SXylle    {
3148*d10b5556SXylle        // Sequence validation
3149*d10b5556SXylle        $this->ensureIsProxy();
3150*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
3151*d10b5556SXylle
3152*d10b5556SXylle        // Argument validation
3153*d10b5556SXylle        if (gettype($type) != 'string')
3154*d10b5556SXylle            throw new CAS_TypeMismatchException($type, '$type', 'string');
3155*d10b5556SXylle
3156*d10b5556SXylle        switch ($type) {
3157*d10b5556SXylle        case PHPCAS_PROXIED_SERVICE_HTTP_GET:
3158*d10b5556SXylle        case PHPCAS_PROXIED_SERVICE_HTTP_POST:
3159*d10b5556SXylle            $requestClass = $this->_requestImplementation;
3160*d10b5556SXylle            $request = new $requestClass();
3161*d10b5556SXylle            if (count($this->_curl_options)) {
3162*d10b5556SXylle                $request->setCurlOptions($this->_curl_options);
3163*d10b5556SXylle            }
3164*d10b5556SXylle            $proxiedService = new $type($request, $this->_serviceCookieJar);
3165*d10b5556SXylle            if ($proxiedService instanceof CAS_ProxiedService_Testable) {
3166*d10b5556SXylle                $proxiedService->setCasClient($this);
3167*d10b5556SXylle            }
3168*d10b5556SXylle            return $proxiedService;
3169*d10b5556SXylle        case PHPCAS_PROXIED_SERVICE_IMAP;
3170*d10b5556SXylle            $proxiedService = new CAS_ProxiedService_Imap($this->_getUser());
3171*d10b5556SXylle            if ($proxiedService instanceof CAS_ProxiedService_Testable) {
3172*d10b5556SXylle                $proxiedService->setCasClient($this);
3173*d10b5556SXylle            }
3174*d10b5556SXylle            return $proxiedService;
3175*d10b5556SXylle        default:
3176*d10b5556SXylle            throw new CAS_InvalidArgumentException(
3177*d10b5556SXylle                "Unknown proxied-service type, $type."
3178*d10b5556SXylle            );
3179*d10b5556SXylle        }
3180*d10b5556SXylle    }
3181*d10b5556SXylle
3182*d10b5556SXylle    /**
3183*d10b5556SXylle     * Initialize a proxied-service handler with the proxy-ticket it should use.
3184*d10b5556SXylle     *
3185*d10b5556SXylle     * @param CAS_ProxiedService $proxiedService service handler
3186*d10b5556SXylle     *
3187*d10b5556SXylle     * @return void
3188*d10b5556SXylle     *
3189*d10b5556SXylle     * @throws CAS_ProxyTicketException If there is a proxy-ticket failure.
3190*d10b5556SXylle     *		The code of the Exception will be one of:
3191*d10b5556SXylle     *			PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE
3192*d10b5556SXylle     *			PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE
3193*d10b5556SXylle     *			PHPCAS_SERVICE_PT_FAILURE
3194*d10b5556SXylle     * @throws CAS_ProxiedService_Exception If there is a failure getting the
3195*d10b5556SXylle     * url from the proxied service.
3196*d10b5556SXylle     */
3197*d10b5556SXylle    public function initializeProxiedService (CAS_ProxiedService $proxiedService)
3198*d10b5556SXylle    {
3199*d10b5556SXylle        // Sequence validation
3200*d10b5556SXylle        $this->ensureIsProxy();
3201*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
3202*d10b5556SXylle
3203*d10b5556SXylle        $url = $proxiedService->getServiceUrl();
3204*d10b5556SXylle        if (!is_string($url)) {
3205*d10b5556SXylle            throw new CAS_ProxiedService_Exception(
3206*d10b5556SXylle                "Proxied Service ".get_class($proxiedService)
3207*d10b5556SXylle                ."->getServiceUrl() should have returned a string, returned a "
3208*d10b5556SXylle                .gettype($url)." instead."
3209*d10b5556SXylle            );
3210*d10b5556SXylle        }
3211*d10b5556SXylle        $pt = $this->retrievePT($url, $err_code, $err_msg);
3212*d10b5556SXylle        if (!$pt) {
3213*d10b5556SXylle            throw new CAS_ProxyTicketException($err_msg, $err_code);
3214*d10b5556SXylle        }
3215*d10b5556SXylle        $proxiedService->setProxyTicket($pt);
3216*d10b5556SXylle    }
3217*d10b5556SXylle
3218*d10b5556SXylle    /**
3219*d10b5556SXylle     * This method is used to access an HTTP[S] service.
3220*d10b5556SXylle     *
3221*d10b5556SXylle     * @param string $url       the service to access.
3222*d10b5556SXylle     * @param int    &$err_code an error code Possible values are
3223*d10b5556SXylle     * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
3224*d10b5556SXylle     * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
3225*d10b5556SXylle     * PHPCAS_SERVICE_NOT_AVAILABLE.
3226*d10b5556SXylle     * @param string &$output   the output of the service (also used to give an error
3227*d10b5556SXylle     * message on failure).
3228*d10b5556SXylle     *
3229*d10b5556SXylle     * @return bool true on success, false otherwise (in this later case, $err_code
3230*d10b5556SXylle     * gives the reason why it failed and $output contains an error message).
3231*d10b5556SXylle     */
3232*d10b5556SXylle    public function serviceWeb($url,&$err_code,&$output)
3233*d10b5556SXylle    {
3234*d10b5556SXylle        // Sequence validation
3235*d10b5556SXylle        $this->ensureIsProxy();
3236*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
3237*d10b5556SXylle
3238*d10b5556SXylle        // Argument validation
3239*d10b5556SXylle        if (gettype($url) != 'string')
3240*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
3241*d10b5556SXylle
3242*d10b5556SXylle        try {
3243*d10b5556SXylle            $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_HTTP_GET);
3244*d10b5556SXylle            $service->setUrl($url);
3245*d10b5556SXylle            $service->send();
3246*d10b5556SXylle            $output = $service->getResponseBody();
3247*d10b5556SXylle            $err_code = PHPCAS_SERVICE_OK;
3248*d10b5556SXylle            return true;
3249*d10b5556SXylle        } catch (CAS_ProxyTicketException $e) {
3250*d10b5556SXylle            $err_code = $e->getCode();
3251*d10b5556SXylle            $output = $e->getMessage();
3252*d10b5556SXylle            return false;
3253*d10b5556SXylle        } catch (CAS_ProxiedService_Exception $e) {
3254*d10b5556SXylle            $lang = $this->getLangObj();
3255*d10b5556SXylle            $output = sprintf(
3256*d10b5556SXylle                $lang->getServiceUnavailable(), $url, $e->getMessage()
3257*d10b5556SXylle            );
3258*d10b5556SXylle            $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
3259*d10b5556SXylle            return false;
3260*d10b5556SXylle        }
3261*d10b5556SXylle    }
3262*d10b5556SXylle
3263*d10b5556SXylle    /**
3264*d10b5556SXylle     * This method is used to access an IMAP/POP3/NNTP service.
3265*d10b5556SXylle     *
3266*d10b5556SXylle     * @param string $url        a string giving the URL of the service, including
3267*d10b5556SXylle     * the mailing box for IMAP URLs, as accepted by imap_open().
3268*d10b5556SXylle     * @param string $serviceUrl a string giving for CAS retrieve Proxy ticket
3269*d10b5556SXylle     * @param string $flags      options given to imap_open().
3270*d10b5556SXylle     * @param int    &$err_code  an error code Possible values are
3271*d10b5556SXylle     * PHPCAS_SERVICE_OK (on success), PHPCAS_SERVICE_PT_NO_SERVER_RESPONSE,
3272*d10b5556SXylle     * PHPCAS_SERVICE_PT_BAD_SERVER_RESPONSE, PHPCAS_SERVICE_PT_FAILURE,
3273*d10b5556SXylle     *  PHPCAS_SERVICE_NOT_AVAILABLE.
3274*d10b5556SXylle     * @param string &$err_msg   an error message on failure
3275*d10b5556SXylle     * @param string &$pt        the Proxy Ticket (PT) retrieved from the CAS
3276*d10b5556SXylle     * server to access the URL on success, false on error).
3277*d10b5556SXylle     *
3278*d10b5556SXylle     * @return object|false an IMAP stream on success, false otherwise (in this later
3279*d10b5556SXylle     *  case, $err_code gives the reason why it failed and $err_msg contains an
3280*d10b5556SXylle     *  error message).
3281*d10b5556SXylle     */
3282*d10b5556SXylle    public function serviceMail($url,$serviceUrl,$flags,&$err_code,&$err_msg,&$pt)
3283*d10b5556SXylle    {
3284*d10b5556SXylle        // Sequence validation
3285*d10b5556SXylle        $this->ensureIsProxy();
3286*d10b5556SXylle        $this->ensureAuthenticationCallSuccessful();
3287*d10b5556SXylle
3288*d10b5556SXylle        // Argument validation
3289*d10b5556SXylle        if (gettype($url) != 'string')
3290*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
3291*d10b5556SXylle        if (gettype($serviceUrl) != 'string')
3292*d10b5556SXylle            throw new CAS_TypeMismatchException($serviceUrl, '$serviceUrl', 'string');
3293*d10b5556SXylle        if (gettype($flags) != 'integer')
3294*d10b5556SXylle            throw new CAS_TypeMismatchException($flags, '$flags', 'string');
3295*d10b5556SXylle
3296*d10b5556SXylle        try {
3297*d10b5556SXylle            $service = $this->getProxiedService(PHPCAS_PROXIED_SERVICE_IMAP);
3298*d10b5556SXylle            $service->setServiceUrl($serviceUrl);
3299*d10b5556SXylle            $service->setMailbox($url);
3300*d10b5556SXylle            $service->setOptions($flags);
3301*d10b5556SXylle
3302*d10b5556SXylle            $stream = $service->open();
3303*d10b5556SXylle            $err_code = PHPCAS_SERVICE_OK;
3304*d10b5556SXylle            $pt = $service->getImapProxyTicket();
3305*d10b5556SXylle            return $stream;
3306*d10b5556SXylle        } catch (CAS_ProxyTicketException $e) {
3307*d10b5556SXylle            $err_msg = $e->getMessage();
3308*d10b5556SXylle            $err_code = $e->getCode();
3309*d10b5556SXylle            $pt = false;
3310*d10b5556SXylle            return false;
3311*d10b5556SXylle        } catch (CAS_ProxiedService_Exception $e) {
3312*d10b5556SXylle            $lang = $this->getLangObj();
3313*d10b5556SXylle            $err_msg = sprintf(
3314*d10b5556SXylle                $lang->getServiceUnavailable(),
3315*d10b5556SXylle                $url,
3316*d10b5556SXylle                $e->getMessage()
3317*d10b5556SXylle            );
3318*d10b5556SXylle            $err_code = PHPCAS_SERVICE_NOT_AVAILABLE;
3319*d10b5556SXylle            $pt = false;
3320*d10b5556SXylle            return false;
3321*d10b5556SXylle        }
3322*d10b5556SXylle    }
3323*d10b5556SXylle
3324*d10b5556SXylle    /** @} **/
3325*d10b5556SXylle
3326*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3327*d10b5556SXylle    // XX                                                                    XX
3328*d10b5556SXylle    // XX                  PROXIED CLIENT FEATURES (CAS 2.0)                 XX
3329*d10b5556SXylle    // XX                                                                    XX
3330*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3331*d10b5556SXylle
3332*d10b5556SXylle    // ########################################################################
3333*d10b5556SXylle    //  PT
3334*d10b5556SXylle    // ########################################################################
3335*d10b5556SXylle    /**
3336*d10b5556SXylle    * @addtogroup internalService
3337*d10b5556SXylle    * @{
3338*d10b5556SXylle    */
3339*d10b5556SXylle
3340*d10b5556SXylle    /**
3341*d10b5556SXylle     * This array will store a list of proxies in front of this application. This
3342*d10b5556SXylle     * property will only be populated if this script is being proxied rather than
3343*d10b5556SXylle     * accessed directly.
3344*d10b5556SXylle     *
3345*d10b5556SXylle     * It is set in CAS_Client::validateCAS20() and can be read by
3346*d10b5556SXylle     * CAS_Client::getProxies()
3347*d10b5556SXylle     *
3348*d10b5556SXylle     * @access private
3349*d10b5556SXylle     */
3350*d10b5556SXylle    private $_proxies = array();
3351*d10b5556SXylle
3352*d10b5556SXylle    /**
3353*d10b5556SXylle     * Answer an array of proxies that are sitting in front of this application.
3354*d10b5556SXylle     *
3355*d10b5556SXylle     * This method will only return a non-empty array if we have received and
3356*d10b5556SXylle     * validated a Proxy Ticket.
3357*d10b5556SXylle     *
3358*d10b5556SXylle     * @return array
3359*d10b5556SXylle     * @access public
3360*d10b5556SXylle     */
3361*d10b5556SXylle    public function getProxies()
3362*d10b5556SXylle    {
3363*d10b5556SXylle        return $this->_proxies;
3364*d10b5556SXylle    }
3365*d10b5556SXylle
3366*d10b5556SXylle    /**
3367*d10b5556SXylle     * Set the Proxy array, probably from persistant storage.
3368*d10b5556SXylle     *
3369*d10b5556SXylle     * @param array $proxies An array of proxies
3370*d10b5556SXylle     *
3371*d10b5556SXylle     * @return void
3372*d10b5556SXylle     * @access private
3373*d10b5556SXylle     */
3374*d10b5556SXylle    private function _setProxies($proxies)
3375*d10b5556SXylle    {
3376*d10b5556SXylle        $this->_proxies = $proxies;
3377*d10b5556SXylle        if (!empty($proxies)) {
3378*d10b5556SXylle            // For proxy-authenticated requests people are not viewing the URL
3379*d10b5556SXylle            // directly since the client is another application making a
3380*d10b5556SXylle            // web-service call.
3381*d10b5556SXylle            // Because of this, stripping the ticket from the URL is unnecessary
3382*d10b5556SXylle            // and causes another web-service request to be performed. Additionally,
3383*d10b5556SXylle            // if session handling on either the client or the server malfunctions
3384*d10b5556SXylle            // then the subsequent request will not complete successfully.
3385*d10b5556SXylle            $this->setNoClearTicketsFromUrl();
3386*d10b5556SXylle        }
3387*d10b5556SXylle    }
3388*d10b5556SXylle
3389*d10b5556SXylle    /**
3390*d10b5556SXylle     * A container of patterns to be allowed as proxies in front of the cas client.
3391*d10b5556SXylle     *
3392*d10b5556SXylle     * @var CAS_ProxyChain_AllowedList
3393*d10b5556SXylle     */
3394*d10b5556SXylle    private $_allowed_proxy_chains;
3395*d10b5556SXylle
3396*d10b5556SXylle    /**
3397*d10b5556SXylle     * Answer the CAS_ProxyChain_AllowedList object for this client.
3398*d10b5556SXylle     *
3399*d10b5556SXylle     * @return CAS_ProxyChain_AllowedList
3400*d10b5556SXylle     */
3401*d10b5556SXylle    public function getAllowedProxyChains ()
3402*d10b5556SXylle    {
3403*d10b5556SXylle        if (empty($this->_allowed_proxy_chains)) {
3404*d10b5556SXylle            $this->_allowed_proxy_chains = new CAS_ProxyChain_AllowedList();
3405*d10b5556SXylle        }
3406*d10b5556SXylle        return $this->_allowed_proxy_chains;
3407*d10b5556SXylle    }
3408*d10b5556SXylle
3409*d10b5556SXylle    /** @} */
3410*d10b5556SXylle    // ########################################################################
3411*d10b5556SXylle    //  PT VALIDATION
3412*d10b5556SXylle    // ########################################################################
3413*d10b5556SXylle    /**
3414*d10b5556SXylle    * @addtogroup internalProxied
3415*d10b5556SXylle    * @{
3416*d10b5556SXylle    */
3417*d10b5556SXylle
3418*d10b5556SXylle    /**
3419*d10b5556SXylle     * This method is used to validate a cas 2.0 ST or PT; halt on failure
3420*d10b5556SXylle     * Used for all CAS 2.0 validations
3421*d10b5556SXylle     *
3422*d10b5556SXylle     * @param string &$validate_url  the url of the reponse
3423*d10b5556SXylle     * @param string &$text_response the text of the repsones
3424*d10b5556SXylle     * @param DOMElement &$tree_response the domxml tree of the respones
3425*d10b5556SXylle     * @param bool   $renew          true to force the authentication with the CAS server
3426*d10b5556SXylle     *
3427*d10b5556SXylle     * @return bool true when successfull and issue a CAS_AuthenticationException
3428*d10b5556SXylle     * and false on an error
3429*d10b5556SXylle     *
3430*d10b5556SXylle     * @throws  CAS_AuthenticationException
3431*d10b5556SXylle     */
3432*d10b5556SXylle    public function validateCAS20(&$validate_url,&$text_response,&$tree_response, $renew=false)
3433*d10b5556SXylle    {
3434*d10b5556SXylle        phpCAS::traceBegin();
3435*d10b5556SXylle        phpCAS::trace($text_response);
3436*d10b5556SXylle        // build the URL to validate the ticket
3437*d10b5556SXylle        if ($this->getAllowedProxyChains()->isProxyingAllowed()) {
3438*d10b5556SXylle            $validate_url = $this->getServerProxyValidateURL().'&ticket='
3439*d10b5556SXylle                .urlencode($this->getTicket());
3440*d10b5556SXylle        } else {
3441*d10b5556SXylle            $validate_url = $this->getServerServiceValidateURL().'&ticket='
3442*d10b5556SXylle                .urlencode($this->getTicket());
3443*d10b5556SXylle        }
3444*d10b5556SXylle
3445*d10b5556SXylle        if ( $this->isProxy() ) {
3446*d10b5556SXylle            // pass the callback url for CAS proxies
3447*d10b5556SXylle            $validate_url .= '&pgtUrl='.urlencode($this->_getCallbackURL());
3448*d10b5556SXylle        }
3449*d10b5556SXylle
3450*d10b5556SXylle        if ( $renew ) {
3451*d10b5556SXylle            // pass the renew
3452*d10b5556SXylle            $validate_url .= '&renew=true';
3453*d10b5556SXylle        }
3454*d10b5556SXylle
3455*d10b5556SXylle        // open and read the URL
3456*d10b5556SXylle        if ( !$this->_readURL($validate_url, $headers, $text_response, $err_msg) ) {
3457*d10b5556SXylle            phpCAS::trace(
3458*d10b5556SXylle                'could not open URL \''.$validate_url.'\' to validate ('.$err_msg.')'
3459*d10b5556SXylle            );
3460*d10b5556SXylle            throw new CAS_AuthenticationException(
3461*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3462*d10b5556SXylle                true/*$no_response*/
3463*d10b5556SXylle            );
3464*d10b5556SXylle        }
3465*d10b5556SXylle
3466*d10b5556SXylle        // create new DOMDocument object
3467*d10b5556SXylle        $dom = new DOMDocument();
3468*d10b5556SXylle        // Fix possible whitspace problems
3469*d10b5556SXylle        $dom->preserveWhiteSpace = false;
3470*d10b5556SXylle        // CAS servers should only return data in utf-8
3471*d10b5556SXylle        $dom->encoding = "utf-8";
3472*d10b5556SXylle        // read the response of the CAS server into a DOMDocument object
3473*d10b5556SXylle        if ( !($dom->loadXML($text_response))) {
3474*d10b5556SXylle            // read failed
3475*d10b5556SXylle            throw new CAS_AuthenticationException(
3476*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3477*d10b5556SXylle                false/*$no_response*/, true/*$bad_response*/, $text_response
3478*d10b5556SXylle            );
3479*d10b5556SXylle        } else if ( !($tree_response = $dom->documentElement) ) {
3480*d10b5556SXylle            // read the root node of the XML tree
3481*d10b5556SXylle            // read failed
3482*d10b5556SXylle            throw new CAS_AuthenticationException(
3483*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3484*d10b5556SXylle                false/*$no_response*/, true/*$bad_response*/, $text_response
3485*d10b5556SXylle            );
3486*d10b5556SXylle        } else if ($tree_response->localName != 'serviceResponse') {
3487*d10b5556SXylle            // insure that tag name is 'serviceResponse'
3488*d10b5556SXylle            // bad root node
3489*d10b5556SXylle            throw new CAS_AuthenticationException(
3490*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3491*d10b5556SXylle                false/*$no_response*/, true/*$bad_response*/, $text_response
3492*d10b5556SXylle            );
3493*d10b5556SXylle        } else if ( $tree_response->getElementsByTagName("authenticationFailure")->length != 0) {
3494*d10b5556SXylle            // authentication failed, extract the error code and message and throw exception
3495*d10b5556SXylle            $auth_fail_list = $tree_response
3496*d10b5556SXylle                ->getElementsByTagName("authenticationFailure");
3497*d10b5556SXylle            throw new CAS_AuthenticationException(
3498*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3499*d10b5556SXylle                false/*$no_response*/, false/*$bad_response*/,
3500*d10b5556SXylle                $text_response,
3501*d10b5556SXylle                $auth_fail_list->item(0)->getAttribute('code')/*$err_code*/,
3502*d10b5556SXylle                trim($auth_fail_list->item(0)->nodeValue)/*$err_msg*/
3503*d10b5556SXylle            );
3504*d10b5556SXylle        } else if ($tree_response->getElementsByTagName("authenticationSuccess")->length != 0) {
3505*d10b5556SXylle            // authentication succeded, extract the user name
3506*d10b5556SXylle            $success_elements = $tree_response
3507*d10b5556SXylle                ->getElementsByTagName("authenticationSuccess");
3508*d10b5556SXylle            if ( $success_elements->item(0)->getElementsByTagName("user")->length == 0) {
3509*d10b5556SXylle                // no user specified => error
3510*d10b5556SXylle                throw new CAS_AuthenticationException(
3511*d10b5556SXylle                    $this, 'Ticket not validated', $validate_url,
3512*d10b5556SXylle                    false/*$no_response*/, true/*$bad_response*/, $text_response
3513*d10b5556SXylle                );
3514*d10b5556SXylle            } else {
3515*d10b5556SXylle                $this->_setUser(
3516*d10b5556SXylle                    trim(
3517*d10b5556SXylle                        $success_elements->item(0)->getElementsByTagName("user")->item(0)->nodeValue
3518*d10b5556SXylle                    )
3519*d10b5556SXylle                );
3520*d10b5556SXylle                $this->_readExtraAttributesCas20($success_elements);
3521*d10b5556SXylle                // Store the proxies we are sitting behind for authorization checking
3522*d10b5556SXylle                $proxyList = array();
3523*d10b5556SXylle                if ( sizeof($arr = $success_elements->item(0)->getElementsByTagName("proxy")) > 0) {
3524*d10b5556SXylle                    foreach ($arr as $proxyElem) {
3525*d10b5556SXylle                        phpCAS::trace("Found Proxy: ".$proxyElem->nodeValue);
3526*d10b5556SXylle                        $proxyList[] = trim($proxyElem->nodeValue);
3527*d10b5556SXylle                    }
3528*d10b5556SXylle                    $this->_setProxies($proxyList);
3529*d10b5556SXylle                    phpCAS::trace("Storing Proxy List");
3530*d10b5556SXylle                }
3531*d10b5556SXylle                // Check if the proxies in front of us are allowed
3532*d10b5556SXylle                if (!$this->getAllowedProxyChains()->isProxyListAllowed($proxyList)) {
3533*d10b5556SXylle                    throw new CAS_AuthenticationException(
3534*d10b5556SXylle                        $this, 'Proxy not allowed', $validate_url,
3535*d10b5556SXylle                        false/*$no_response*/, true/*$bad_response*/,
3536*d10b5556SXylle                        $text_response
3537*d10b5556SXylle                    );
3538*d10b5556SXylle                } else {
3539*d10b5556SXylle                    $result = true;
3540*d10b5556SXylle                }
3541*d10b5556SXylle            }
3542*d10b5556SXylle        } else {
3543*d10b5556SXylle            throw new CAS_AuthenticationException(
3544*d10b5556SXylle                $this, 'Ticket not validated', $validate_url,
3545*d10b5556SXylle                false/*$no_response*/, true/*$bad_response*/,
3546*d10b5556SXylle                $text_response
3547*d10b5556SXylle            );
3548*d10b5556SXylle        }
3549*d10b5556SXylle
3550*d10b5556SXylle        $this->_renameSession($this->getTicket());
3551*d10b5556SXylle
3552*d10b5556SXylle        // at this step, Ticket has been validated and $this->_user has been set,
3553*d10b5556SXylle
3554*d10b5556SXylle        phpCAS::traceEnd($result);
3555*d10b5556SXylle        return $result;
3556*d10b5556SXylle    }
3557*d10b5556SXylle
3558*d10b5556SXylle    /**
3559*d10b5556SXylle     * This method recursively parses the attribute XML.
3560*d10b5556SXylle     * It also collapses name-value pairs into a single
3561*d10b5556SXylle     * array entry. It parses all common formats of
3562*d10b5556SXylle     * attributes and well formed XML files.
3563*d10b5556SXylle     *
3564*d10b5556SXylle     * @param string $root       the DOM root element to be parsed
3565*d10b5556SXylle     * @param string $namespace  namespace of the elements
3566*d10b5556SXylle     *
3567*d10b5556SXylle     * @return an array of the parsed XML elements
3568*d10b5556SXylle     *
3569*d10b5556SXylle     * Formats tested:
3570*d10b5556SXylle     *
3571*d10b5556SXylle     *  "Jasig Style" Attributes:
3572*d10b5556SXylle     *
3573*d10b5556SXylle     *      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3574*d10b5556SXylle     *          <cas:authenticationSuccess>
3575*d10b5556SXylle     *              <cas:user>jsmith</cas:user>
3576*d10b5556SXylle     *              <cas:attributes>
3577*d10b5556SXylle     *                  <cas:attraStyle>RubyCAS</cas:attraStyle>
3578*d10b5556SXylle     *                  <cas:surname>Smith</cas:surname>
3579*d10b5556SXylle     *                  <cas:givenName>John</cas:givenName>
3580*d10b5556SXylle     *                  <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
3581*d10b5556SXylle     *                  <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
3582*d10b5556SXylle     *              </cas:attributes>
3583*d10b5556SXylle     *              <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3584*d10b5556SXylle     *          </cas:authenticationSuccess>
3585*d10b5556SXylle     *      </cas:serviceResponse>
3586*d10b5556SXylle     *
3587*d10b5556SXylle     *  "Jasig Style" Attributes (longer version):
3588*d10b5556SXylle     *
3589*d10b5556SXylle     *      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3590*d10b5556SXylle     *          <cas:authenticationSuccess>
3591*d10b5556SXylle     *              <cas:user>jsmith</cas:user>
3592*d10b5556SXylle     *              <cas:attributes>
3593*d10b5556SXylle     *                  <cas:attribute>
3594*d10b5556SXylle     *                      <cas:name>surname</cas:name>
3595*d10b5556SXylle     *                      <cas:value>Smith</cas:value>
3596*d10b5556SXylle     *                  </cas:attribute>
3597*d10b5556SXylle     *                  <cas:attribute>
3598*d10b5556SXylle     *                      <cas:name>givenName</cas:name>
3599*d10b5556SXylle     *                      <cas:value>John</cas:value>
3600*d10b5556SXylle     *                  </cas:attribute>
3601*d10b5556SXylle     *                  <cas:attribute>
3602*d10b5556SXylle     *                      <cas:name>memberOf</cas:name>
3603*d10b5556SXylle     *                      <cas:value>['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu']</cas:value>
3604*d10b5556SXylle     *                  </cas:attribute>
3605*d10b5556SXylle     *              </cas:attributes>
3606*d10b5556SXylle     *              <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3607*d10b5556SXylle     *          </cas:authenticationSuccess>
3608*d10b5556SXylle     *      </cas:serviceResponse>
3609*d10b5556SXylle     *
3610*d10b5556SXylle     *  "RubyCAS Style" attributes
3611*d10b5556SXylle     *
3612*d10b5556SXylle     *      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3613*d10b5556SXylle     *          <cas:authenticationSuccess>
3614*d10b5556SXylle     *              <cas:user>jsmith</cas:user>
3615*d10b5556SXylle     *
3616*d10b5556SXylle     *              <cas:attraStyle>RubyCAS</cas:attraStyle>
3617*d10b5556SXylle     *              <cas:surname>Smith</cas:surname>
3618*d10b5556SXylle     *              <cas:givenName>John</cas:givenName>
3619*d10b5556SXylle     *              <cas:memberOf>CN=Staff,OU=Groups,DC=example,DC=edu</cas:memberOf>
3620*d10b5556SXylle     *              <cas:memberOf>CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu</cas:memberOf>
3621*d10b5556SXylle     *
3622*d10b5556SXylle     *              <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3623*d10b5556SXylle     *          </cas:authenticationSuccess>
3624*d10b5556SXylle     *      </cas:serviceResponse>
3625*d10b5556SXylle     *
3626*d10b5556SXylle     *  "Name-Value" attributes.
3627*d10b5556SXylle     *
3628*d10b5556SXylle     *  Attribute format from these mailing list thread:
3629*d10b5556SXylle     *  http://jasig.275507.n4.nabble.com/CAS-attributes-and-how-they-appear-in-the-CAS-response-td264272.html
3630*d10b5556SXylle     *  Note: This is a less widely used format, but in use by at least two institutions.
3631*d10b5556SXylle     *
3632*d10b5556SXylle     *      <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
3633*d10b5556SXylle     *          <cas:authenticationSuccess>
3634*d10b5556SXylle     *              <cas:user>jsmith</cas:user>
3635*d10b5556SXylle     *
3636*d10b5556SXylle     *              <cas:attribute name='attraStyle' value='Name-Value' />
3637*d10b5556SXylle     *              <cas:attribute name='surname' value='Smith' />
3638*d10b5556SXylle     *              <cas:attribute name='givenName' value='John' />
3639*d10b5556SXylle     *              <cas:attribute name='memberOf' value='CN=Staff,OU=Groups,DC=example,DC=edu' />
3640*d10b5556SXylle     *              <cas:attribute name='memberOf' value='CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu' />
3641*d10b5556SXylle     *
3642*d10b5556SXylle     *              <cas:proxyGrantingTicket>PGTIOU-84678-8a9d2sfa23casd</cas:proxyGrantingTicket>
3643*d10b5556SXylle     *          </cas:authenticationSuccess>
3644*d10b5556SXylle     *      </cas:serviceResponse>
3645*d10b5556SXylle     *
3646*d10b5556SXylle     * result:
3647*d10b5556SXylle     *
3648*d10b5556SXylle     *      Array (
3649*d10b5556SXylle     *          [surname] => Smith
3650*d10b5556SXylle     *          [givenName] => John
3651*d10b5556SXylle     *          [memberOf] => Array (
3652*d10b5556SXylle     *              [0] => CN=Staff, OU=Groups, DC=example, DC=edu
3653*d10b5556SXylle     *              [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu
3654*d10b5556SXylle     *          )
3655*d10b5556SXylle     *      )
3656*d10b5556SXylle     */
3657*d10b5556SXylle    private function _xml_to_array($root, $namespace = "cas")
3658*d10b5556SXylle    {
3659*d10b5556SXylle        $result = array();
3660*d10b5556SXylle        if ($root->hasAttributes()) {
3661*d10b5556SXylle            $attrs = $root->attributes;
3662*d10b5556SXylle            $pair = array();
3663*d10b5556SXylle            foreach ($attrs as $attr) {
3664*d10b5556SXylle                if ($attr->name === "name") {
3665*d10b5556SXylle                    $pair['name'] = $attr->value;
3666*d10b5556SXylle                } elseif ($attr->name === "value") {
3667*d10b5556SXylle                    $pair['value'] = $attr->value;
3668*d10b5556SXylle                } else {
3669*d10b5556SXylle                    $result[$attr->name] = $attr->value;
3670*d10b5556SXylle                }
3671*d10b5556SXylle                if (array_key_exists('name', $pair) && array_key_exists('value', $pair)) {
3672*d10b5556SXylle                    $result[$pair['name']] = $pair['value'];
3673*d10b5556SXylle                }
3674*d10b5556SXylle            }
3675*d10b5556SXylle        }
3676*d10b5556SXylle        if ($root->hasChildNodes()) {
3677*d10b5556SXylle            $children = $root->childNodes;
3678*d10b5556SXylle            if ($children->length == 1) {
3679*d10b5556SXylle                $child = $children->item(0);
3680*d10b5556SXylle                if ($child->nodeType == XML_TEXT_NODE) {
3681*d10b5556SXylle                    $result['_value'] = $child->nodeValue;
3682*d10b5556SXylle                    return (count($result) == 1) ? $result['_value'] : $result;
3683*d10b5556SXylle                }
3684*d10b5556SXylle            }
3685*d10b5556SXylle            $groups = array();
3686*d10b5556SXylle            foreach ($children as $child) {
3687*d10b5556SXylle                $child_nodeName = str_ireplace($namespace . ":", "", $child->nodeName);
3688*d10b5556SXylle                if (in_array($child_nodeName, array("user", "proxies", "proxyGrantingTicket"))) {
3689*d10b5556SXylle                    continue;
3690*d10b5556SXylle                }
3691*d10b5556SXylle                if (!isset($result[$child_nodeName])) {
3692*d10b5556SXylle                    $res = $this->_xml_to_array($child, $namespace);
3693*d10b5556SXylle                    if (!empty($res)) {
3694*d10b5556SXylle                        $result[$child_nodeName] = $this->_xml_to_array($child, $namespace);
3695*d10b5556SXylle                    }
3696*d10b5556SXylle                } else {
3697*d10b5556SXylle                    if (!isset($groups[$child_nodeName])) {
3698*d10b5556SXylle                        $result[$child_nodeName] = array($result[$child_nodeName]);
3699*d10b5556SXylle                        $groups[$child_nodeName] = 1;
3700*d10b5556SXylle                    }
3701*d10b5556SXylle                    $result[$child_nodeName][] = $this->_xml_to_array($child, $namespace);
3702*d10b5556SXylle                }
3703*d10b5556SXylle            }
3704*d10b5556SXylle        }
3705*d10b5556SXylle        return $result;
3706*d10b5556SXylle    }
3707*d10b5556SXylle
3708*d10b5556SXylle    /**
3709*d10b5556SXylle     * This method parses a "JSON-like array" of strings
3710*d10b5556SXylle     * into an array of strings
3711*d10b5556SXylle     *
3712*d10b5556SXylle     * @param string $json_value  the json-like string:
3713*d10b5556SXylle     *      e.g.:
3714*d10b5556SXylle     *          ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu']
3715*d10b5556SXylle     *
3716*d10b5556SXylle     * @return array of strings Description
3717*d10b5556SXylle     *      e.g.:
3718*d10b5556SXylle     *          Array (
3719*d10b5556SXylle     *              [0] => CN=Staff,OU=Groups,DC=example,DC=edu
3720*d10b5556SXylle     *              [1] => CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu
3721*d10b5556SXylle     *          )
3722*d10b5556SXylle     */
3723*d10b5556SXylle    private function _parse_json_like_array_value($json_value)
3724*d10b5556SXylle    {
3725*d10b5556SXylle        $parts = explode(",", trim($json_value, "[]"));
3726*d10b5556SXylle        $out = array();
3727*d10b5556SXylle        $quote = '';
3728*d10b5556SXylle        foreach ($parts as $part) {
3729*d10b5556SXylle            $part = trim($part);
3730*d10b5556SXylle            if ($quote === '') {
3731*d10b5556SXylle                $value = "";
3732*d10b5556SXylle                if ($this->_startsWith($part, '\'')) {
3733*d10b5556SXylle                    $quote = '\'';
3734*d10b5556SXylle                } elseif ($this->_startsWith($part, '"')) {
3735*d10b5556SXylle                    $quote = '"';
3736*d10b5556SXylle                } else {
3737*d10b5556SXylle                    $out[] = $part;
3738*d10b5556SXylle                }
3739*d10b5556SXylle                $part = ltrim($part, $quote);
3740*d10b5556SXylle            }
3741*d10b5556SXylle            if ($quote !== '') {
3742*d10b5556SXylle                $value .= $part;
3743*d10b5556SXylle                if ($this->_endsWith($part, $quote)) {
3744*d10b5556SXylle                    $out[] = rtrim($value, $quote);
3745*d10b5556SXylle                    $quote = '';
3746*d10b5556SXylle                } else {
3747*d10b5556SXylle                    $value .= ", ";
3748*d10b5556SXylle                };
3749*d10b5556SXylle            }
3750*d10b5556SXylle        }
3751*d10b5556SXylle        return $out;
3752*d10b5556SXylle    }
3753*d10b5556SXylle
3754*d10b5556SXylle    /**
3755*d10b5556SXylle     * This method recursively removes unneccessary hirarchy levels in array-trees.
3756*d10b5556SXylle     * into an array of strings
3757*d10b5556SXylle     *
3758*d10b5556SXylle     * @param array $arr the array to flatten
3759*d10b5556SXylle     *      e.g.:
3760*d10b5556SXylle     *          Array (
3761*d10b5556SXylle     *              [attributes] => Array (
3762*d10b5556SXylle     *                  [attribute] => Array (
3763*d10b5556SXylle     *                      [0] => Array (
3764*d10b5556SXylle     *                          [name] => surname
3765*d10b5556SXylle     *                          [value] => Smith
3766*d10b5556SXylle     *                      )
3767*d10b5556SXylle     *                      [1] => Array (
3768*d10b5556SXylle     *                          [name] => givenName
3769*d10b5556SXylle     *                          [value] => John
3770*d10b5556SXylle     *                      )
3771*d10b5556SXylle     *                      [2] => Array (
3772*d10b5556SXylle     *                          [name] => memberOf
3773*d10b5556SXylle     *                          [value] => ['CN=Staff,OU=Groups,DC=example,DC=edu', 'CN=Spanish Department,OU=Departments,OU=Groups,DC=example,DC=edu']
3774*d10b5556SXylle     *                      )
3775*d10b5556SXylle     *                  )
3776*d10b5556SXylle     *              )
3777*d10b5556SXylle     *          )
3778*d10b5556SXylle     *
3779*d10b5556SXylle     * @return array the flattened array
3780*d10b5556SXylle     *      e.g.:
3781*d10b5556SXylle     *          Array (
3782*d10b5556SXylle     *              [attribute] => Array (
3783*d10b5556SXylle     *                  [surname] => Smith
3784*d10b5556SXylle     *                  [givenName] => John
3785*d10b5556SXylle     *                  [memberOf] => Array (
3786*d10b5556SXylle     *                      [0] => CN=Staff, OU=Groups, DC=example, DC=edu
3787*d10b5556SXylle     *                      [1] => CN=Spanish Department, OU=Departments, OU=Groups, DC=example, DC=edu
3788*d10b5556SXylle     *                  )
3789*d10b5556SXylle     *              )
3790*d10b5556SXylle     *          )
3791*d10b5556SXylle     */
3792*d10b5556SXylle    private function _flatten_array($arr)
3793*d10b5556SXylle    {
3794*d10b5556SXylle        if (!is_array($arr)) {
3795*d10b5556SXylle            if ($this->_startsWith($arr, '[') && $this->_endsWith($arr, ']')) {
3796*d10b5556SXylle                return $this->_parse_json_like_array_value($arr);
3797*d10b5556SXylle            } else {
3798*d10b5556SXylle                return $arr;
3799*d10b5556SXylle            }
3800*d10b5556SXylle        }
3801*d10b5556SXylle        $out = array();
3802*d10b5556SXylle        foreach ($arr as $key => $val) {
3803*d10b5556SXylle            if (!is_array($val)) {
3804*d10b5556SXylle                $out[$key] = $val;
3805*d10b5556SXylle            } else {
3806*d10b5556SXylle                switch (count($val)) {
3807*d10b5556SXylle                case 1 : {
3808*d10b5556SXylle                        $key = key($val);
3809*d10b5556SXylle                        if (array_key_exists($key, $out)) {
3810*d10b5556SXylle                            $value = $out[$key];
3811*d10b5556SXylle                            if (!is_array($value)) {
3812*d10b5556SXylle                                $out[$key] = array();
3813*d10b5556SXylle                                $out[$key][] = $value;
3814*d10b5556SXylle                            }
3815*d10b5556SXylle                            $out[$key][] = $this->_flatten_array($val[$key]);
3816*d10b5556SXylle                        } else {
3817*d10b5556SXylle                            $out[$key] = $this->_flatten_array($val[$key]);
3818*d10b5556SXylle                        };
3819*d10b5556SXylle                        break;
3820*d10b5556SXylle                    };
3821*d10b5556SXylle                case 2 : {
3822*d10b5556SXylle                        if (array_key_exists("name", $val) && array_key_exists("value", $val)) {
3823*d10b5556SXylle                            $key = $val['name'];
3824*d10b5556SXylle                            if (array_key_exists($key, $out)) {
3825*d10b5556SXylle                                $value = $out[$key];
3826*d10b5556SXylle                                if (!is_array($value)) {
3827*d10b5556SXylle                                    $out[$key] = array();
3828*d10b5556SXylle                                    $out[$key][] = $value;
3829*d10b5556SXylle                                }
3830*d10b5556SXylle                                $out[$key][] = $this->_flatten_array($val['value']);
3831*d10b5556SXylle                            } else {
3832*d10b5556SXylle                                $out[$key] = $this->_flatten_array($val['value']);
3833*d10b5556SXylle                            };
3834*d10b5556SXylle                        } else {
3835*d10b5556SXylle                            $out[$key] = $this->_flatten_array($val);
3836*d10b5556SXylle                        }
3837*d10b5556SXylle                        break;
3838*d10b5556SXylle                    };
3839*d10b5556SXylle                default: {
3840*d10b5556SXylle                        $out[$key] = $this->_flatten_array($val);
3841*d10b5556SXylle                    }
3842*d10b5556SXylle                }
3843*d10b5556SXylle            }
3844*d10b5556SXylle        }
3845*d10b5556SXylle        return $out;
3846*d10b5556SXylle    }
3847*d10b5556SXylle
3848*d10b5556SXylle    /**
3849*d10b5556SXylle     * This method will parse the DOM and pull out the attributes from the XML
3850*d10b5556SXylle     * payload and put them into an array, then put the array into the session.
3851*d10b5556SXylle     *
3852*d10b5556SXylle     * @param DOMNodeList $success_elements payload of the response
3853*d10b5556SXylle     *
3854*d10b5556SXylle     * @return bool true when successfull, halt otherwise by calling
3855*d10b5556SXylle     * CAS_Client::_authError().
3856*d10b5556SXylle     */
3857*d10b5556SXylle    private function _readExtraAttributesCas20($success_elements)
3858*d10b5556SXylle    {
3859*d10b5556SXylle        phpCAS::traceBegin();
3860*d10b5556SXylle
3861*d10b5556SXylle        $extra_attributes = array();
3862*d10b5556SXylle        if ($this->_casAttributeParserCallbackFunction !== null
3863*d10b5556SXylle            && is_callable($this->_casAttributeParserCallbackFunction)
3864*d10b5556SXylle        ) {
3865*d10b5556SXylle            array_unshift($this->_casAttributeParserCallbackArgs, $success_elements->item(0));
3866*d10b5556SXylle            phpCAS :: trace("Calling attritubeParser callback");
3867*d10b5556SXylle            $extra_attributes =  call_user_func_array(
3868*d10b5556SXylle                $this->_casAttributeParserCallbackFunction,
3869*d10b5556SXylle                $this->_casAttributeParserCallbackArgs
3870*d10b5556SXylle            );
3871*d10b5556SXylle        } else {
3872*d10b5556SXylle            phpCAS :: trace("Parse extra attributes:    ");
3873*d10b5556SXylle            $attributes = $this->_xml_to_array($success_elements->item(0));
3874*d10b5556SXylle            phpCAS :: trace(print_r($attributes,true). "\nFLATTEN Array:    ");
3875*d10b5556SXylle            $extra_attributes = $this->_flatten_array($attributes);
3876*d10b5556SXylle            phpCAS :: trace(print_r($extra_attributes, true)."\nFILTER :    ");
3877*d10b5556SXylle            if (array_key_exists("attribute", $extra_attributes)) {
3878*d10b5556SXylle                $extra_attributes = $extra_attributes["attribute"];
3879*d10b5556SXylle            } elseif (array_key_exists("attributes", $extra_attributes)) {
3880*d10b5556SXylle                $extra_attributes = $extra_attributes["attributes"];
3881*d10b5556SXylle            };
3882*d10b5556SXylle            phpCAS :: trace(print_r($extra_attributes, true)."return");
3883*d10b5556SXylle        }
3884*d10b5556SXylle        $this->setAttributes($extra_attributes);
3885*d10b5556SXylle        phpCAS::traceEnd();
3886*d10b5556SXylle        return true;
3887*d10b5556SXylle    }
3888*d10b5556SXylle
3889*d10b5556SXylle    /**
3890*d10b5556SXylle     * Add an attribute value to an array of attributes.
3891*d10b5556SXylle     *
3892*d10b5556SXylle     * @param array  &$attributeArray reference to array
3893*d10b5556SXylle     * @param string $name            name of attribute
3894*d10b5556SXylle     * @param string $value           value of attribute
3895*d10b5556SXylle     *
3896*d10b5556SXylle     * @return void
3897*d10b5556SXylle     */
3898*d10b5556SXylle    private function _addAttributeToArray(array &$attributeArray, $name, $value)
3899*d10b5556SXylle    {
3900*d10b5556SXylle        // If multiple attributes exist, add as an array value
3901*d10b5556SXylle        if (isset($attributeArray[$name])) {
3902*d10b5556SXylle            // Initialize the array with the existing value
3903*d10b5556SXylle            if (!is_array($attributeArray[$name])) {
3904*d10b5556SXylle                $existingValue = $attributeArray[$name];
3905*d10b5556SXylle                $attributeArray[$name] = array($existingValue);
3906*d10b5556SXylle            }
3907*d10b5556SXylle
3908*d10b5556SXylle            $attributeArray[$name][] = trim($value);
3909*d10b5556SXylle        } else {
3910*d10b5556SXylle            $attributeArray[$name] = trim($value);
3911*d10b5556SXylle        }
3912*d10b5556SXylle    }
3913*d10b5556SXylle
3914*d10b5556SXylle    /** @} */
3915*d10b5556SXylle
3916*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3917*d10b5556SXylle    // XX                                                                    XX
3918*d10b5556SXylle    // XX                               MISC                                 XX
3919*d10b5556SXylle    // XX                                                                    XX
3920*d10b5556SXylle    // XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
3921*d10b5556SXylle
3922*d10b5556SXylle    /**
3923*d10b5556SXylle     * @addtogroup internalMisc
3924*d10b5556SXylle     * @{
3925*d10b5556SXylle     */
3926*d10b5556SXylle
3927*d10b5556SXylle    // ########################################################################
3928*d10b5556SXylle    //  URL
3929*d10b5556SXylle    // ########################################################################
3930*d10b5556SXylle    /**
3931*d10b5556SXylle    * the URL of the current request (without any ticket CGI parameter). Written
3932*d10b5556SXylle    * and read by CAS_Client::getURL().
3933*d10b5556SXylle    *
3934*d10b5556SXylle    * @hideinitializer
3935*d10b5556SXylle    */
3936*d10b5556SXylle    private $_url = '';
3937*d10b5556SXylle
3938*d10b5556SXylle
3939*d10b5556SXylle    /**
3940*d10b5556SXylle     * This method sets the URL of the current request
3941*d10b5556SXylle     *
3942*d10b5556SXylle     * @param string $url url to set for service
3943*d10b5556SXylle     *
3944*d10b5556SXylle     * @return void
3945*d10b5556SXylle     */
3946*d10b5556SXylle    public function setURL($url)
3947*d10b5556SXylle    {
3948*d10b5556SXylle        // Argument Validation
3949*d10b5556SXylle        if (gettype($url) != 'string')
3950*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
3951*d10b5556SXylle
3952*d10b5556SXylle        $this->_url = $url;
3953*d10b5556SXylle    }
3954*d10b5556SXylle
3955*d10b5556SXylle    /**
3956*d10b5556SXylle     * This method returns the URL of the current request (without any ticket
3957*d10b5556SXylle     * CGI parameter).
3958*d10b5556SXylle     *
3959*d10b5556SXylle     * @return string The URL
3960*d10b5556SXylle     */
3961*d10b5556SXylle    public function getURL()
3962*d10b5556SXylle    {
3963*d10b5556SXylle        phpCAS::traceBegin();
3964*d10b5556SXylle        // the URL is built when needed only
3965*d10b5556SXylle        if ( empty($this->_url) ) {
3966*d10b5556SXylle            // remove the ticket if present in the URL
3967*d10b5556SXylle            $final_uri = $this->getServiceBaseUrl()->get();
3968*d10b5556SXylle            $request_uri = explode('?', $_SERVER['REQUEST_URI'], 2);
3969*d10b5556SXylle            $final_uri .= $request_uri[0];
3970*d10b5556SXylle
3971*d10b5556SXylle            if (isset($request_uri[1]) && $request_uri[1]) {
3972*d10b5556SXylle                $query_string= $this->_removeParameterFromQueryString('ticket', $request_uri[1]);
3973*d10b5556SXylle
3974*d10b5556SXylle                // If the query string still has anything left,
3975*d10b5556SXylle                // append it to the final URI
3976*d10b5556SXylle                if ($query_string !== '') {
3977*d10b5556SXylle                    $final_uri .= "?$query_string";
3978*d10b5556SXylle                }
3979*d10b5556SXylle            }
3980*d10b5556SXylle
3981*d10b5556SXylle            phpCAS::trace("Final URI: $final_uri");
3982*d10b5556SXylle            $this->setURL($final_uri);
3983*d10b5556SXylle        }
3984*d10b5556SXylle        phpCAS::traceEnd($this->_url);
3985*d10b5556SXylle        return $this->_url;
3986*d10b5556SXylle    }
3987*d10b5556SXylle
3988*d10b5556SXylle    /**
3989*d10b5556SXylle     * This method sets the base URL of the CAS server.
3990*d10b5556SXylle     *
3991*d10b5556SXylle     * @param string $url the base URL
3992*d10b5556SXylle     *
3993*d10b5556SXylle     * @return string base url
3994*d10b5556SXylle     */
3995*d10b5556SXylle    public function setBaseURL($url)
3996*d10b5556SXylle    {
3997*d10b5556SXylle        // Argument Validation
3998*d10b5556SXylle        if (gettype($url) != 'string')
3999*d10b5556SXylle            throw new CAS_TypeMismatchException($url, '$url', 'string');
4000*d10b5556SXylle
4001*d10b5556SXylle        return $this->_server['base_url'] = $url;
4002*d10b5556SXylle    }
4003*d10b5556SXylle
4004*d10b5556SXylle    /**
4005*d10b5556SXylle     * The ServiceBaseUrl object that provides base URL during service URL
4006*d10b5556SXylle     * discovery process.
4007*d10b5556SXylle     *
4008*d10b5556SXylle     * @var CAS_ServiceBaseUrl_Interface
4009*d10b5556SXylle     *
4010*d10b5556SXylle     * @hideinitializer
4011*d10b5556SXylle     */
4012*d10b5556SXylle    private $_serviceBaseUrl = null;
4013*d10b5556SXylle
4014*d10b5556SXylle    /**
4015*d10b5556SXylle     * Answer the CAS_ServiceBaseUrl_Interface object for this client.
4016*d10b5556SXylle     *
4017*d10b5556SXylle     * @return CAS_ServiceBaseUrl_Interface
4018*d10b5556SXylle     */
4019*d10b5556SXylle    public function getServiceBaseUrl()
4020*d10b5556SXylle    {
4021*d10b5556SXylle        if (empty($this->_serviceBaseUrl)) {
4022*d10b5556SXylle            phpCAS::error("ServiceBaseUrl object is not initialized");
4023*d10b5556SXylle        }
4024*d10b5556SXylle        return $this->_serviceBaseUrl;
4025*d10b5556SXylle    }
4026*d10b5556SXylle
4027*d10b5556SXylle    /**
4028*d10b5556SXylle     * This method sets the service base URL used during service URL discovery process.
4029*d10b5556SXylle     *
4030*d10b5556SXylle     * This is required since phpCAS 1.6.0 to protect the integrity of the authentication.
4031*d10b5556SXylle     *
4032*d10b5556SXylle     * @since phpCAS 1.6.0
4033*d10b5556SXylle     *
4034*d10b5556SXylle     * @param $name can be any of the following:
4035*d10b5556SXylle     *   - A base URL string. The service URL discovery will always use this (protocol,
4036*d10b5556SXylle     *     hostname and optional port number) without using any external host names.
4037*d10b5556SXylle     *   - An array of base URL strings. The service URL discovery will check against
4038*d10b5556SXylle     *     this list before using the auto discovered base URL. If there is no match,
4039*d10b5556SXylle     *     the first base URL in the array will be used as the default. This option is
4040*d10b5556SXylle     *     helpful if your PHP website is accessible through multiple domains without a
4041*d10b5556SXylle     *     canonical name, or through both HTTP and HTTPS.
4042*d10b5556SXylle     *   - A class that implements CAS_ServiceBaseUrl_Interface. If you need to customize
4043*d10b5556SXylle     *     the base URL discovery behavior, you can pass in a class that implements the
4044*d10b5556SXylle     *     interface.
4045*d10b5556SXylle     *
4046*d10b5556SXylle     * @return void
4047*d10b5556SXylle     */
4048*d10b5556SXylle    private function _setServiceBaseUrl($name)
4049*d10b5556SXylle    {
4050*d10b5556SXylle        if (is_array($name)) {
4051*d10b5556SXylle            $this->_serviceBaseUrl = new CAS_ServiceBaseUrl_AllowedListDiscovery($name);
4052*d10b5556SXylle        } else if (is_string($name)) {
4053*d10b5556SXylle            $this->_serviceBaseUrl = new CAS_ServiceBaseUrl_Static($name);
4054*d10b5556SXylle        } else if ($name instanceof CAS_ServiceBaseUrl_Interface) {
4055*d10b5556SXylle            $this->_serviceBaseUrl = $name;
4056*d10b5556SXylle        } else {
4057*d10b5556SXylle            throw new CAS_TypeMismatchException($name, '$name', 'array, string, or CAS_ServiceBaseUrl_Interface object');
4058*d10b5556SXylle        }
4059*d10b5556SXylle    }
4060*d10b5556SXylle
4061*d10b5556SXylle    /**
4062*d10b5556SXylle     * Removes a parameter from a query string
4063*d10b5556SXylle     *
4064*d10b5556SXylle     * @param string $parameterName name of parameter
4065*d10b5556SXylle     * @param string $queryString   query string
4066*d10b5556SXylle     *
4067*d10b5556SXylle     * @return string new query string
4068*d10b5556SXylle     *
4069*d10b5556SXylle     * @link http://stackoverflow.com/questions/1842681/regular-expression-to-remove-one-parameter-from-query-string
4070*d10b5556SXylle     */
4071*d10b5556SXylle    private function _removeParameterFromQueryString($parameterName, $queryString)
4072*d10b5556SXylle    {
4073*d10b5556SXylle        $parameterName	= preg_quote($parameterName);
4074*d10b5556SXylle        return preg_replace(
4075*d10b5556SXylle            "/&$parameterName(=[^&]*)?|^$parameterName(=[^&]*)?&?/",
4076*d10b5556SXylle            '', $queryString
4077*d10b5556SXylle        );
4078*d10b5556SXylle    }
4079*d10b5556SXylle
4080*d10b5556SXylle    /**
4081*d10b5556SXylle     * This method is used to append query parameters to an url. Since the url
4082*d10b5556SXylle     * might already contain parameter it has to be detected and to build a proper
4083*d10b5556SXylle     * URL
4084*d10b5556SXylle     *
4085*d10b5556SXylle     * @param string $url   base url to add the query params to
4086*d10b5556SXylle     * @param string $query params in query form with & separated
4087*d10b5556SXylle     *
4088*d10b5556SXylle     * @return string url with query params
4089*d10b5556SXylle     */
4090*d10b5556SXylle    private function _buildQueryUrl($url, $query)
4091*d10b5556SXylle    {
4092*d10b5556SXylle        $url .= (strstr($url, '?') === false) ? '?' : '&';
4093*d10b5556SXylle        $url .= $query;
4094*d10b5556SXylle        return $url;
4095*d10b5556SXylle    }
4096*d10b5556SXylle
4097*d10b5556SXylle    /**
4098*d10b5556SXylle     * This method tests if a string starts with a given character.
4099*d10b5556SXylle     *
4100*d10b5556SXylle     * @param string $text  text to test
4101*d10b5556SXylle     * @param string $char  character to test for
4102*d10b5556SXylle     *
4103*d10b5556SXylle     * @return bool          true if the $text starts with $char
4104*d10b5556SXylle     */
4105*d10b5556SXylle    private function _startsWith($text, $char)
4106*d10b5556SXylle    {
4107*d10b5556SXylle        return (strpos($text, $char) === 0);
4108*d10b5556SXylle    }
4109*d10b5556SXylle
4110*d10b5556SXylle    /**
4111*d10b5556SXylle     * This method tests if a string ends with a given character
4112*d10b5556SXylle     *
4113*d10b5556SXylle     * @param string $text  text to test
4114*d10b5556SXylle     * @param string $char  character to test for
4115*d10b5556SXylle     *
4116*d10b5556SXylle     * @return bool         true if the $text ends with $char
4117*d10b5556SXylle     */
4118*d10b5556SXylle    private function _endsWith($text, $char)
4119*d10b5556SXylle    {
4120*d10b5556SXylle        return (strpos(strrev($text), $char) === 0);
4121*d10b5556SXylle    }
4122*d10b5556SXylle
4123*d10b5556SXylle    /**
4124*d10b5556SXylle     * Answer a valid session-id given a CAS ticket.
4125*d10b5556SXylle     *
4126*d10b5556SXylle     * The output must be deterministic to allow single-log-out when presented with
4127*d10b5556SXylle     * the ticket to log-out.
4128*d10b5556SXylle     *
4129*d10b5556SXylle     *
4130*d10b5556SXylle     * @param string $ticket name of the ticket
4131*d10b5556SXylle     *
4132*d10b5556SXylle     * @return string
4133*d10b5556SXylle     */
4134*d10b5556SXylle    private function _sessionIdForTicket($ticket)
4135*d10b5556SXylle    {
4136*d10b5556SXylle      // Hash the ticket to ensure that the value meets the PHP 7.1 requirement
4137*d10b5556SXylle      // that session-ids have a length between 22 and 256 characters.
4138*d10b5556SXylle      return hash('sha256', $this->_sessionIdSalt . $ticket);
4139*d10b5556SXylle    }
4140*d10b5556SXylle
4141*d10b5556SXylle    /**
4142*d10b5556SXylle     * Set a salt/seed for the session-id hash to make it harder to guess.
4143*d10b5556SXylle     *
4144*d10b5556SXylle     * @var string $_sessionIdSalt
4145*d10b5556SXylle     */
4146*d10b5556SXylle    private $_sessionIdSalt = '';
4147*d10b5556SXylle
4148*d10b5556SXylle    /**
4149*d10b5556SXylle     * Set a salt/seed for the session-id hash to make it harder to guess.
4150*d10b5556SXylle     *
4151*d10b5556SXylle     * @param string $salt
4152*d10b5556SXylle     *
4153*d10b5556SXylle     * @return void
4154*d10b5556SXylle     */
4155*d10b5556SXylle    public function setSessionIdSalt($salt) {
4156*d10b5556SXylle      $this->_sessionIdSalt = (string)$salt;
4157*d10b5556SXylle    }
4158*d10b5556SXylle
4159*d10b5556SXylle    // ########################################################################
4160*d10b5556SXylle    //  AUTHENTICATION ERROR HANDLING
4161*d10b5556SXylle    // ########################################################################
4162*d10b5556SXylle    /**
4163*d10b5556SXylle    * This method is used to print the HTML output when the user was not
4164*d10b5556SXylle    * authenticated.
4165*d10b5556SXylle    *
4166*d10b5556SXylle    * @param string $failure      the failure that occured
4167*d10b5556SXylle    * @param string $cas_url      the URL the CAS server was asked for
4168*d10b5556SXylle    * @param bool   $no_response  the response from the CAS server (other
4169*d10b5556SXylle    * parameters are ignored if true)
4170*d10b5556SXylle    * @param bool   $bad_response bad response from the CAS server ($err_code
4171*d10b5556SXylle    * and $err_msg ignored if true)
4172*d10b5556SXylle    * @param string $cas_response the response of the CAS server
4173*d10b5556SXylle    * @param int    $err_code     the error code given by the CAS server
4174*d10b5556SXylle    * @param string $err_msg      the error message given by the CAS server
4175*d10b5556SXylle    *
4176*d10b5556SXylle    * @return void
4177*d10b5556SXylle    */
4178*d10b5556SXylle    private function _authError(
4179*d10b5556SXylle        $failure,
4180*d10b5556SXylle        $cas_url,
4181*d10b5556SXylle        $no_response=false,
4182*d10b5556SXylle        $bad_response=false,
4183*d10b5556SXylle        $cas_response='',
4184*d10b5556SXylle        $err_code=-1,
4185*d10b5556SXylle        $err_msg=''
4186*d10b5556SXylle    ) {
4187*d10b5556SXylle        phpCAS::traceBegin();
4188*d10b5556SXylle        $lang = $this->getLangObj();
4189*d10b5556SXylle        $this->printHTMLHeader($lang->getAuthenticationFailed());
4190*d10b5556SXylle        $this->printf(
4191*d10b5556SXylle            $lang->getYouWereNotAuthenticated(), htmlentities($this->getURL()),
4192*d10b5556SXylle            isset($_SERVER['SERVER_ADMIN']) ? $_SERVER['SERVER_ADMIN']:''
4193*d10b5556SXylle        );
4194*d10b5556SXylle        phpCAS::trace('CAS URL: '.$cas_url);
4195*d10b5556SXylle        phpCAS::trace('Authentication failure: '.$failure);
4196*d10b5556SXylle        if ( $no_response ) {
4197*d10b5556SXylle            phpCAS::trace('Reason: no response from the CAS server');
4198*d10b5556SXylle        } else {
4199*d10b5556SXylle            if ( $bad_response ) {
4200*d10b5556SXylle                phpCAS::trace('Reason: bad response from the CAS server');
4201*d10b5556SXylle            } else {
4202*d10b5556SXylle                switch ($this->getServerVersion()) {
4203*d10b5556SXylle                case CAS_VERSION_1_0:
4204*d10b5556SXylle                    phpCAS::trace('Reason: CAS error');
4205*d10b5556SXylle                    break;
4206*d10b5556SXylle                case CAS_VERSION_2_0:
4207*d10b5556SXylle                case CAS_VERSION_3_0:
4208*d10b5556SXylle                    if ( $err_code === -1 ) {
4209*d10b5556SXylle                        phpCAS::trace('Reason: no CAS error');
4210*d10b5556SXylle                    } else {
4211*d10b5556SXylle                        phpCAS::trace(
4212*d10b5556SXylle                            'Reason: ['.$err_code.'] CAS error: '.$err_msg
4213*d10b5556SXylle                        );
4214*d10b5556SXylle                    }
4215*d10b5556SXylle                    break;
4216*d10b5556SXylle                }
4217*d10b5556SXylle            }
4218*d10b5556SXylle            phpCAS::trace('CAS response: '.$cas_response);
4219*d10b5556SXylle        }
4220*d10b5556SXylle        $this->printHTMLFooter();
4221*d10b5556SXylle        phpCAS::traceExit();
4222*d10b5556SXylle        throw new CAS_GracefullTerminationException();
4223*d10b5556SXylle    }
4224*d10b5556SXylle
4225*d10b5556SXylle    // ########################################################################
4226*d10b5556SXylle    //  PGTIOU/PGTID and logoutRequest rebroadcasting
4227*d10b5556SXylle    // ########################################################################
4228*d10b5556SXylle
4229*d10b5556SXylle    /**
4230*d10b5556SXylle     * Boolean of whether to rebroadcast pgtIou/pgtId and logoutRequest, and
4231*d10b5556SXylle     * array of the nodes.
4232*d10b5556SXylle     */
4233*d10b5556SXylle    private $_rebroadcast = false;
4234*d10b5556SXylle    private $_rebroadcast_nodes = array();
4235*d10b5556SXylle
4236*d10b5556SXylle    /**
4237*d10b5556SXylle     * Constants used for determining rebroadcast node type.
4238*d10b5556SXylle     */
4239*d10b5556SXylle    const HOSTNAME = 0;
4240*d10b5556SXylle    const IP = 1;
4241*d10b5556SXylle
4242*d10b5556SXylle    /**
4243*d10b5556SXylle     * Determine the node type from the URL.
4244*d10b5556SXylle     *
4245*d10b5556SXylle     * @param String $nodeURL The node URL.
4246*d10b5556SXylle     *
4247*d10b5556SXylle     * @return int hostname
4248*d10b5556SXylle     *
4249*d10b5556SXylle     */
4250*d10b5556SXylle    private function _getNodeType($nodeURL)
4251*d10b5556SXylle    {
4252*d10b5556SXylle        phpCAS::traceBegin();
4253*d10b5556SXylle        if (preg_match("/^(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})$/", $nodeURL)) {
4254*d10b5556SXylle            phpCAS::traceEnd(self::IP);
4255*d10b5556SXylle            return self::IP;
4256*d10b5556SXylle        } else {
4257*d10b5556SXylle            phpCAS::traceEnd(self::HOSTNAME);
4258*d10b5556SXylle            return self::HOSTNAME;
4259*d10b5556SXylle        }
4260*d10b5556SXylle    }
4261*d10b5556SXylle
4262*d10b5556SXylle    /**
4263*d10b5556SXylle     * Store the rebroadcast node for pgtIou/pgtId and logout requests.
4264*d10b5556SXylle     *
4265*d10b5556SXylle     * @param string $rebroadcastNodeUrl The rebroadcast node URL.
4266*d10b5556SXylle     *
4267*d10b5556SXylle     * @return void
4268*d10b5556SXylle     */
4269*d10b5556SXylle    public function addRebroadcastNode($rebroadcastNodeUrl)
4270*d10b5556SXylle    {
4271*d10b5556SXylle        // Argument validation
4272*d10b5556SXylle        if ( !(bool)preg_match("/^(http|https):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i", $rebroadcastNodeUrl))
4273*d10b5556SXylle            throw new CAS_TypeMismatchException($rebroadcastNodeUrl, '$rebroadcastNodeUrl', 'url');
4274*d10b5556SXylle
4275*d10b5556SXylle        // Store the rebroadcast node and set flag
4276*d10b5556SXylle        $this->_rebroadcast = true;
4277*d10b5556SXylle        $this->_rebroadcast_nodes[] = $rebroadcastNodeUrl;
4278*d10b5556SXylle    }
4279*d10b5556SXylle
4280*d10b5556SXylle    /**
4281*d10b5556SXylle     * An array to store extra rebroadcast curl options.
4282*d10b5556SXylle     */
4283*d10b5556SXylle    private $_rebroadcast_headers = array();
4284*d10b5556SXylle
4285*d10b5556SXylle    /**
4286*d10b5556SXylle     * This method is used to add header parameters when rebroadcasting
4287*d10b5556SXylle     * pgtIou/pgtId or logoutRequest.
4288*d10b5556SXylle     *
4289*d10b5556SXylle     * @param string $header Header to send when rebroadcasting.
4290*d10b5556SXylle     *
4291*d10b5556SXylle     * @return void
4292*d10b5556SXylle     */
4293*d10b5556SXylle    public function addRebroadcastHeader($header)
4294*d10b5556SXylle    {
4295*d10b5556SXylle        if (gettype($header) != 'string')
4296*d10b5556SXylle            throw new CAS_TypeMismatchException($header, '$header', 'string');
4297*d10b5556SXylle
4298*d10b5556SXylle        $this->_rebroadcast_headers[] = $header;
4299*d10b5556SXylle    }
4300*d10b5556SXylle
4301*d10b5556SXylle    /**
4302*d10b5556SXylle     * Constants used for determining rebroadcast type (logout or pgtIou/pgtId).
4303*d10b5556SXylle     */
4304*d10b5556SXylle    const LOGOUT = 0;
4305*d10b5556SXylle    const PGTIOU = 1;
4306*d10b5556SXylle
4307*d10b5556SXylle    /**
4308*d10b5556SXylle     * This method rebroadcasts logout/pgtIou requests. Can be LOGOUT,PGTIOU
4309*d10b5556SXylle     *
4310*d10b5556SXylle     * @param int $type type of rebroadcasting.
4311*d10b5556SXylle     *
4312*d10b5556SXylle     * @return void
4313*d10b5556SXylle     */
4314*d10b5556SXylle    private function _rebroadcast($type)
4315*d10b5556SXylle    {
4316*d10b5556SXylle        phpCAS::traceBegin();
4317*d10b5556SXylle
4318*d10b5556SXylle        $rebroadcast_curl_options = array(
4319*d10b5556SXylle        CURLOPT_FAILONERROR => 1,
4320*d10b5556SXylle        CURLOPT_FOLLOWLOCATION => 1,
4321*d10b5556SXylle        CURLOPT_RETURNTRANSFER => 1,
4322*d10b5556SXylle        CURLOPT_CONNECTTIMEOUT => 1,
4323*d10b5556SXylle        CURLOPT_TIMEOUT => 4);
4324*d10b5556SXylle
4325*d10b5556SXylle        // Try to determine the IP address of the server
4326*d10b5556SXylle        if (!empty($_SERVER['SERVER_ADDR'])) {
4327*d10b5556SXylle            $ip = $_SERVER['SERVER_ADDR'];
4328*d10b5556SXylle        } else if (!empty($_SERVER['LOCAL_ADDR'])) {
4329*d10b5556SXylle            // IIS 7
4330*d10b5556SXylle            $ip = $_SERVER['LOCAL_ADDR'];
4331*d10b5556SXylle        }
4332*d10b5556SXylle        // Try to determine the DNS name of the server
4333*d10b5556SXylle        if (!empty($ip)) {
4334*d10b5556SXylle            $dns = gethostbyaddr($ip);
4335*d10b5556SXylle        }
4336*d10b5556SXylle        $multiClassName = 'CAS_Request_CurlMultiRequest';
4337*d10b5556SXylle        $multiRequest = new $multiClassName();
4338*d10b5556SXylle
4339*d10b5556SXylle        for ($i = 0; $i < sizeof($this->_rebroadcast_nodes); $i++) {
4340*d10b5556SXylle            if ((($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::HOSTNAME) && !empty($dns) && (stripos($this->_rebroadcast_nodes[$i], $dns) === false))
4341*d10b5556SXylle                || (($this->_getNodeType($this->_rebroadcast_nodes[$i]) == self::IP) && !empty($ip) && (stripos($this->_rebroadcast_nodes[$i], $ip) === false))
4342*d10b5556SXylle            ) {
4343*d10b5556SXylle                phpCAS::trace(
4344*d10b5556SXylle                    'Rebroadcast target URL: '.$this->_rebroadcast_nodes[$i]
4345*d10b5556SXylle                    .$_SERVER['REQUEST_URI']
4346*d10b5556SXylle                );
4347*d10b5556SXylle                $className = $this->_requestImplementation;
4348*d10b5556SXylle                $request = new $className();
4349*d10b5556SXylle
4350*d10b5556SXylle                $url = $this->_rebroadcast_nodes[$i].$_SERVER['REQUEST_URI'];
4351*d10b5556SXylle                $request->setUrl($url);
4352*d10b5556SXylle
4353*d10b5556SXylle                if (count($this->_rebroadcast_headers)) {
4354*d10b5556SXylle                    $request->addHeaders($this->_rebroadcast_headers);
4355*d10b5556SXylle                }
4356*d10b5556SXylle
4357*d10b5556SXylle                $request->makePost();
4358*d10b5556SXylle                if ($type == self::LOGOUT) {
4359*d10b5556SXylle                    // Logout request
4360*d10b5556SXylle                    $request->setPostBody(
4361*d10b5556SXylle                        'rebroadcast=false&logoutRequest='.$_POST['logoutRequest']
4362*d10b5556SXylle                    );
4363*d10b5556SXylle                } else if ($type == self::PGTIOU) {
4364*d10b5556SXylle                    // pgtIou/pgtId rebroadcast
4365*d10b5556SXylle                    $request->setPostBody('rebroadcast=false');
4366*d10b5556SXylle                }
4367*d10b5556SXylle
4368*d10b5556SXylle                $request->setCurlOptions($rebroadcast_curl_options);
4369*d10b5556SXylle
4370*d10b5556SXylle                $multiRequest->addRequest($request);
4371*d10b5556SXylle            } else {
4372*d10b5556SXylle                phpCAS::trace(
4373*d10b5556SXylle                    'Rebroadcast not sent to self: '
4374*d10b5556SXylle                    .$this->_rebroadcast_nodes[$i].' == '.(!empty($ip)?$ip:'')
4375*d10b5556SXylle                    .'/'.(!empty($dns)?$dns:'')
4376*d10b5556SXylle                );
4377*d10b5556SXylle            }
4378*d10b5556SXylle        }
4379*d10b5556SXylle        // We need at least 1 request
4380*d10b5556SXylle        if ($multiRequest->getNumRequests() > 0) {
4381*d10b5556SXylle            $multiRequest->send();
4382*d10b5556SXylle        }
4383*d10b5556SXylle        phpCAS::traceEnd();
4384*d10b5556SXylle    }
4385*d10b5556SXylle
4386*d10b5556SXylle    /** @} */
4387*d10b5556SXylle}
4388