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

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