2 // vim: tabstop=2:shiftwidth=2
5 * NP_OpenId ($Revision: 1.3 $)
6 * by hsur ( http://blog.cles.jp/np_cles )
7 * $Id: NP_OpenId.php,v 1.3 2008-06-10 14:35:12 hsur Exp $
12 * Copyright (C) 2008 CLES. All rights reserved.
14 * This program is free software; you can redistribute it and/or
15 * modify it under the terms of the GNU General Public License
16 * as published by the Free Software Foundation; either version 2
17 * of the License, or (at your option) any later version.
19 * This program is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU General Public License for more details.
24 * You should have received a copy of the GNU General Public License
25 * along with this program; if not, write to the Free Software
26 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
28 * In addition, as a special exception, cles( http://blog.cles.jp/np_cles ) gives
29 * permission to link the code of this program with those files in the PEAR
30 * library that are licensed under the PHP License (or with modified versions
31 * of those files that use the same license as those files), and distribute
32 * linked combinations including the two. You must obey the GNU General Public
33 * License in all respects for all of the code used other than those files in
34 * the PEAR library that are licensed under the PHP License. If you modify
35 * this file, you may extend this exception to your version of the file,
36 * but you are not obligated to do so. If you do not wish to do so, delete
37 * this exception statement from your version.
40 // ParanoidHTTPFetcher bug?
41 define('Auth_Yadis_CURL_OVERRIDE', '1');
44 define('NP_OPENID_COOKIE', 'EXTAUTH');
47 require(dirname(__FILE__).'/sharedlibs/sharedlibs.php');
48 require_once 'cles/Template.php';
49 require_once 'Auth/OpenID/Consumer.php';
50 require_once 'cles/SQLStoreForNucleus.php';
51 require_once 'Auth/OpenID/SReg.php';
52 require_once 'Auth/OpenID/PAPE.php';
53 require_once 'Jsphon.php';
55 class NP_OpenId extends NucleusPlugin {
60 function getAuthor() {
64 return 'http://blog.cles.jp/np_cles/category/31/subcatid/21';
66 function getVersion() {
69 function getMinNucleusVersion() {
72 function getMinNucleusPatchLevel() {
75 function getEventList() {
76 return array ('FormExtra', 'ValidateForm', 'PreAddComment', 'PostAddComment', 'PostDeleteComment', 'ExternalAuth', 'Logout', 'LoginSuccess');
78 function getTableList() {
80 sql_table('plugin_openid'),
81 sql_table('plugin_openid_comment'),
82 sql_table('plugin_openid_profile'),
83 sql_table('plugin_openid_assc'),
84 sql_table('plugin_openid_nonce')
87 function getDescription() {
88 return '[$Revision: 1.3 $]<br />Adds OpenID authentication to anonymous comment, to prevent robots from spamming.';
90 function supportsFeature($what) {
92 case 'SqlTablePrefix':
98 function hasAdminArea() {
106 require_once 'Auth/OpenID/FileStore.php';
107 $store_path = "/tmp/_php_consumer_test";
109 $this->store = new Auth_OpenID_FileStore($store_path);
111 // include language file for this plugin
112 $language = ereg_replace( '[\\|/]', '', getLanguageName());
113 if (file_exists($this->getDirectory().'language/'.$language.'.php'))
114 @ include_once($this->getDirectory().'language/'.$language.'.php');
116 @ include_once($this->getDirectory().'language/english.php');
118 $this->store = new cles_SQLStoreForNucleus();
119 $this->consumer = new Auth_OpenID_Consumer($this->store);
120 $this->loggedinUser = null;
121 $this->comments = array();
125 $this->store->createTables();
128 'CREATE TABLE IF NOT EXISTS ' . sql_table('plugin_openid')
130 . ' cookie varchar(40) NOT NULL default \'\','
131 . ' identity varchar(255) NOT NULL,'
132 . ' sreg text NOT NULL default \'\','
133 . ' ts datetime NOT NULL default \'0000-00-00 00:00:00\','
134 . ' PRIMARY KEY (cookie)'
138 'CREATE TABLE IF NOT EXISTS ' . sql_table('plugin_openid_profile')
140 . ' identity varchar(255) NOT NULL,'
141 . ' nick varchar(255) NOT NULL unique default \'\','
142 . ' email varchar(255) ,'
143 . ' sreg text NOT NULL default \'\','
144 . ' ts datetime NOT NULL default \'0000-00-00 00:00:00\','
145 . ' PRIMARY KEY (identity)'
149 'CREATE TABLE IF NOT EXISTS ' . sql_table('plugin_openid_comment')
151 . ' cnumber int(11) NOT NULL,'
152 . ' citem int(11) NOT NULL,'
153 . ' identity varchar(255) NOT NULL default \'\','
154 . ' ts datetime NOT NULL default \'0000-00-00 00:00:00\','
155 . ' PRIMARY KEY(cnumber), '
161 $this->createOption('permitComment', 'Permit comments w/o login?', 'yesno', 'yes', '');
162 $this->createOption('permitMail', 'Permit mail w/o login?', 'yesno', 'yes', '');
164 $this->createOption('CommentFormError', 'Error message (comment)', 'text', 'To submit comment, you need to sign-in to OpenID.', '');
165 $this->createOption('MemberMailError', 'Error message (mail form)', 'text', 'To send email, you need to sign-in to OpenID.', '');
167 $this->createOption('dropdb', 'Erase on uninstall?', 'yesno', 'no', '');
168 $this->createOption('debug', 'Debug mode ?', 'yesno', 'no');
170 $this->createOption('enableLinkedWith', 'Enable local account linked with OpenID account ? ', 'yesno', 'no');
171 $this->createMemberOption('linkedWith', 'Linked with following account', 'text', '');
174 function unInstall() {
175 sql_query('DROP TABLE '.sql_table('plugin_openid_assc'));
176 sql_query('DROP TABLE '.sql_table('plugin_openid_nonce'));
178 if ($this->getOption('dropdb') == 'yes'){
179 sql_query('DROP TABLE '.sql_table('plugin_openid'));
180 sql_query('DROP TABLE '.sql_table('plugin_openid_profile'));
181 sql_query('DROP TABLE '.sql_table('plugin_openid_comment'));
184 function doAction($type) {
187 if( $this->login() ){
188 $this->_info('Authentication success: identity=' . $this->loggedinUser['identity']);
189 $url = preg_replace('/action=logout&?/','', requestVar('return_url'));
190 $this->_doLoginLocal($this->loggedinUser['identity']);
191 $this->_redirect( $url );
193 $this->_info('Authentication failure');
194 return 'Authentication failure';
199 return $this->doAuth( requestVar('openid_identifier'), requestVar('return_url') );
204 $this->_redirect( requestVar('url') );
207 case 'updateProfile':
209 if( $this->isLoggedin() ){
211 $aVars['nick'] = requestVar('nick');
212 $aVars['email'] = requestVar('email');
214 $this->_doUpdateProfile($aVars);
216 $aVars['message'] = NP_OPENID_updateSucceeded;
217 $aVars['result'] = 'succeeded';
219 $aVars['message'] = NP_OPENID_notloggedin;
220 $aVars['result'] = 'failure';
224 if(_CHARSET != 'UTF-8') mb_convert_variables('UTF-8', _CHARSET, $aVars);
225 echo Jsphon::encode($aVars);
229 return 'Unknown action: '.$type;
234 function doAuth($identifier, $returnUrl){
236 if( !$identifier ) return 'Missing OpenID identifier.';
238 $auth_request = $this->consumer->begin($identifier);
239 if (!$auth_request) {
240 $this->reason = $auth_request;
241 return "OpenID identifier is invalid.";
243 $sreg_request = Auth_OpenID_SRegRequest::build(
247 array('fullname', 'email')
249 $auth_request->addExtension($sreg_request);
251 $returnTo = $CONF['PluginURL'].'openid/rd.php?action=verify&return_url='.urlencode($returnUrl);
252 $trustRoot = $CONF['IndexURL'];
254 if ($auth_request->shouldSendRedirect()) {
255 $redirect_url = $auth_request->redirectURL($trustRoot, $returnTo);
257 if (Auth_OpenID::isFailure($redirect_url)) {
258 return "Could not redirect to server: " . $redirect_url->message;
260 header("Location: ". $redirect_url);
261 $this->redirectTo = $redirect_url;
264 $form_id = 'openid_message';
265 $form_html = $auth_request->formMarkup($trustRoot, $returnTo,
266 false, array('id' => $form_id));
268 if (Auth_OpenID::isFailure($form_html)) {
269 return "Could not redirect to server: " . $form_html->message;
271 $page_contents = array(
272 "<html><head><title>",
273 "OpenID transaction in progress",
275 "<body onload='document.getElementById(\"".$form_id."\").submit()'>",
278 print implode("\n", $page_contents);
284 function _doUpdateProfile($profile){
285 $query = sprintf('REPLACE INTO ' . sql_table('plugin_openid_profile')
286 . ' ( identity, nick, email, ts ) '
287 . " values('%s', '%s', '%s', now())",
288 mysql_real_escape_string( $this->loggedinUser['identity'] ),
289 mysql_real_escape_string( $profile['nick'] ),
290 mysql_real_escape_string( $profile['email'] )
294 $this->loggedinUser['nick'] = $profile['nick'];
295 $this->loggedinUser['email'] = $profile['email'];
298 function _doLoginLocal($name){
299 if( $this->getOption('enableLinkedWith') != 'yes' ) return false;
301 $linkedWith = $this->getAllMemberOptions('linkedWith');
302 ksort($linkedWith, SORT_NUMERIC);
305 foreach( $linkedWith as $id => $accountList ){
306 $accounts = explode(",", $accountList);
307 $accounts = array_map("trim", $accounts);
309 foreach( $accounts as $account ){
310 if( $account == '*' || $account == $name ){
316 if( $localId == -1 ) return false;
318 global $manager, $CONF, $member;
319 $member =& MEMBER::createFromID($localId);
320 $member->loggedin = 1;
322 $member->newCookieKey();
323 $member->setCookies(0);
324 if ( isset($CONF['secureCookieKey']) ) {
325 $member->setCookieKey(md5($member->getCookieKey().$CONF['secureCookieKeyIP']));
328 $manager->notify('LoginSuccess', array('member' => &$member) );
330 $this->_info('Login local account :' . $member->getDisplayName() );
331 ACTIONLOG::add(INFO, 'Login successful for '.$member->getDisplayName().' (sharedpc=0, OpenId)');
335 function _info($msg) {
336 if ($this->getOption('debug') == 'yes') {
337 ACTIONLOG :: add(INFO, 'OpenId: '.$msg);
341 function _warn($msg) {
342 ACTIONLOG :: add(WARNING, 'OpenId: '.$msg);
345 function _redirect($url){
346 header('Location: '.$url);
349 function _generateKey(){
350 mt_srand( (double) microtime() * 1000000);
351 return md5(uniqid(mt_rand()));
354 function isLoggedin(){
356 if( $this->loggedinUser['identity'] ) return true;
358 $cookie = cookieVar($CONF['CookiePrefix'] . NP_OPENID_AUTH_COOKIE);
359 if( ! $cookie ) return false;
361 $query = sprintf('SELECT a.cookie as cookie, a.identity as identity, a.sreg as sreg, a.ts as ts, p.nick as nick, p.email as email FROM ' . sql_table('plugin_openid') . ' a '
362 . ' LEFT OUTER JOIN ' . sql_table('plugin_openid_profile') . ' p ON a.identity = p.identity '
363 . " where a.cookie = '%s' and a.ts > date_sub( now(), interval 1 day)"
364 , mysql_real_escape_string( trim($cookie) )
366 $res = sql_query($query);
367 if( @mysql_num_rows($res) > 0) {
368 $this->loggedinUser = mysql_fetch_assoc($res);
369 $this->loggedinUser = array_merge($this->loggedinUser, unserialize($this->loggedinUser['sreg']));
377 //$return_url = $CONF['PluginURL'].'openid/rd.php?action=verify&return_url='.urlencode(requestVar('return_url'));
378 $return_url = $CONF['PluginURL'].'openid/rd.php';
379 $response = $this->consumer->complete( $return_url );
380 if ($response->status == Auth_OpenID_CANCEL) {
381 $this->message = 'Verification cancelled.';
383 } else if ($response->status == Auth_OpenID_FAILURE) {
384 $this->message = "OpenID authentication failed: " . $response->message;
385 $this->reason = $response;
387 } else if ($response->status != Auth_OpenID_SUCCESS) {
388 $this->message = 'Unknown status: ' . $response->status;
392 // Auth_OpenID_SUCCESS
394 $identity = $response->getDisplayIdentifier();
395 $sreg_resp = Auth_OpenID_SRegResponse::fromSuccessResponse($response);
396 $sreg = $sreg_resp->contents(); // assoc
399 $cookie = $this->_generateKey();
400 $query = sprintf('REPLACE INTO ' . sql_table('plugin_openid')
401 . ' ( cookie, identity, sreg, ts ) '
402 . " values('%s', '%s', '%s', '%s')",
403 mysql_real_escape_string( $cookie ),
404 mysql_real_escape_string( $identity ),
405 mysql_real_escape_string( serialize($sreg) ),
406 mysql_real_escape_string( date("Y/m/d H:i:s", $ts ) )
410 $query = sprintf('SELECT a.cookie as cookie, a.identity as identity, a.sreg as sreg, a.ts as ts, p.nick as nick, p.email as email FROM ' . sql_table('plugin_openid') . ' a '
411 . ' LEFT OUTER JOIN ' . sql_table('plugin_openid_profile') . ' p ON a.identity = p.identity '
412 . " where a.cookie = '%s' and a.ts > date_sub( now(), interval 1 day)"
413 , mysql_real_escape_string( trim($cookie) )
415 $res = sql_query($query);
417 if( @mysql_num_rows($res) > 0) {
418 $this->loggedinUser = mysql_fetch_assoc($res);
419 $this->loggedinUser = array_merge($this->loggedinUser, unserialize($this->loggedinUser['sreg']));
421 setcookie($CONF['CookiePrefix'] . NP_OPENID_AUTH_COOKIE , $cookie, 0, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
430 $this->loggedinUser = null;
431 setcookie($CONF['CookiePrefix'] . NP_OPENID_AUTH_COOKIE, '', 0, $CONF['CookiePath'], $CONF['CookieDomain'], $CONF['CookieSecure']);
435 function event_ExternalAuth(&$data){
436 if( $data['externalauth']['source'] == $this->getName() ) return;
437 if( isset($data['externalauth']['result']) && $data['externalauth']['result'] == true ){
441 if( $this->isLoggedin() ){
442 $data['externalauth']['result'] = true;
443 $data['externalauth']['plugin'] = $this->getName();
447 function doSkinVar($skinType, $type = "") {
448 global $CONF, $manager, $member;
449 if($skinType != 'item') return;
450 if( $member->isLoggedIn() ) return;
452 $externalauth = array ( 'source' => $this->getName() );
453 $manager->notify('ExternalAuth', array ('externalauth' => &$externalauth));
454 if (isset($externalauth['result']) && $externalauth['result'] == true) return;
456 $te = $this->_getTemplateEngine();
458 $aVars['PluginURL'] = $CONF['PluginURL'];
460 $te = $this->_getTemplateEngine();
461 if( $this->isLoggedin() ){
463 $return_url = $CONF['PluginURL'] . 'openid/rd.php?action=rd&url='
464 . urlencode( 'http://'.serverVar("HTTP_HOST") .serverVar("REQUEST_URI") );
465 $aVars['url'] = $return_url;
466 $aVars['nick'] = $this->loggedinUser['nick'];
467 $aVars['email'] = $this->loggedinUser['email'];
468 $aVars['ts'] = $this->loggedinUser['ts'];
469 $aVars['identity'] = $this->loggedinUser['identity'];
470 $aVars['visible'] = $aVars['nick'] ? 'false' : 'true' ;
472 $actionUrl = parse_url($CONF['ActionURL']);
473 $aVars['updateUrl'] = $actionUrl['path'];
475 echo $te->fetchAndFill('yui', $aVars, strtolower(__CLASS__));
476 echo $te->fetchAndFill('loggedin', $aVars, strtolower(__CLASS__));
477 echo $te->fetchAndFill('form', $aVars, strtolower(__CLASS__));
480 $aVars['url'] = $CONF['PluginURL'] . 'openid/rd.php?action=doauth&return_url='
481 . urlencode( 'http://'.serverVar("HTTP_HOST") .serverVar("REQUEST_URI") );
483 echo $te->fetchAndFill('notloggedin', $aVars, strtolower(__CLASS__));
487 function event_FormExtra(&$data) {
488 global $CONF, $manager, $member;
489 if( $member->isLoggedIn() ) return;
491 switch ($data['type']) {
492 case 'commentform-notloggedin' :
493 case 'membermailform-notloggedin':
500 $externalauth = array ( 'source' => $this->getName() );
501 $manager->notify('ExternalAuth', array ('externalauth' => &$externalauth));
502 if (isset($externalauth['result']) && $externalauth['result'] == true) return;
507 function event_ValidateForm(&$data) {
508 global $manager, $member;
509 if( $member->isLoggedIn() ) return;
511 $externalauth = array ( 'source' => $this->getName() );
512 $manager->notify('ExternalAuth', array ('externalauth' => &$externalauth));
513 if (isset($externalauth['result']) && $externalauth['result'] == true) return;
515 switch ($data['type']) {
517 if( (! $this->isLoggedin() ) && $this->getOption('permitComment') == 'no' )
518 $data['error'] = $this->getOption('CommentFormError');
521 if( (! $this->isLoggedin() ) && $this->getOption('permitMail') == 'no' )
522 $data['error'] = $this->getOption('MemberMailError');
529 function event_PreAddComment(&$data) {
531 if( $member->isLoggedIn() ) return;
533 if( ! $this->isLoggedin() ) return;
534 $data['comment']['user'] = $this->loggedinUser['nick'].' [OpenID]';
537 function event_PostAddComment(&$data) {
539 if( $member->isLoggedIn() ) return;
541 if( ! $this->isLoggedin() ) return;
543 $query = sprintf('INSERT INTO ' . sql_table('plugin_openid_comment')
544 . '( cnumber, citem, identity, ts ) '
545 . "values('%s', '%s', '%s', now() )",
546 mysql_real_escape_string( $data['commentid'] ),
547 mysql_real_escape_string( intval($itemid) ),
548 mysql_real_escape_string( $this->loggedinUser['identity'] )
553 function event_PostDeleteComment(&$data) {
554 $query = sprintf('DELETE FROM ' . sql_table('plugin_openid_comment')
555 . " where cnumber = '%s'",
556 mysql_real_escape_string( intval($data['commentid']) )
561 function event_LoginSuccess(&$data) {
562 if( $this->isLoggedin() ){
567 function event_Logout(&$data) {
568 if( $this->isLoggedin() ){
573 function doTemplateCommentsVar(&$item, &$comment){
574 global $member, $CONF;
575 $itemid = intval($item['itemid']);
576 if( ! $this->comments[$itemid] ){
577 $this->comments[$itemid]['cached'] = true;
578 $query = sprintf('SELECT c.cnumber as cnumber, c.identity as identity, p.nick as nick, p.email as email, p.sreg as sreg FROM ' . sql_table('plugin_openid_comment') . ' c '
579 . ' LEFT OUTER JOIN ' . sql_table('plugin_openid_profile') . ' p ON c.identity = p.identity '
580 . " WHERE citem = '%s'"
581 , mysql_real_escape_string( intval($itemid) )
583 $res = sql_query($query);
584 $this->comments[$itemid] = array();
585 while( $a =& mysql_fetch_assoc($res)) {
586 $cnumber = $a['cnumber'];
587 $this->comments[$itemid][$cnumber] = $a;
590 $cnumber = $comment['commentid'];
591 if( $openIdComment = $this->comments[$itemid][$cnumber] ){
592 $aVars['identity'] = $openIdComment['identity'];
593 $aVars['PluginURL'] = $CONF['PluginURL'];
595 $sreg = unserialize($openIdComment['sreg']);
596 if( is_array($sreg) )
597 $aVars = array_merge($aVars, $sreg);
599 $te = $this->_getTemplateEngine();
600 if ( $member->isLoggedIn() ){
601 echo $te->fetchAndFill('admin', $aVars, strtolower(__CLASS__));
603 echo $te->fetchAndFill('user', $aVars, strtolower(__CLASS__));
608 function _getTemplateEngine(){
609 if( ! $this->templateEngine )
610 $this->templateEngine =& new cles_Template(dirname(__FILE__).'/openid/template');
612 $this->templateEngine->defaultLang = 'english';
613 return $this->templateEngine;