root/openid/files/lib/data/openid/OpenID.class.php @ 1470

Revision 1470, 13.1 kB (checked in by Torben Brodt, 2 years ago)

need possibility to see that user is linked with openid, see #535

Line 
1<?php
2require_once(WCF_DIR.'lib/data/user/UserEditor.class.php');
3require_once(WCF_DIR.'lib/util/UserUtil.class.php');
4
5/**
6 * embeds the openid system into the wcf
7 * registers include pathes and cares for all dependencies
8 *
9 * @author      Torben Brodt
10 * @copyright   2010 easy-coding.de
11 * @license     GNU General Public License <http://opensource.org/licenses/gpl-3.0.html>
12 * @package     de.easy-coding.wcf.openid
13 */
14class OpenID {
15
16        /**
17         * sets include pathes
18         *
19         */
20        public function __construct() {
21
22                // fix for windows and openbasedire restrictions
23                define('Auth_OpenID_RAND_SOURCE', null);
24
25                $path_extra = dirname(__FILE__);
26                $path = ini_get('include_path');
27                $path = $path_extra . PATH_SEPARATOR . $path;
28                ini_set('include_path', $path);
29
30                /**
31                 * session wrapper
32                 */
33                require_once "Auth/Yadis/Manager.php";
34
35                /**
36                 * session wrapper
37                 */
38                require_once "OpenIDSession.class.php";
39
40                /**
41                 * Require the OpenID consumer code.
42                 */
43                require_once "Auth/OpenID/Consumer.php";
44
45                /**
46                 * Require the "file store" module, which we'll need to store
47                 * OpenID information.
48                 */
49                require_once "Auth/OpenID/FileStore.php";
50
51                /**
52                 * Require the Simple Registration extension API.
53                 */
54                require_once "Auth/OpenID/SReg.php";
55
56                /**
57                 * Require the PAPE extension module.
58                 */
59                require_once "Auth/OpenID/PAPE.php";
60
61                /**
62                 * Attribution Exchange
63                 */
64                require_once "Auth/OpenID/AX.php";
65        }
66
67        /**
68         * returns file store (tempoary store object)
69         */
70        protected function getStore() {
71                $store_path = FileUtil::getTemporaryFilename('openid_');
72                $store_path = TMP_DIR."/_openid".WCF_N;
73                return new Auth_OpenID_FileStore($store_path);
74        }
75
76        /**
77         * get authenticated user
78         */
79        protected function getConsumer() {
80                $session = new OpenIDSession();
81
82                /**
83                 * Create a consumer object using the store object created
84                 * earlier.
85                 */
86                $store = $this->getStore();
87                $consumer = new Auth_OpenID_Consumer($store, $session);
88                return $consumer;
89        }
90
91        /**
92         * gets root url
93         *
94         * @return      string
95         */
96        public static function getTrustRoot() {
97                return PAGE_URL;
98        }
99
100        /**
101         * call api and try authentication
102         */
103        public function tryRegistration($openid, $returnTo, $policy_uris = array()) {
104                $consumer = $this->getConsumer();
105
106                // Begin the OpenID authentication process.
107                $auth_request = $consumer->begin($openid);
108
109                // No auth request means we can't begin OpenID.
110                if (!$auth_request) {
111                        throw new Exception("Authentication error; not a valid OpenID.");
112                }
113
114                $sreg_request = Auth_OpenID_SRegRequest::build(
115                         // Required
116                         array('nickname'),
117                         // Optional
118                         array('email')
119                );
120
121                if ($sreg_request) {
122                        $auth_request->addExtension($sreg_request);
123                }
124
125                $pape_request = new Auth_OpenID_PAPE_Request($policy_uris);
126                if ($pape_request) {
127                        $auth_request->addExtension($pape_request);
128                }
129
130                // Redirect the user to the OpenID server for authentication.
131                // Store the token for this authentication so we can verify the
132                // response.
133
134                // For OpenID 1, send a redirect.  For OpenID 2, use a Javascript
135                // form to send a POST request to the server.
136                if ($auth_request->shouldSendRedirect()) {
137                        $redirect_url = $auth_request->redirectURL(self::getTrustRoot(), $returnTo);
138
139                        // If the redirect URL can't be built, display an error
140                        // message.
141                        if (Auth_OpenID::isFailure($redirect_url)) {
142                                throw new Exception("Could not redirect to server: " . $redirect_url->message);
143                        } else {
144                                // Send redirect.
145                                header("Location: ".$redirect_url);
146                                exit;
147                        }
148                } else {
149                        // Generate form markup and render it.
150                        $form_id = 'openid_message';
151                        $form_html = $auth_request->htmlMarkup(self::getTrustRoot(), $returnTo, false, array('id' => $form_id));
152
153                        // Display an error if the form markup couldn't be generated;
154                        // otherwise, render the HTML.
155                        if (Auth_OpenID::isFailure($form_html)) {
156                                throw new Exception("Could not redirect to server: " . $form_html->message);
157                        } else {
158
159                                // used by openid 2, formular and redirect are printed out
160                                echo $form_html;
161                                exit;
162                        }
163                }
164        }
165
166        /**
167         * got answer, save user
168         */
169        public function finishRegistration($return_to) {
170                $consumer = $this->getConsumer();
171
172                // Complete the authentication process using the server's
173                // response.
174                $response = $consumer->complete($return_to);
175
176                // Check the response status.
177                if ($response->status == Auth_OpenID_CANCEL) {
178                        // This means the authentication was cancelled.
179                        $msg = 'Verification cancelled.';
180                } else if ($response->status == Auth_OpenID_FAILURE) {
181                        // Authentication failed; display the error message.
182                        $msg = "OpenID authentication failed: " . $response->message;
183                } else if ($response->status == Auth_OpenID_SUCCESS) {
184                        // This means the authentication succeeded; extract the
185                        // identity URL and Simple Registration data (if it was
186                        // returned).
187                        $openid = $response->getDisplayIdentifier();
188                        $esc_identity = StringUtil::encodeHTML($openid);
189
190                        if ($response->endpoint->canonicalID) {
191                                $encoded_canonicalID = StringUtil::encodeHTML($response->endpoint->canonicalID);
192                        }
193
194                        $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
195                        $sreg = $sreg_resp->contents();
196
197                        // save user authentication
198                        $user = $this->finishUser(array(
199                                'name' => isset($sreg['nickname']) ? $sreg['nickname'] : null,
200                                'email' => isset($sreg['email']) ? $sreg['email'] : null,
201                                'identifier' => $openid
202                        ));
203
204                        if($user) {
205                                // set cookies
206                                UserAuth::getInstance()->storeAccessData($user, $user->username, $user->password);
207                                HeaderUtil::setCookie('password', $user->password, TIME_NOW + 365 * 24 * 3600);
208
209                                // change user
210                                WCF::getSession()->changeUser($user);
211                        }
212                }
213        }
214
215        public function tryAttributionExchange($openid, $returnTo) {
216                $consumer = $this->getConsumer();
217
218                // Create an authentication request to the OpenID provider
219                $auth = $consumer->begin($openid);
220
221                // Create attribute request object
222                // See http://code.google.com/apis/accounts/docs/OpenID.html#Parameters for parameters
223                // Usage: make($type_uri, $count=1, $required=false, $alias=null)
224                $attribute = array();
225                $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/contact/email', 1, 1, 'email');
226                $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/first', 1, 1, 'firstname');
227                $attribute[] = Auth_OpenID_AX_AttrInfo::make('http://axschema.org/namePerson/last', 1, 1, 'lastname');
228
229                // Create AX fetch request
230                $ax = new Auth_OpenID_AX_FetchRequest();
231
232                // Add attributes to AX fetch request
233                foreach($attribute as $attr){
234                        $ax->add($attr);
235                }
236
237                // Add AX fetch request to authentication request
238                $auth->addExtension($ax);
239
240                // Redirect to OpenID provider for authentication
241                $url = $auth->redirectURL(self::getTrustRoot(), $returnTo);
242                header('Location: ' . $url);
243                exit;
244        }
245
246        public function finishAttributionExchange($return_to) {
247                $consumer = $this->getConsumer();
248
249                // Create an authentication request to the OpenID provider
250                $response = $consumer->complete($return_to);
251
252                if ($response->status == Auth_OpenID_SUCCESS) {
253
254                        // Get registration informations
255                        $ax = new Auth_OpenID_AX_FetchResponse();
256                        $obj = $ax->fromSuccessResponse($response);
257
258                        // Print me raw
259                        $me = array();
260                        foreach($obj->data as $key => $val) {
261                                if(!isset($val[0])) {
262                                        continue;
263                                }
264                                $key = substr($key, strrpos($key, '/') + 1);
265                                $me[$key] = $val[0];
266                        }
267
268                        $userID = WCF::getUser()->userID;
269                        if($userID) {
270                                $editor = WCF::getUser()->getEditor();
271
272                                // only update username, if old username is still hashed
273                                $username = '';
274                                if(preg_match('/#\d+$/', $editor->username)) {
275                                        if(isset($me['first'])) {
276                                                $username .= ucfirst($me['first']);
277                                        }
278                                        if(isset($me['last'])) {
279                                                $username .= ucfirst($me['last']);
280                                        }
281                                        if(!empty($username)) {
282                                                $username = $this->findUsername($username);
283                                        }
284                                }
285
286                                // update email address
287                                $email = '';
288                                if(isset($me['email']) && UserUtil::isValidEmail($me['email']) && UserUtil::isAvailableEmail($me['email'])) {
289                                        $email = $me['email'];
290                                }
291
292                                if($username || $email) {
293                                        $editor->update($username, $email);
294                                        WCF::getSession()->updateUserData();
295                                }
296                        }
297                }
298        }
299
300        /**
301         * is there an existing user with given facebook id?
302         *
303         * @param       integer         $userID
304         * @return      boolean
305         */
306        public static function hasOpenIDAccount($userID) {
307                $sql = "SELECT          uto.userID
308                        FROM            wcf".WCF_N."_user_to_openid uto
309                        WHERE           utb.userID = ".intval($userID);
310                $row = WCF::getDB()->getFirstRow($sql);
311
312                return $row && $row['userID'] > 0;
313        }
314
315        /**
316         * is there an existing user with given openid id?
317         *
318         * @param       array           $me
319         * @return      User
320         */
321        protected static function getOpenIDEnabledUser($me) {
322                $sql = "SELECT          userID
323                        FROM            wcf".WCF_N."_user_to_openid uto
324                        WHERE           openID = '".sha1($me['identifier'])."'";
325                $row = WCF::getDB()->getFirstRow($sql);
326
327                $user = $row ? new User($row['userID']) : null;
328                return $user && $user->userID ? $user : null;
329        }
330
331        /**
332         * adds openid link to user
333         *
334         * @param       array           $me
335         * @param       User            $user
336         * @return      boolean
337         */
338        protected function addOpenIDUser($me, $user) {
339                $sql = "REPLACE INTO    wcf".WCF_N."_user_to_openid
340                                        (openID, userID)
341                        VALUES          ('".sha1($me['identifier'])."', ".intval($user->userID).")";
342
343                return WCF::getDB()->sendQuery($sql);
344        }
345
346        /**
347         * save incoming user
348         */
349        protected function finishUser($me) {
350
351                // take default username from hostname
352                if($me['name'] === null) {
353                        $host = parse_url($me['identifier'], PHP_URL_HOST)." ID #1";
354                        $host = preg_replace("/^www\./", "", $host);
355                        $me['name'] = $host;
356                }
357
358                // openid permissions granted, does an login exist?
359                $user = self::getOpenIDEnabledUser($me);
360
361                // openid permissions granted but no login exists
362                if(!$user) {
363
364                        // totally unknown, add a new user
365                        $user = $this->registerUser($me);
366
367                        // either user is new, oder just got a link, but add a openid link
368                        $this->addOpenIDUser($me, $user);
369                }
370
371                return $user;
372        }
373
374        /**
375         * get a available username
376         *
377         * @param       string          $username
378         * @return      string
379         */
380        protected function findUsername($username) {
381                if(!UserUtil::isValidUsername($username)) {
382                        return null;
383                }
384
385                if(UserUtil::isAvailableUsername($username)) {
386                        return $username;
387                }
388
389                // try to increase last digit
390                if(preg_match('/(\d+)$/', $username, $res)) {
391                        return $this->findUsername(preg_replace('/(\d+)/', ($res[1] + 1), $username));
392                } else {
393                        return $this->findUsername($username.'2');
394                }
395        }
396
397        /**
398         * registers a new user with valid username
399         *
400         * @param       array           $me
401         * @return      User
402         */
403        protected function registerUser($me) {
404                $user = null;
405                // get a valid username
406                $username = $this->findUsername($me['name']);
407
408                // take default email
409                if($me['email'] === null || !UserUtil::isValidEmail($me['email'])) {
410                        $host = parse_url($me['identifier'], PHP_URL_HOST);
411                        $host = preg_replace("/^www\./", "", $host);
412                        $me['email'] = md5($me['identifier']).'@openid.'.$host;
413                }
414
415                // create new user
416                if($username) {
417                        $user = $this->createNewUser(
418                                $username,
419                                $me['email']
420                        );
421                } else {
422                        throw new SystemException('invalid openid username: '.$me['name']);
423                }
424                return $user;
425        }
426
427        /**
428         * adds a new wcf user and sends, bypasses all registration steps and send out mails
429         *
430         * @param       string          $username
431         * @param       string          $email
432         * @return      User
433         */
434        protected function createNewUser($username, $email) {
435
436                $password = UserRegistrationUtil::getNewPassword((REGISTER_PASSWORD_MIN_LENGTH > 9 ? REGISTER_PASSWORD_MIN_LENGTH : 9));
437                $groups = array();
438                $activeOptions = array();
439
440                $additionalFields = array();
441                $additionalFields['languageID'] = WCF::getLanguage()->getLanguageID();
442                $additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress;
443
444                $visibleLanguages = $this->getAvailableLanguages();
445                $visibleLanguages = array_keys($visibleLanguages);
446                $addDefaultGroups = true;
447
448                // create the user
449                if(($user = UserEditor::create($username, $email, $password, $groups, $activeOptions, $additionalFields, $visibleLanguages, $addDefaultGroups))) {
450
451                        // notify admin
452                        if (REGISTER_ADMIN_NOTIFICATION) {
453                                // get default language
454                                $language = (WCF::getLanguage()->getLanguageID() != Language::getDefaultLanguageID()
455                                        ? new Language(Language::getDefaultLanguageID())
456                                        : WCF::getLanguage());
457                                $language->setLocale();
458
459                                // send mail
460                                $mail = new Mail(
461                                        MAIL_ADMIN_ADDRESS,
462                                        $language->get('wcf.user.register.notification.mail.subject', array(
463                                                'PAGE_TITLE' => $language->get(PAGE_TITLE)
464                                        )),
465                                        $language->get('wcf.user.register.notification.mail', array(
466                                                'PAGE_TITLE' => $language->get(PAGE_TITLE),
467                                                '$username' => $user->username
468                                        ))
469                                );
470                                $mail->send();
471
472                                WCF::getLanguage()->setLocale();
473                        }
474                }
475
476                return $user;
477        }
478
479        /**
480         * Returns a list of all available languages.
481         *
482         * @return      array
483         */
484        protected function getAvailableLanguages() {
485                $availableLanguages = array();
486                foreach (Language::getAvailableLanguages(PACKAGE_ID) as $language) {
487                        $availableLanguages[$language['languageID']] = WCF::getLanguage()->get('wcf.global.language.'.$language['languageCode']);
488                }
489
490                // sort languages
491                StringUtil::sort($availableLanguages);
492
493                return $availableLanguages;
494        }
495
496        /**
497         * @see UserLoginForm::readData
498         */
499        public static function updateCurrentUser() {
500               
501        }
502}
Note: See TracBrowser for help on using the browser.