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

Jacek Kowalski
2019-08-07 467a55acd9651c8a9ce7367b7c7f0ee9d653b108
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']);
710793 151             $_SESSION[$this->sessionName] = $user;
64d82d 152             return $user;
JK 153         } else {
154             header('Location: '.$this->loginUrl());
155             die();
156         }
157     }
158     
501c90 159     protected function findCaFile() {
JK 160         $cafiles = array(
161             '/etc/ssl/certs/ca-certificates.crt',
162             '/etc/ssl/certs/ca-bundle.crt',
163             '/etc/pki/tls/certs/ca-bundle.crt',
164         );
165         
166         $cafile = NULL;
167         foreach($cafiles as $file) {
168             if(is_file($file)) {
169                 $cafile = $file;
170                 break;
171             }
172         }
173         
174         return $cafile;
175     }
176     
fa3e92 177     protected function createStreamContext($hostname) {
8e8f55 178         $context = array(
64d82d 179             'http' => array(
JK 180                 'method' => 'GET',
181                 'user_agent' => 'uphpCAS/'.self::VERSION,
182                 'max_redirects' => 3,
183             ),
184             'ssl' => array(
185                 'verify_peer' => TRUE,
90ae5b 186                 'verify_peer_name' => TRUE,
64d82d 187                 'verify_depth' => 5,
90ae5b 188                 'allow_self_signed' => FALSE,
JK 189                 'disable_compression' => TRUE,
64d82d 190             ),
8e8f55 191         );
64d82d 192         
8e8f55 193         if($this->caFile) {
JK 194             $context['ssl']['cafile'] = $this->caFile;
d35cf4 195         }
fa3e92 196         
8e8f55 197         if(version_compare(PHP_VERSION, '5.6', '<')) {
JK 198             $context['ssl']['ciphers'] = 'ECDH:DH:AES:CAMELLIA:!SSLv2:!aNULL'
199                 .':!eNULL:!EXPORT:!DES:!3DES:!MD5:!RC4:!ADH:!PSK:!SRP';
200             $context['ssl']['CN_match'] = $hostname;
201         }
202         
203         return stream_context_create($context);
fa3e92 204     }
JK 205     
206     public function verifyTicket($ticket) {
207         $url = parse_url($this->serverUrl);
208         $context = $this->createStreamContext($url['host']);
d35cf4 209         
90ae5b 210         $data = file_get_contents($this->serverUrl
JK 211                     .'/serviceValidate?service='.urlencode($this->serviceUrl)
fa3e92 212                     .'&ticket='.urlencode($ticket), FALSE, $context);
64d82d 213         if($data === FALSE) {
JK 214             throw new JasigException('Authentication error: CAS server is unavailable');
215         }
216         
217         $xmlEntityLoader = libxml_disable_entity_loader(TRUE);
218         $xmlInternalErrors = libxml_use_internal_errors(TRUE);
219         try {
220             $xml = new DOMDocument();
221             $xml->loadXML($data);
222             
223             foreach(libxml_get_errors() as $error) {
90ae5b 224                 $e = new ErrorException($error->message, $error->code, 1,
JK 225                         $error->file, $error->line);
64d82d 226                 switch ($error->level) {
JK 227                     case LIBXML_ERR_ERROR:
228                     case LIBXML_ERR_FATAL:
90ae5b 229                         throw new Exception('Fatal error during XML parsing',
JK 230                                 0, $e);
64d82d 231                         break;
JK 232                 }
233             }
2f13b1 234         } catch(Exception $e) {
64d82d 235             libxml_clear_errors();
JK 236             libxml_disable_entity_loader($xmlEntityLoader);
237             libxml_use_internal_errors($xmlInternalErrors);
a66017 238             throw new JasigException('Authentication error: CAS server'
JK 239                     .' response invalid - parse error', 0, $e);
64d82d 240         }
a66017 241         libxml_clear_errors();
JK 242         libxml_disable_entity_loader($xmlEntityLoader);
243         libxml_use_internal_errors($xmlInternalErrors);
64d82d 244         
JK 245         $failure = $xml->getElementsByTagName('authenticationFailure');
246         $success = $xml->getElementsByTagName('authenticationSuccess');
247         
248         if($failure->length > 0) {
249             $failure = $failure->item(0);
250             if(!($failure instanceof DOMElement)) {
90ae5b 251                 throw new JasigException('Authentication error: CAS server'
JK 252                         .' response invalid - authenticationFailure');
64d82d 253             }
90ae5b 254             throw new JasigAuthException('Authentication error: '
JK 255                     .$failure->textContent);
64d82d 256         } elseif($success->length > 0) {
JK 257             $success = $success->item(0);
258             if(!($success instanceof DOMElement)) {
90ae5b 259                 throw new JasigException('Authentication error: CAS server'
JK 260                         .' response invalid - authenticationSuccess');
64d82d 261             }
JK 262             
263             $user = $success->getElementsByTagName('user');
264             if($user->length == 0) {
90ae5b 265                 throw new JasigException('Authentication error: CAS server'
JK 266                         .' response invalid - user');
64d82d 267             }
JK 268             
269             $user = trim($user->item(0)->textContent);
2f13b1 270             if(strlen($user) < 1) {
90ae5b 271                 throw new JasigException('Authentication error: CAS server'
JK 272                         .' response invalid - user value');
64d82d 273             }
JK 274             
275             $jusr = new JasigUser();
276             $jusr->user = $user;
277             
278             $attrs = $success->getElementsByTagName('attributes');
279             if($attrs->length > 0) {
280                 $attrs = $attrs->item(0);
281                 foreach($attrs->childNodes as $node) {
282                     $jusr->attributes[$node->localName] = $node->textContent;
283                 }
284             }
285             
286             return $jusr;
2f13b1 287         } else {
90ae5b 288             throw new JasigException('Authentication error: CAS server'
JK 289                     .' response invalid - required tag not found');
64d82d 290         }
JK 291     }
292 }