mirror of https://github.com/jacekkow/uphpCAS

Jacek Kowalski
2020-03-16 c0b106aec5b09847f6c9e2b452d0eac6fe8061cf
commit | author | age
64d82d 1 <?php
JK 2 // Thrown when internal error occurs
3 class JasigException extends Exception {}
2f13b1 4 // Thrown when CAS server returns authentication error
64d82d 5 class JasigAuthException extends JasigException {}
JK 6
7 class JasigUser {
8     public $user;
9     public $attributes = array();
10 }
11
12 class uphpCAS {
13     const VERSION = '1.0';
14     protected $serverUrl = '';
15     protected $serviceUrl;
710793 16     protected $sessionName = 'uphpCAS-user';
8e8f55 17     protected $method = 'POST';
JK 18     protected $caFile = NULL;
64d82d 19     
710793 20     function __construct($serverUrl = NULL, $serviceUrl = NULL, $sessionName = NULL) {
64d82d 21         if($serverUrl != NULL) {
JK 22             $this->serverUrl = rtrim($serverUrl, '/');
23         }
24         
25         if($serviceUrl != NULL) {
26             $this->serviceUrl = $serviceUrl;
27         } else {
8891c7 28             $this->serviceUrl = $this->getCurrentUrl();
64d82d 29         }
710793 30         
JK 31         if($sessionName) {
32             $this->sessionName = $sessionName;
8e8f55 33         }
JK 34         
35         if(version_compare(PHP_VERSION, '5.6', '<')) {
36             $this->caFile = $this->findCaFile();
710793 37         }
64d82d 38     }
JK 39     
8891c7 40     public function getCurrentUrl() {
JK 41         $url = 'http://';
42         $port = 0;
43         if(isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
44             $url = 'https://';
45             if(isset($_SERVER['SERVER_PORT'])
46                     && $_SERVER['SERVER_PORT'] != '443') {
47                 $port = $_SERVER['SERVER_PORT'];
48             }
49         } elseif(isset($_SERVER['SERVER_PORT'])
50                 && $_SERVER['SERVER_PORT'] != '80') {
51             $port = $_SERVER['SERVER_PORT'];
52         }
53         
54         $url .= $_SERVER['SERVER_NAME'];
55         
56         if($port != 0) {
57             $url .= ':'.$port;
58         }
44b838 59         
8891c7 60         $url .= $_SERVER['REQUEST_URI'];
2b2985 61         
44b838 62         if(isset($_GET['ticket'])) {
JK 63             $pos = max(
64                 strrpos($url, '?ticket='),
65                 strrpos($url, '&ticket=')
66             );
67             $url = substr($url, 0, $pos);
68         }
69         
2b2985 70         return $url;
8891c7 71     }
JK 72     
deadb1 73     public function getServerUrl() {
bae7f7 74         return $this->serverUrl;
JK 75     }
64d82d 76     public function setServerUrl($serverUrl) {
JK 77         $this->serverUrl = $serverUrl;
78     }
79     
bae7f7 80     public function getServiceUrl() {
JK 81         return $this->serviceUrl;
82     }
64d82d 83     public function setServiceUrl($serviceUrl) {
JK 84         $this->serviceUrl = $serviceUrl;
85     }
86     
165b17 87     public function getSessionName() {
710793 88         return $this->sessionName;
JK 89     }
90     public function setSessionName($sessionName) {
91         $this->sessionName = $sessionName;
92     }
93     
8e8f55 94     public function getMethod() {
JK 95         return $this->method;
96     }
97     public function setMethod($method) {
98         if($method != 'GET' && $method != 'POST') {
99             throw new DomainException('Unsupported CAS response'
100                 .' method: '.$method);
101         }
102         $this->method = $method;
103     }
104     
105     public function getCaFile() {
106         return $this->caFile;
107     }
108     public function setCaFile($caFile) {
109         if(!is_file($caFile)) {
110             throw new DomainException('Invalid CA file: '.$caFile);
111         }
112         $this->caFile = $caFile;
113     }
114     
64d82d 115     public function loginUrl() {
8e8f55 116         return $this->serverUrl.'/login?method='.$this->method
JK 117             .'&service='.urlencode($this->serviceUrl);
64d82d 118     }
JK 119     
f124d4 120     public function logoutUrl($returnUrl = NULL) {
8e8f55 121         return $this->serverUrl.'/logout'
JK 122             .($returnUrl ? '?service='.urlencode($returnUrl) : '');
64d82d 123     }
JK 124     
467a55 125     public function logoutLocal() {
041819 126         @session_start();
467a55 127         unset($_SESSION[$this->sessionName]);
JK 128     }
129     
130     public function logout($returnUrl = NULL) {
131         $this->logoutLocal();
cc5e29 132         if($this->isAuthenticated()) {
JK 133             header('Location: '.$this->logoutUrl($returnUrl));
134             die();
135         } elseif($returnUrl) {
136             header('Location: '.$returnUrl);
137             die();
64d82d 138         }
JK 139     }
140     
fc49b8 141     public function isAuthenticated() {
710793 142         return isset($_SESSION[$this->sessionName]);
fc49b8 143     }
JK 144     
64d82d 145     public function authenticate() {
041819 146         @session_start();
fc49b8 147         if($this->isAuthenticated()) {
710793 148             return $_SESSION[$this->sessionName];
2092d3 149         } elseif(isset($_REQUEST['ticket'])) {
JK 150             $user = $this->verifyTicket($_REQUEST['ticket']);
ccc18b 151             session_regenerate_id();
710793 152             $_SESSION[$this->sessionName] = $user;
64d82d 153             return $user;
JK 154         } else {
155             header('Location: '.$this->loginUrl());
156             die();
157         }
158     }
159     
501c90 160     protected function findCaFile() {
JK 161         $cafiles = array(
162             '/etc/ssl/certs/ca-certificates.crt',
163             '/etc/ssl/certs/ca-bundle.crt',
164             '/etc/pki/tls/certs/ca-bundle.crt',
165         );
166         
167         $cafile = NULL;
168         foreach($cafiles as $file) {
169             if(is_file($file)) {
170                 $cafile = $file;
171                 break;
172             }
173         }
174         
175         return $cafile;
176     }
177     
fa3e92 178     protected function createStreamContext($hostname) {
8e8f55 179         $context = array(
64d82d 180             'http' => array(
JK 181                 'method' => 'GET',
182                 'user_agent' => 'uphpCAS/'.self::VERSION,
183                 'max_redirects' => 3,
184             ),
185             'ssl' => array(
186                 'verify_peer' => TRUE,
90ae5b 187                 'verify_peer_name' => TRUE,
64d82d 188                 'verify_depth' => 5,
90ae5b 189                 'allow_self_signed' => FALSE,
JK 190                 'disable_compression' => TRUE,
64d82d 191             ),
8e8f55 192         );
64d82d 193         
8e8f55 194         if($this->caFile) {
JK 195             $context['ssl']['cafile'] = $this->caFile;
d35cf4 196         }
fa3e92 197         
8e8f55 198         if(version_compare(PHP_VERSION, '5.6', '<')) {
JK 199             $context['ssl']['ciphers'] = 'ECDH:DH:AES:CAMELLIA:!SSLv2:!aNULL'
200                 .':!eNULL:!EXPORT:!DES:!3DES:!MD5:!RC4:!ADH:!PSK:!SRP';
201             $context['ssl']['CN_match'] = $hostname;
202         }
203         
204         return stream_context_create($context);
fa3e92 205     }
JK 206     
207     public function verifyTicket($ticket) {
208         $url = parse_url($this->serverUrl);
209         $context = $this->createStreamContext($url['host']);
d35cf4 210         
90ae5b 211         $data = file_get_contents($this->serverUrl
JK 212                     .'/serviceValidate?service='.urlencode($this->serviceUrl)
fa3e92 213                     .'&ticket='.urlencode($ticket), FALSE, $context);
64d82d 214         if($data === FALSE) {
JK 215             throw new JasigException('Authentication error: CAS server is unavailable');
216         }
217         
218         $xmlEntityLoader = libxml_disable_entity_loader(TRUE);
219         $xmlInternalErrors = libxml_use_internal_errors(TRUE);
220         try {
221             $xml = new DOMDocument();
222             $xml->loadXML($data);
223             
224             foreach(libxml_get_errors() as $error) {
90ae5b 225                 $e = new ErrorException($error->message, $error->code, 1,
JK 226                         $error->file, $error->line);
64d82d 227                 switch ($error->level) {
JK 228                     case LIBXML_ERR_ERROR:
229                     case LIBXML_ERR_FATAL:
90ae5b 230                         throw new Exception('Fatal error during XML parsing',
JK 231                                 0, $e);
64d82d 232                         break;
JK 233                 }
234             }
2f13b1 235         } catch(Exception $e) {
64d82d 236             libxml_clear_errors();
JK 237             libxml_disable_entity_loader($xmlEntityLoader);
238             libxml_use_internal_errors($xmlInternalErrors);
a66017 239             throw new JasigException('Authentication error: CAS server'
JK 240                     .' response invalid - parse error', 0, $e);
64d82d 241         }
a66017 242         libxml_clear_errors();
JK 243         libxml_disable_entity_loader($xmlEntityLoader);
244         libxml_use_internal_errors($xmlInternalErrors);
64d82d 245         
JK 246         $failure = $xml->getElementsByTagName('authenticationFailure');
247         $success = $xml->getElementsByTagName('authenticationSuccess');
248         
249         if($failure->length > 0) {
250             $failure = $failure->item(0);
251             if(!($failure instanceof DOMElement)) {
90ae5b 252                 throw new JasigException('Authentication error: CAS server'
JK 253                         .' response invalid - authenticationFailure');
64d82d 254             }
90ae5b 255             throw new JasigAuthException('Authentication error: '
JK 256                     .$failure->textContent);
64d82d 257         } elseif($success->length > 0) {
JK 258             $success = $success->item(0);
259             if(!($success instanceof DOMElement)) {
90ae5b 260                 throw new JasigException('Authentication error: CAS server'
JK 261                         .' response invalid - authenticationSuccess');
64d82d 262             }
JK 263             
264             $user = $success->getElementsByTagName('user');
265             if($user->length == 0) {
90ae5b 266                 throw new JasigException('Authentication error: CAS server'
JK 267                         .' response invalid - user');
64d82d 268             }
JK 269             
270             $user = trim($user->item(0)->textContent);
2f13b1 271             if(strlen($user) < 1) {
90ae5b 272                 throw new JasigException('Authentication error: CAS server'
JK 273                         .' response invalid - user value');
64d82d 274             }
JK 275             
276             $jusr = new JasigUser();
277             $jusr->user = $user;
278             
279             $attrs = $success->getElementsByTagName('attributes');
280             if($attrs->length > 0) {
281                 $attrs = $attrs->item(0);
282                 foreach($attrs->childNodes as $node) {
283                     $jusr->attributes[$node->localName] = $node->textContent;
284                 }
285             }
286             
287             return $jusr;
2f13b1 288         } else {
90ae5b 289             throw new JasigException('Authentication error: CAS server'
JK 290                     .' response invalid - required tag not found');
64d82d 291         }
JK 292     }
293 }