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

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