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

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