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

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