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

Revision 1206, 11.5 kB (checked in by d0nut, 3 years ago)

finished openid implementation

Line 
1<?php
2require_once(WCF_DIR.'lib/data/user/UserEditor.class.php');
3
4/**
5 * embeds the openid system into the wcf
6 * registers include pathes and cares for all dependencies
7 *
8 * @author      Torben Brodt
9 * @copyright   2010 easy-coding.de
10 * @license     GNU General Public License <http://opensource.org/licenses/gpl-3.0.html>
11 * @package     de.easy-coding.wcf.openid
12 */
13class OpenID {
14
15        /**
16         * form instance needed for finish action
17         *
18         * @var UserLoginForm|OpenIDPage
19         */
20        protected $eventObj;
21
22        /**
23         * sets include pathes
24         *
25         * @param       UserLoginForm|OpenIDPage        $eventObj
26         */
27        public function __construct($eventObj = null) {
28                $this->eventObj = $eventObj;
29
30                $path_extra = dirname(__FILE__);
31                $path = ini_get('include_path');
32                $path = $path_extra . PATH_SEPARATOR . $path;
33                ini_set('include_path', $path);
34
35                /**
36                 * session wrapper
37                 */
38                require_once "Auth/Yadis/Manager.php";
39
40                /**
41                 * session wrapper
42                 */
43                require_once "OpenIDSession.class.php";
44
45                /**
46                 * Require the OpenID consumer code.
47                 */
48                require_once "Auth/OpenID/Consumer.php";
49
50                /**
51                 * Require the "file store" module, which we'll need to store
52                 * OpenID information.
53                 */
54                require_once "Auth/OpenID/FileStore.php";
55
56                /**
57                 * Require the Simple Registration extension API.
58                 */
59                require_once "Auth/OpenID/SReg.php";
60
61                /**
62                 * Require the PAPE extension module.
63                 */
64                require_once "Auth/OpenID/PAPE.php";
65        }
66
67        /**
68         * returns file store
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
81                $session = new OpenIDSession();
82       
83                /**
84                 * Create a consumer object using the store object created
85                 * earlier.
86                 */
87                $store = $this->getStore();
88                $consumer =& new Auth_OpenID_Consumer($store, $session);
89                return $consumer;
90        }
91
92        /**
93         * gets openid handler url
94         *
95         * @return      string
96         */
97        public static function getReturnTo() {
98                return PAGE_URL.'/index.php?page=OpenID';
99        }
100
101        /**
102         * gets root url
103         *
104         * @return      string
105         */
106        public static function getTrustRoot() {
107                return PAGE_URL;
108        }
109
110        /**
111         * call api and try authentication
112         */
113        public function tryAuthentication($openid, $policy_uris = array()) {
114                $consumer = $this->getConsumer();
115
116                // Begin the OpenID authentication process.
117                $auth_request = $consumer->begin($openid);
118
119                // No auth request means we can't begin OpenID.
120                if (!$auth_request) {
121                        throw new Exception("Authentication error; not a valid OpenID.");
122                }
123
124                $sreg_request = Auth_OpenID_SRegRequest::build(
125                         // Required
126                         array('nickname'),
127                         // Optional
128                         array('email')
129                );
130
131                if ($sreg_request) {
132                        $auth_request->addExtension($sreg_request);
133                }
134
135                $pape_request = new Auth_OpenID_PAPE_Request($policy_uris);
136                if ($pape_request) {
137                        $auth_request->addExtension($pape_request);
138                }
139
140                // Redirect the user to the OpenID server for authentication.
141                // Store the token for this authentication so we can verify the
142                // response.
143
144                // For OpenID 1, send a redirect.  For OpenID 2, use a Javascript
145                // form to send a POST request to the server.
146                if ($auth_request->shouldSendRedirect()) {
147                        $redirect_url = $auth_request->redirectURL(self::getTrustRoot(), self::getReturnTo());
148
149                        // If the redirect URL can't be built, display an error
150                        // message.
151                        if (Auth_OpenID::isFailure($redirect_url)) {
152                                throw new Exception("Could not redirect to server: " . $redirect_url->message);
153                        } else {
154                                // Send redirect.
155                                header("Location: ".$redirect_url);
156                                exit;
157                        }
158                } else {
159                        // Generate form markup and render it.
160                        $form_id = 'openid_message';
161                        $form_html = $auth_request->htmlMarkup(self::getTrustRoot(), self::getReturnTo(), false, array('id' => $form_id));
162
163                        // Display an error if the form markup couldn't be generated;
164                        // otherwise, render the HTML.
165                        if (Auth_OpenID::isFailure($form_html)) {
166                                throw new Exception("Could not redirect to server: " . $form_html->message);
167                        } else {
168                       
169                                // used by openid 2, formular and redirect are printed out
170                                echo $form_html;
171                                exit;
172                        }
173                }
174        }
175
176        /**
177         * got answer, save user
178         */
179        public function finishAuthentication() {
180                $consumer = $this->getConsumer();
181
182                // Complete the authentication process using the server's
183                // response.
184                $return_to = self::getReturnTo();
185                $response = $consumer->complete($return_to);
186
187                // Check the response status.
188                if ($response->status == Auth_OpenID_CANCEL) {
189                        // This means the authentication was cancelled.
190                        $msg = 'Verification cancelled.';
191                } else if ($response->status == Auth_OpenID_FAILURE) {
192                        // Authentication failed; display the error message.
193                        $msg = "OpenID authentication failed: " . $response->message;
194                } else if ($response->status == Auth_OpenID_SUCCESS) {
195                        // This means the authentication succeeded; extract the
196                        // identity URL and Simple Registration data (if it was
197                        // returned).
198                        $openid = $response->getDisplayIdentifier();
199                        $esc_identity = StringUtil::encodeHTML($openid);
200
201                        $success = sprintf('You have successfully verified ' .
202                                '<a href="%s">%s</a> as your identity.',
203                                $esc_identity, $esc_identity);
204
205                        if ($response->endpoint->canonicalID) {
206                                $encoded_canonicalID = StringUtil::encodeHTML($response->endpoint->canonicalID);
207                                $success .= '  (XRI CanonicalID: '.$encoded_canonicalID.') ';
208                        }
209
210                        $pape_resp = Auth_OpenID_PAPE_Response::fromSuccessResponse($response);
211
212                        if ($pape_resp) {
213                                if ($pape_resp->auth_policies) {
214                                        $success .= "<p>The following PAPE policies affected the authentication:</p><ul>";
215
216                                        foreach ($pape_resp->auth_policies as $uri) {
217                                                $encoded_uri = StringUtil::encodeHTML($uri);
218                                                $success .= "<li><tt>$encoded_uri</tt></li>";
219                                        }
220
221                                        $success .= "</ul>";
222                                } else {
223                                        $success .= "<p>No PAPE policies affected the authentication.</p>";
224                                }
225
226                                if ($pape_resp->auth_age) {
227                                        $age = StringUtil::encodeHTML($pape_resp->auth_age);
228                                        $success .= "<p>The authentication age returned by the " .
229                                                "server is: <tt>".$age."</tt></p>";
230                                }
231
232                                if ($pape_resp->nist_auth_level) {
233                                        $auth_level = StringUtil::encodeHTML($pape_resp->nist_auth_level);
234                                        $success .= "<p>The NIST auth level returned by the " .
235                                                "server is: <tt>".$auth_level."</tt></p>";
236                                }
237
238                        } else {
239                                $success .= "<p>No PAPE response was sent by the provider.</p>";
240                        }
241                       
242                        $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
243                        $sreg = $sreg_resp->contents();
244                       
245                        // save user authentication
246                        $this->finishUser(array(
247                                'name' => isset($sreg['nickname']) ? $sreg['nickname'] : null,
248                                'email' => isset($sreg['email']) ? $sreg['email'] : null,
249                                'identifier' => $openid
250                        ));
251                }
252        }
253
254        /**
255         * is there an existing user with given openid id?
256         *
257         * @param       array           $me
258         * @return      User
259         */
260        protected function getOpenIDEnabledUser($me) {
261                $sql = "SELECT          userID
262                        FROM            wcf".WCF_N."_user_to_openid
263                        WHERE           openID = '".sha1($me['identifier'])."'";
264                $row = WCF::getDB()->getFirstRow($sql);
265
266                $user = $row ? new User($row['userID']) : null;
267                return $user && $user->userID ? $user : null;
268        }
269
270        /**
271         * adds openid link to user
272         *
273         * @param       array           $me
274         * @param       User            $user
275         * @return      boolean
276         */
277        protected function addOpenIDUser($me, $user) {
278                $sql = "REPLACE INTO    wcf".WCF_N."_user_to_openid
279                                        (openID, userID)
280                        VALUES          ('".sha1($me['identifier'])."', ".intval($user->userID).")";
281
282                return WCF::getDB()->sendQuery($sql);
283        }
284
285        /**
286         * save incoming user
287         */
288        public function finishUser($me) {
289
290                // take default username from hostname
291                if($me['name'] === null) {
292                        $host = parse_url($me['identifier'], PHP_URL_HOST)." ID #1";
293                        $host = preg_replace("/^www\./", "", $host);
294                        $me['name'] = $host;
295                }
296               
297                // openid permissions granted, does an login exist?
298                $user = $this->getOpenIDEnabledUser($me);
299
300                // openid permissions granted but no login exists
301                if(!$user) {
302
303                        // totally unknown, add a new user
304                        $user = $this->registerUser($me);
305
306                        // either user is new, oder just got a link, but add a openid link
307                        $this->addOpenIDUser($me, $user);
308                }
309
310                if($user) {
311
312                        // UserLoginForm should not write cookie, since interfaces only support unhashed password
313                        $this->eventObj->useCookies = 0;
314
315                        // set cookies
316                        UserAuth::getInstance()->storeAccessData($user, $user->username, $user->password);
317                        HeaderUtil::setCookie('password', $user->password, TIME_NOW + 365 * 24 * 3600);
318
319                        // save cookie and redirect
320                        $this->eventObj->user = $user;
321                        $this->eventObj->save();
322
323                        exit;
324                }
325        }
326
327        /**
328         * get a available username
329         *
330         * @param       string          $username
331         * @return      string
332         */
333        protected function findUsername($username) {
334                if(!UserUtil::isValidUsername($username)) {
335                        return null;
336                }
337
338                if(UserUtil::isAvailableUsername($username)) {
339                        return $username;
340                }
341
342                // try to increase last digit
343                if(preg_match('/(\d+)$/', $username, $res)) {
344                        return $this->findUsername(preg_replace('/(\d+)/', ($res[1] + 1), $username));
345                } else {
346                        return $this->findUsername($username.'2');
347                }
348        }
349
350        /**
351         * registers a new user with valid username
352         *
353         * @param       array           $me
354         * @return      User
355         */
356        protected function registerUser($me) {
357                $user = null;
358                // get a valid username
359                $username = $this->findUsername($me['name']);
360               
361                // take default email
362                if($me['email'] === null) {
363                        $me['email'] = sha1($me['identifier']).'@openid';
364                }
365
366                // create new user
367                if($username) {
368                        $user = $this->createNewUser(
369                                $username,
370                                $me['email']
371                        );
372                } else {
373                        throw new SystemException('invalid openid username: '.$me['name']);
374                }
375                return $user;
376        }
377
378        /**
379         * adds a new wcf user and sends, bypasses all registration steps and send out mails
380         *
381         * @param       string          $username
382         * @param       string          $email
383         * @return      User
384         */
385        protected function createNewUser($username, $email) {
386
387                $password = UserRegistrationUtil::getNewPassword((REGISTER_PASSWORD_MIN_LENGTH > 9 ? REGISTER_PASSWORD_MIN_LENGTH : 9));
388                $groups = array();
389                $activeOptions = array();
390
391                $additionalFields = array();
392                $additionalFields['languageID'] = WCF::getLanguage()->getLanguageID();
393                $additionalFields['registrationIpAddress'] = WCF::getSession()->ipAddress;
394
395                $visibleLanguages = $this->getAvailableLanguages();
396                $visibleLanguages = array_keys($visibleLanguages);
397                $addDefaultGroups = true;
398
399                // create the user
400                if(($user = UserEditor::create($username, $email, $password, $groups, $activeOptions, $additionalFields, $visibleLanguages, $addDefaultGroups))) {
401
402                        // notify admin
403                        if (REGISTER_ADMIN_NOTIFICATION) {
404                                // get default language
405                                $language = (WCF::getLanguage()->getLanguageID() != Language::getDefaultLanguageID()
406                                        ? new Language(Language::getDefaultLanguageID())
407                                        : WCF::getLanguage());
408                                $language->setLocale();
409
410                                // send mail
411                                $mail = new Mail(
412                                        MAIL_ADMIN_ADDRESS,
413                                        $language->get('wcf.user.register.notification.mail.subject', array(
414                                                'PAGE_TITLE' => $language->get(PAGE_TITLE)
415                                        )),
416                                        $language->get('wcf.user.register.notification.mail', array(
417                                                'PAGE_TITLE' => $language->get(PAGE_TITLE),
418                                                '$username' => $user->username
419                                        ))
420                                );
421                                $mail->send();
422
423                                WCF::getLanguage()->setLocale();
424                        }
425                }
426
427                return $user;
428        }
429
430        /**
431         * Returns a list of all available languages.
432         *
433         * @return      array
434         */
435        protected function getAvailableLanguages() {
436                $availableLanguages = array();
437                foreach (Language::getAvailableLanguages(PACKAGE_ID) as $language) {
438                        $availableLanguages[$language['languageID']] = WCF::getLanguage()->get('wcf.global.language.'.$language['languageCode']);
439                }
440
441                // sort languages
442                StringUtil::sort($availableLanguages);
443
444                return $availableLanguages;
445        }
446}
Note: See TracBrowser for help on using the browser.