r25860 MediaWiki - Code Review archive

Repository:MediaWiki
Revision:r25859‎ | r25860 | r25861 >
Date:17:36, 14 September 2007
Author:evan
Status:old
Tags:
Comment:
Tagging the 0.7.0 release of the 'OpenID' MediaWiki extension.
Modified paths:
  • /tags/extensions/OpenID/REL0_7_0 (added) (history)
  • /tags/extensions/OpenID/REL0_7_0 (added) (history)

Diff [purge]

Index: tags/extensions/OpenID/REL0_7_0/TODO
@@ -0,0 +1,45 @@
 2++ Add a Storage class for $wgMemc
 3++ Prevent running OpenIDLogin, OpenIDFinish when you're already logged in
 4++ Add a server mode
 5++ Prevent serving OpenID accounts
 6++ Prevent trusting the same server
 7++ Limit domains whose OpenIDs you trust
 8++ For domains, Order Allow,Deny or Deny,Allow
 9++ Function to let skins tell if user is OpenID user
 10++ Function to let skins know user's OpenID URL
 11++ Interwiki login
 12+- drop-down for other URLs
 13++ server mode login
 14++ server mode trust form
 15++ server mode share fields form
 16++ change a "regular" account to an OpenID account
 17+- keep user from setting password in OpenID account
 18+- keep user from mailing new password in OpenID account
 19+- participate in account-creation throttle
 20+- audit to prevent circumventing blocks/bans
 21+- OpenID extension to share MW settings
 22+- share timezone when/if available
 23+- move Special: pages to one page with a parameter
 24++ split big file into 2-3 smaller modules
 25++ add openid.server to user pages
 26++ hide openid.server for non-user pages
 27+- optimize trust storage
 28++ change trust data separators from , and | to FS and RS
 29+- User preferences tab for OpenID
 30+- Manage allow-to-trust settings in User preferences
 31+- Optionally redirect User: page to OpenID URL
 32+- If user logs in with OpenID, add a cookie, and auto-login next time
 33++ Have some $trust_root values that we _always_ allow to trust
 34++ README
 35++ form to choose user ID if not sreg.nickname or sreg.fullname
 36++ try to guess user name from URL with /
 37+ like http://getopenid.com/evanprodromou or
 38+ http://profile.typekey.com/EvanProdromou/
 39++ Allow specifying a FileStore rather than a MemcStore
 40+- Allow specifying a MySQLStore rather than a MemcStore
 41++ Fall back to FileStore if Memc is bogus
 42++ Unit test for MemcStore
 43+- for just-a-hostname OpenIDs, if the first element is not
 44+ www, use the first element as a proposed ID
 45+- configurable regexps for finding a user ID from an OpenID.
 46++ Make the OpenID login page more prominent. A personal toolbox link for each page?
Property changes on: tags/extensions/OpenID/REL0_7_0/TODO
___________________________________________________________________
Added: svn:eol-style
147 + native
Index: tags/extensions/OpenID/REL0_7_0/Convert.php
@@ -0,0 +1,154 @@
 2+<?php
 3+/**
 4+ * Convert.php -- Convert existing account to OpenID account
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+if (defined('MEDIAWIKI')) {
 27+
 28+ # We use some of the consumer code
 29+
 30+ require_once("$IP/extensions/OpenID/Consumer.php");
 31+
 32+ function wfSpecialOpenIDConvert($par) {
 33+
 34+ global $wgRequest, $wgUser, $wgOut;
 35+
 36+ if ($wgUser->getID() == 0) {
 37+ $wgOut->errorpage('openiderror', 'notloggedin');
 38+ return;
 39+ }
 40+
 41+ switch ($par) {
 42+ case 'Finish':
 43+ OpenIDConvertFinish();
 44+ break;
 45+ default:
 46+ $openid_url = $wgRequest->getText('openid_url');
 47+ if (isset($openid_url) && strlen($openid_url) > 0) {
 48+ OpenIDConvert($openid_url);
 49+ } else {
 50+ OpenIDConvertForm();
 51+ }
 52+ }
 53+ }
 54+
 55+ function OpenIDConvert($openid_url) {
 56+ global $wgUser, $wgOut;
 57+
 58+ # Expand Interwiki
 59+
 60+ $openid_url = OpenIDInterwikiExpand($openid_url);
 61+
 62+ if (!OpenIDCanLogin($openid_url)) {
 63+ $wgOut->errorpage('openidpermission', 'openidpermissiontext');
 64+ return;
 65+ }
 66+
 67+ $other = OpenIDGetUser($openid_url);
 68+
 69+ if (isset($other)) {
 70+ if ($other->getId() == $wgUser->getID()) {
 71+ $wgOut->errorpage('openiderror', 'openidconvertyourstext');
 72+ } else {
 73+ $wgOut->errorpage('openiderror', 'openidconvertothertext');
 74+ }
 75+ return;
 76+ }
 77+
 78+ # If we're OK to here, let the user go log in
 79+
 80+ OpenIDLogin($openid_url, 'OpenIDConvert/Finish');
 81+ }
 82+
 83+ function OpenIDConvertForm() {
 84+ global $wgOut, $wgUser;
 85+ $sk = $wgUser->getSkin();
 86+ $url = OpenIDGetUserUrl($wgUser);
 87+ if (is_null($url)) {
 88+ $url = '';
 89+ }
 90+
 91+ $ok = wfMsg('ok');
 92+ $instructions = wfMsg('openidconvertinstructions');
 93+ $wgOut->addHTML("<p>{$instructions}</p>" .
 94+ '<form action="' . $sk->makeSpecialUrl('OpenIDConvert') . '" method="POST">' .
 95+ '<input type="text" name="openid_url" size=30 ' .
 96+ ' style="background: url(http://www.openid.net/login-bg.gif) ' .
 97+ ' no-repeat; background-color: #fff; background-position: 0 50%; ' .
 98+ ' color: #000; padding-left: 18px;" value="' . $url . '" />' .
 99+ '<input type="submit" value="' . $ok . '" />' .
 100+ '</form>');
 101+ }
 102+
 103+ function OpenIDConvertFinish() {
 104+
 105+ global $wgUser, $wgOut;
 106+
 107+ $consumer = OpenIDConsumer();
 108+ $response = $consumer->complete($_GET);
 109+
 110+ if (!isset($response)) {
 111+ $wgOut->errorpage('openiderror', 'openiderrortext');
 112+ return;
 113+ }
 114+
 115+ switch ($response->status) {
 116+ case Auth_OpenID_CANCEL:
 117+ // This means the authentication was cancelled.
 118+ $wgOut->errorpage('openidcancel', 'openidcanceltext');
 119+ break;
 120+ case Auth_OpenID_FAILURE:
 121+ $wgOut->errorpage('openidfailure', 'openidfailuretext');
 122+ break;
 123+ case Auth_OpenID_SUCCESS:
 124+ // This means the authentication succeeded.
 125+ $openid_url = $response->identity_url;
 126+
 127+ if (!isset($openid_url)) {
 128+ $wgOut->errorpage('openiderror', 'openiderrortext');
 129+ return;
 130+ }
 131+
 132+ # We check again for dupes; this may be normalized or
 133+ # reformatted by the server.
 134+
 135+ $other = OpenIDGetUser($openid_url);
 136+
 137+ if (isset($other)) {
 138+ if ($other->getId() == $wgUser->getID()) {
 139+ $wgOut->errorpage('openiderror', 'openidconvertyourstext');
 140+ } else {
 141+ $wgOut->errorpage('openiderror', 'openidconvertothertext');
 142+ }
 143+ return;
 144+ }
 145+
 146+ OpenIDSetUserUrl($wgUser, $openid_url);
 147+
 148+ $wgOut->setPageTitle( wfMsg( 'openidconvertsuccess' ) );
 149+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
 150+ $wgOut->setArticleRelated( false );
 151+ $wgOut->addWikiText( wfMsg( 'openidconvertsuccesstext', $openid_url ) );
 152+ $wgOut->returnToMain( );
 153+ }
 154+ }
 155+}
Property changes on: tags/extensions/OpenID/REL0_7_0/Convert.php
___________________________________________________________________
Added: svn:eol-style
1156 + native
Index: tags/extensions/OpenID/REL0_7_0/optionToTable.php
@@ -0,0 +1,50 @@
 2+<?php
 3+/**
 4+ * optionToTable.php -- Convert old user_options-based
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+require_once('commandLine.inc');
 27+ini_set( "include_path", "/usr/share/php:" . ini_get("include_path"));
 28+
 29+require_once("$IP/extensions/OpenID/Consumer.php");
 30+
 31+global $wgSharedDB, $wgDBprefix;
 32+$tableName = "${wgDBprefix}user_openid";
 33+if (isset($wgSharedDB)) {
 34+ $tableName = "`$wgSharedDB`.$tableName";
 35+}
 36+
 37+$dbr =& wfGetDB( DB_SLAVE );
 38+
 39+$res = $dbr->select(array('user'),
 40+ array('user_name'),
 41+ array('user_options LIKE "%openid_url%"'),
 42+ 'optionToTable',
 43+ array('ORDER BY' => 'user_name'));
 44+
 45+while ($res && $row = $dbr->fetchObject($res)) {
 46+ $user = User::newFromName($row->user_name);
 47+ print( $user->getName() . ": " . $user->getOption('openid_url') . "\n");
 48+ OpenIDSetUserUrl($user, $user->getOption('openid_url'));
 49+}
 50+$dbr->freeResult($res);
 51+
Property changes on: tags/extensions/OpenID/REL0_7_0/optionToTable.php
___________________________________________________________________
Added: svn:eol-style
152 + native
Index: tags/extensions/OpenID/REL0_7_0/Server.php
@@ -0,0 +1,791 @@
 2+<?php
 3+/**
 4+ * Server.php -- Server side of OpenID site
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+if (defined('MEDIAWIKI')) {
 27+
 28+ require_once("Auth/OpenID/Server.php");
 29+ require_once("Auth/OpenID/Consumer.php");
 30+
 31+ # These are trust roots that we don't bother asking users
 32+ # whether the trust root is allowed to trust; typically
 33+ # for closely-linked partner sites.
 34+
 35+ $wgOpenIDServerForceAllowTrust = array();
 36+
 37+ # Where to store transitory data. Can be 'memc' for the $wgMemc
 38+ # global caching object, or 'file' if caching is turned off
 39+ # completely and you need a fallback.
 40+
 41+ $wgOpenIDServerStoreType = ($wgMainCacheType == CACHE_NONE) ? 'file' : 'memc';
 42+
 43+ # If the store type is set to 'file', this is is the name of a
 44+ # directory to store the data in.
 45+
 46+ $wgOpenIDServerStorePath = ($wgMainCacheType == CACHE_NONE) ? "/tmp/$wgDBname/openidserver/" : NULL;
 47+
 48+ # Outputs a Yadis (http://yadis.org/) XRDS file, saying that this server
 49+ # supports OpenID and lots of other jazz.
 50+
 51+ function wfSpecialOpenIDXRDS($par) {
 52+ global $wgOut;
 53+
 54+ // XRDS preamble XML.
 55+ $xml_template = array('<?xml version="1.0" encoding="UTF-8"?>',
 56+ '<xrds:XRDS',
 57+ ' xmlns:xrds="xri://\$xrds"',
 58+ ' xmlns:openid="http://openid.net/xmlns/1.0"',
 59+ ' xmlns="xri://$xrd*($v*2.0)">',
 60+ '<XRD>');
 61+
 62+ // Generate the user page URL.
 63+ $user_title = Title::makeTitle(NS_USER, $par);
 64+ $user_url = $user_title->getFullURL();
 65+
 66+ // Generate the OpenID server endpoint URL.
 67+ $server_title = Title::makeTitle(NS_SPECIAL, 'OpenIDServer');
 68+ $server_url = $server_title->getFullURL();
 69+
 70+ // Define array of Yadis services to be included in
 71+ // the XRDS output.
 72+ $services = array(
 73+ array('uri' => $server_url,
 74+ 'priority' => '0',
 75+ 'types' => array('http://openid.net/signon/1.0',
 76+ 'http://openid.net/sreg/1.0'),
 77+ 'delegate' => $user_url)
 78+ );
 79+
 80+ // Generate <Service> elements into $service_text.
 81+ $service_text = "\n";
 82+ foreach ($services as $service) {
 83+ $types = array();
 84+ foreach ($service['types'] as $type_uri) {
 85+ $types[] = ' <Type>'.$type_uri.'</Type>';
 86+ }
 87+ $service_text .= implode("\n",
 88+ array(' <Service priority="'.$service['priority'].'">',
 89+ ' <URI>'.$server_url.'</URI>',
 90+ implode("\n", $types),
 91+ ' </Service>'));
 92+ }
 93+
 94+ $wgOut->disable();
 95+
 96+ // Print content-type and XRDS XML.
 97+ header("Content-Type", "application/xrds+xml");
 98+ print implode("\n", $xml_template);
 99+ print $service_text;
 100+ print implode("\n", array("</XRD>", "</xrds:XRDS>"));
 101+ }
 102+
 103+ # Special page for the server side of OpenID
 104+ # It has three major flavors:
 105+ # * no parameter is for external requests to validate a user.
 106+ # * 'Login' is we got a validation request but the
 107+ # user wasn't logged in. We show them a form (see OpenIDServerLoginForm)
 108+ # and they post the results, which go to OpenIDServerLogin
 109+ # * 'Trust' is when the user has logged in, but they haven't
 110+ # specified whether it's OK to let the requesting site trust them.
 111+ # If they haven't, we show them a form (see OpenIDServerTrustForm)
 112+ # and let them post results which go to OpenIDServerTrust.
 113+ #
 114+ # OpenID has its own modes; we only handle two of them ('check_setup' and
 115+ # 'check_immediate') and let the OpenID libraries handle the rest.
 116+ #
 117+ # Output may be just a redirect, or a form if we need info.
 118+
 119+ function wfSpecialOpenIDServer($par) {
 120+
 121+ global $wgOut;
 122+
 123+ $server =& OpenIDServer();
 124+
 125+ switch ($par) {
 126+ case 'Login':
 127+ list($request, $sreg) = OpenIDServerFetchValues();
 128+ $result = OpenIDServerLogin($request);
 129+ if ($result) {
 130+ if (is_string($result)) {
 131+ OpenIDServerLoginForm($request, $result);
 132+ return;
 133+ } else {
 134+ OpenIDServerResponse($server, $result);
 135+ return;
 136+ }
 137+ }
 138+ break;
 139+ case 'Trust':
 140+ list($request, $sreg) = OpenIDServerFetchValues();
 141+ $result = OpenIDServerTrust($request, $sreg);
 142+ if ($result) {
 143+ if (is_string($result)) {
 144+ OpenIDServerTrustForm($request, $sreg, $result);
 145+ return;
 146+ } else {
 147+ OpenIDServerResponse($server, $result);
 148+ return;
 149+ }
 150+ }
 151+ break;
 152+ default:
 153+ if (strlen($par)) {
 154+ $wgOut->errorpage('openiderror', 'openiderrortext');
 155+ return;
 156+ } else {
 157+ $method = $_SERVER['REQUEST_METHOD'];
 158+ $query = null;
 159+ if ($method == 'GET') {
 160+ $query = $_GET;
 161+ } else {
 162+ $query = $_POST;
 163+ }
 164+
 165+ $query = Auth_OpenID::fixArgs($query);
 166+ $request = $server->decodeRequest($query);
 167+ $sreg = OpenIdServerSregFromQuery($query);
 168+ $response = NULL;
 169+ break;
 170+ }
 171+ }
 172+
 173+ if (!isset($request)) {
 174+ $wgOut->errorpage('openiderror', 'openiderrortext');
 175+ return;
 176+ }
 177+
 178+ global $wgUser;
 179+
 180+ switch ($request->mode) {
 181+ case "checkid_setup":
 182+ $response = OpenIDServerCheck($server, $request, $sreg, false);
 183+ break;
 184+ case "checkid_immediate":
 185+ $response = OpenIDServerCheck($server, $request, $sreg, true);
 186+ break;
 187+ default:
 188+ # For all the other parts, just let the libs do it
 189+ $response =& $server->handleRequest($request);
 190+ }
 191+
 192+ # OpenIDServerCheck returns NULL if some output (like a form)
 193+ # has been done
 194+
 195+ if (isset($response)) {
 196+ # We're done; clear values
 197+ OpenIDServerClearValues();
 198+ OpenIDServerResponse($server, $response);
 199+ }
 200+ }
 201+
 202+ # Returns the full URL of the special page; we need to pass it around
 203+ # for some requests
 204+
 205+ function OpenIDServerUrl() {
 206+ $nt = Title::makeTitleSafe(NS_SPECIAL, 'OpenIDServer');
 207+ if (isset($nt)) {
 208+ return $nt->getFullURL();
 209+ } else {
 210+ return NULL;
 211+ }
 212+ }
 213+
 214+ # Returns an Auth_OpenID_Server from the libraries. Utility.
 215+
 216+ function OpenIDServer() {
 217+ global $wgOpenIDServerStorePath,
 218+ $wgOpenIDServerStoreType;
 219+
 220+ $store = getOpenIDStore($wgOpenIDServerStoreType,
 221+ 'server',
 222+ array('path' => $wgOpenIDServerStorePath));
 223+
 224+ return new Auth_OpenID_Server($store);
 225+ }
 226+
 227+ # Checks a validation request. $imm means don't run any UI.
 228+ # Fairly meticulous and step-by step, and uses assertions
 229+ # to point out assumptions at each step.
 230+ #
 231+ # XXX: this should probably be broken up into multiple functions for
 232+ # clarity.
 233+
 234+ function OpenIDServerCheck($server, $request, $sreg, $imm = true) {
 235+
 236+ global $wgUser, $wgOut;
 237+
 238+ assert(isset($wgUser) && isset($wgOut));
 239+ assert(isset($server));
 240+ assert(isset($request));
 241+ assert(isset($sreg));
 242+ assert(isset($imm) && is_bool($imm));
 243+
 244+ # Is the passed identity URL a user page?
 245+
 246+ $url = $request->identity;
 247+
 248+ assert(isset($url) && strlen($url) > 0);
 249+
 250+ $name = OpenIDUrlToUserName($url);
 251+
 252+ if (!isset($name) || strlen($name) == 0) {
 253+ wfDebug("OpenID: '$url' not a user page.\n");
 254+ return $request->answer(false, OpenIdServerUrl());
 255+ }
 256+
 257+ assert(isset($name) && strlen($name) > 0);
 258+
 259+ # Is there a logged in user?
 260+
 261+ if ($wgUser->getId() == 0) {
 262+ wfDebug("OpenID: User not logged in.\n");
 263+ if ($imm) {
 264+ return $request->answer(false, OpenIdServerUrl());
 265+ } else {
 266+ # Bank these for later
 267+ OpenIDServerSaveValues($request, $sreg);
 268+ OpenIDServerLoginForm($request);
 269+ return NULL;
 270+ }
 271+ }
 272+
 273+ assert($wgUser->getId() != 0);
 274+
 275+ # Is the user page for the logged-in user?
 276+
 277+ $user = User::newFromName($name);
 278+
 279+ if (!isset($user) ||
 280+ $user->getId() != $wgUser->getId()) {
 281+ wfDebug("OpenID: User from url not logged in user.\n");
 282+ return $request->answer(false, OpenIdServerUrl());
 283+ }
 284+
 285+ assert(isset($user) && $user->getId() == $wgUser->getId() && $user->getId() != 0);
 286+
 287+ # Is the user an OpenID user?
 288+
 289+ $openid = OpenIDGetUserUrl($user);
 290+
 291+ if (isset($openid) && strlen($openid) > 0) {
 292+ wfDebug("OpenID: Not one of our users; logs in with OpenID.\n");
 293+ return $request->answer(false, OpenIdServerUrl());
 294+ }
 295+
 296+ assert(is_array($sreg));
 297+
 298+ # Does the request require sreg fields that the user has not specified?
 299+
 300+ if (array_key_exists('required', $sreg)) {
 301+ $notFound = false;
 302+ foreach ($sreg['required'] as $reqfield) {
 303+ if (is_null(OpenIdGetUserField($user, $reqfield))) {
 304+ $notFound = true;
 305+ break;
 306+ }
 307+ }
 308+ if ($notFound) {
 309+ wfDebug("OpenID: Consumer demands info we don't have.\n");
 310+ return $request->answer(false, OpenIdServerUrl());
 311+ }
 312+ }
 313+
 314+ # Trust check
 315+
 316+ $trust_root = $request->trust_root;
 317+
 318+ assert(isset($trust_root) && is_string($trust_root) && strlen($trust_root) > 0);
 319+
 320+ $trust = OpenIDGetUserTrust($user, $trust_root);
 321+
 322+ # Is there a trust record?
 323+
 324+ if (is_null($trust)) {
 325+ wfDebug("OpenID: No trust record.\n");
 326+ if ($imm) {
 327+ return $request->answer(false, OpenIdServerUrl());
 328+ } else {
 329+ # Bank these for later
 330+ OpenIDServerSaveValues($request, $sreg);
 331+ OpenIDServerTrustForm($request, $sreg);
 332+ return NULL;
 333+ }
 334+ }
 335+
 336+ assert(!is_null($trust));
 337+
 338+ # Is the trust record _not_ to allow trust?
 339+ # NB: exactly equal
 340+
 341+ if ($trust === false) {
 342+ wfDebug("OpenID: User specified not to allow trust.\n");
 343+ return $request->answer(false, OpenIdServerUrl());
 344+ }
 345+
 346+ assert(isset($trust) && is_array($trust));
 347+
 348+ # Does the request require sreg fields that the user has
 349+ # not allowed us to pass, or has not specified?
 350+
 351+ if (array_key_exists('required', $sreg)) {
 352+ $notFound = false;
 353+ foreach ($sreg['required'] as $reqfield) {
 354+ if (!in_array($reqfield, $trust) ||
 355+ is_null(OpenIdGetUserField($user, $reqfield))) {
 356+ $notFound = true;
 357+ break;
 358+ }
 359+ }
 360+ if ($notFound) {
 361+ wfDebug("OpenID: Consumer demands info user doesn't want shared.\n");
 362+ return $request->answer(false, OpenIdServerUrl());
 363+ }
 364+ }
 365+
 366+ # assert(all required sreg fields are in $trust)
 367+
 368+ # XXX: run a hook here to check
 369+
 370+ # SUCCESS
 371+
 372+ $response_fields = array_intersect(array_unique(array_merge($sreg['required'], $sreg['optional'])),
 373+ $trust);
 374+
 375+ $response = $request->answer(true);
 376+
 377+ assert(isset($response));
 378+
 379+ foreach ($response_fields as $field) {
 380+ $value = OpenIDGetUserField($user, $field);
 381+ if (!is_null($value)) {
 382+ $response->addField('sreg', $field, $value);
 383+ }
 384+ }
 385+
 386+ return $response;
 387+ }
 388+
 389+ # Get the user's configured trust value for a particular trust root.
 390+ # Returns one of three values:
 391+ # * NULL -> no stored trust preferences
 392+ # * false -> stored trust preference is not to trust
 393+ # * array -> possibly empty array of allowed profile fields; trust is OK
 394+
 395+ function OpenIDGetUserTrust($user, $trust_root) {
 396+ static $allFields = array('nickname', 'fullname', 'email', 'language');
 397+ global $wgOpenIDServerForceAllowTrust;
 398+
 399+ foreach ($wgOpenIDServerForceAllowTrust as $force) {
 400+ if (preg_match($force, $trust_root)) {
 401+ return $allFields;
 402+ }
 403+ }
 404+
 405+ $trust_array = OpenIDGetUserTrustArray($user);
 406+
 407+ if (array_key_exists($trust_root, $trust_array)) {
 408+ return $trust_array[$trust_root];
 409+ } else {
 410+ return null; # Unspecified trust
 411+ }
 412+ }
 413+
 414+ function OpenIDSetUserTrust(&$user, $trust_root, $value) {
 415+
 416+ $trust_array = OpenIDGetUserTrustArray($user);
 417+
 418+ if (is_null($value)) {
 419+ if (array_key_exists($trust_root, $trust_array)) {
 420+ unset($trust_array[$trust_root]);
 421+ }
 422+ } else {
 423+ $trust_array[$trust_root] = $value;
 424+ }
 425+
 426+ OpenIDSetUserTrustArray($user, $trust_array);
 427+ }
 428+
 429+ function OpenIDGetUserTrustArray($user) {
 430+ $trust_array = array();
 431+ $trust_str = $user->getOption('openid_trust');
 432+ if (strlen($trust_str) > 0) {
 433+ $trust_records = explode("\x1E", $trust_str);
 434+ foreach ($trust_records as $record) {
 435+ $fields = explode("\x1F", $record);
 436+ $trust_root = array_shift($fields);
 437+ if (count($fields) == 1 && strcmp($fields[0], 'no') == 0) {
 438+ $trust_array[$trust_root] = false;
 439+ } else {
 440+ $fields = array_map('trim', $fields);
 441+ $fields = array_filter($fields, 'OpenIDValidField');
 442+ $trust_array[$trust_root] = $fields;
 443+ }
 444+ }
 445+ }
 446+ return $trust_array;
 447+ }
 448+
 449+ function OpenIDSetUserTrustArray(&$user, $arr) {
 450+ $trust_records = array();
 451+ foreach ($arr as $root => $value) {
 452+ if ($value === false) {
 453+ $record = implode("\x1F", array($root, 'no'));
 454+ } else if (is_array($value)) {
 455+ if (count($value) == 0) {
 456+ $record = $root;
 457+ } else {
 458+ $value = array_map('trim', $value);
 459+ $value = array_filter($value, 'OpenIDValidField');
 460+ $record = implode("\x1F", array_merge(array($root), $value));
 461+ }
 462+ } else {
 463+ continue;
 464+ }
 465+ $trust_records[] = $record;
 466+ }
 467+ $trust_str = implode("\x1E", $trust_records);
 468+ $user->setOption('openid_trust', $trust_str);
 469+ }
 470+
 471+ function OpenIDValidField($name) {
 472+ # XXX: eventually add timezone
 473+ static $fields = array('nickname', 'email', 'fullname', 'language');
 474+ return in_array($name, $fields);
 475+ }
 476+
 477+ function OpenIDServerSregFromQuery($query) {
 478+ $sreg = array('required' => array(), 'optional' => array(),
 479+ 'policy_url' => NULL);
 480+ if (array_key_exists('openid.sreg.required', $query)) {
 481+ $sreg['required'] = explode(',', $query['openid.sreg.required']);
 482+ }
 483+ if (array_key_exists('openid.sreg.optional', $query)) {
 484+ $sreg['optional'] = explode(',', $query['openid.sreg.optional']);
 485+ }
 486+ if (array_key_exists('openid.sreg.policy_url', $query)) {
 487+ $sreg['policy_url'] = $query['openid.sreg.policy_url'];
 488+ }
 489+ return $sreg;
 490+ }
 491+
 492+ function OpenIDSetUserField(&$user, $field, $value) {
 493+ switch ($field) {
 494+ case 'fullname':
 495+ $user->setRealName($value);
 496+ return true;
 497+ break;
 498+ case 'email':
 499+ # FIXME: deal with validation
 500+ $user->setEmail($value);
 501+ return true;
 502+ break;
 503+ case 'language':
 504+ $user->setOption('language', $value);
 505+ return true;
 506+ break;
 507+ default:
 508+ return false;
 509+ }
 510+ }
 511+
 512+ function OpenIDGetUserField($user, $field) {
 513+ switch ($field) {
 514+ case 'nickname':
 515+ return $user->getName();
 516+ break;
 517+ case 'fullname':
 518+ return $user->getRealName();
 519+ break;
 520+ case 'email':
 521+ return $user->getEmail();
 522+ break;
 523+ case 'language':
 524+ return $user->getOption('language');
 525+ break;
 526+ default:
 527+ return NULL;
 528+ }
 529+ }
 530+
 531+ function OpenIDServerResponse($server, $response) {
 532+ global $wgOut;
 533+
 534+ $wgOut->disable();
 535+
 536+ $wr =& $server->encodeResponse($response);
 537+
 538+ header("Status: " . $wr->code);
 539+
 540+ foreach ($wr->headers as $k => $v) {
 541+ header("$k: $v");
 542+ }
 543+
 544+ print $wr->body;
 545+ return;
 546+ }
 547+
 548+ function OpenIDServerLoginForm($request, $msg = null) {
 549+
 550+ global $wgOut, $wgUser;
 551+
 552+ $url = $request->identity;
 553+ $name = OpenIDUrlToUserName($url);
 554+ $trust_root = $request->trust_root;
 555+
 556+ $instructions = wfMsg('openidserverlogininstructions', $url, $name, $trust_root);
 557+
 558+ $username = wfMsg('yourname');
 559+ $password = wfMsg('yourpassword');
 560+ $ok = wfMsg('ok');
 561+ $cancel = wfMsg('cancel');
 562+
 563+ if (isset($msg)) {
 564+ $wgOut->addHTML("<p class='error'>{$msg}</p>");
 565+ }
 566+
 567+ $sk = $wgUser->getSkin();
 568+
 569+ $wgOut->addHTML("<p>{$instructions}</p>" .
 570+ '<form action="' . $sk->makeSpecialUrl('OpenIDServer/Login') . '" method="POST">' .
 571+ '<table>' .
 572+ "<tr><td><label for='username'>{$username}</label></td>" .
 573+ ' <td><span id="username">' . htmlspecialchars($name) . '</span></td></tr>' .
 574+ "<tr><td><label for='password'>{$password}</label></td>" .
 575+ ' <td><input type="password" name="wpPassword" size="32" value="" /></td></tr>' .
 576+ "<tr><td colspan='2'><input type='submit' name='wpOK' value='{$ok}' /> <input type='submit' name='wpCancel' value='{$cancel}' /></td></tr>" .
 577+ '</table>' .
 578+ '</form>');
 579+ }
 580+
 581+ function OpenIDServerSaveValues($request, $sreg) {
 582+ global $wgSessionStarted, $wgUser;
 583+
 584+ if (!$wgSessionStarted) {
 585+ $wgUser->SetupSession();
 586+ }
 587+
 588+ $_SESSION['openid_server_request'] = $request;
 589+ $_SESSION['openid_server_sreg'] = $sreg;
 590+
 591+ return true;
 592+ }
 593+
 594+ function OpenIDServerFetchValues() {
 595+ return array($_SESSION['openid_server_request'], $_SESSION['openid_server_sreg']);
 596+ }
 597+
 598+ function OpenIDServerClearValues() {
 599+ unset($_SESSION['openid_server_request']);
 600+ unset($_SESSION['openid_server_sreg']);
 601+ return true;
 602+ }
 603+
 604+ function OpenIDServerLogin($request) {
 605+
 606+ global $wgRequest, $wgUser;
 607+
 608+ assert(isset($request));
 609+
 610+ assert(isset($wgRequest));
 611+
 612+ if ($wgRequest->getCheck('wpCancel')) {
 613+ return $request->answer(false);
 614+ }
 615+
 616+ $password = $wgRequest->getText('wpPassword');
 617+
 618+ if (!isset($password) || strlen($password) == 0) {
 619+ return wfMsg('wrongpasswordempty');
 620+ }
 621+
 622+ assert (isset($password) && strlen($password) > 0);
 623+
 624+ $url = $request->identity;
 625+
 626+ assert(isset($url) && is_string($url) && strlen($url) > 0);
 627+
 628+ $name = OpenIDUrlToUserName($url);
 629+
 630+ assert(isset($name) && is_string($name) && strlen($name) > 0);
 631+
 632+ $user = User::newFromName($name);
 633+
 634+ assert(isset($user));
 635+
 636+ if (!$user->checkPassword($password)) {
 637+ return wfMsg('wrongpassword');
 638+ } else {
 639+ $id = $user->getId();
 640+ $wgUser = $user;
 641+ $wgUser->SetupSession();
 642+ $wgUser->SetCookies();
 643+ wfRunHooks('UserLoginComplete', array(&$wgUser));
 644+ return false;
 645+ }
 646+ }
 647+
 648+ function OpenIDServerTrustForm($request, $sreg, $msg = NULL) {
 649+
 650+ global $wgOut, $wgUser;
 651+
 652+ $url = $request->identity;
 653+ $name = OpenIDUrlToUserName($url);
 654+ $trust_root = $request->trust_root;
 655+
 656+ $instructions = wfMsg('openidtrustinstructions', $trust_root);
 657+ $allow = wfMsg('openidallowtrust', $trust_root);
 658+
 659+ if (is_null($sreg['policy_url'])) {
 660+ $policy = wfMsg('openidnopolicy');
 661+ } else {
 662+ $policy = wfMsg('openidpolicy', $sreg['policy_url']);
 663+ }
 664+
 665+ if (isset($msg)) {
 666+ $wgOut->addHTML("<p class='error'>{$msg}</p>");
 667+ }
 668+
 669+ $ok = wfMsg('ok');
 670+ $cancel = wfMsg('cancel');
 671+
 672+ $sk = $wgUser->getSkin();
 673+
 674+ $wgOut->addHTML("<p>{$instructions}</p>" .
 675+ '<form action="' . $sk->makeSpecialUrl('OpenIDServer/Trust') . '" method="POST">' .
 676+ '<input name="wpAllowTrust" type="checkbox" value="on" checked="checked" id="wpAllowTrust">' .
 677+ '<label for="wpAllowTrust">' . $allow . '</label><br />');
 678+
 679+ $fields = array_filter(array_unique(array_merge($sreg['optional'], $sreg['required'])),
 680+ 'OpenIDValidField');
 681+
 682+ if (count($fields) > 0) {
 683+ $wgOut->addHTML('<table>');
 684+ foreach ($fields as $field) {
 685+ $wgOut->addHTML("<tr>");
 686+ $wgOut->addHTML("<th><label for='wpAllow{$field}'>");
 687+ $wgOut->addHTML(wfMsg("openid$field"));
 688+ $wgOut->addHTML("</label></th>");
 689+ $value = OpenIDGetUserField($wgUser, $field);
 690+ $wgOut->addHTML("</td>");
 691+ $wgOut->addHTML("<td> " . ((is_null($value)) ? '' : $value) . "</td>");
 692+ $wgOut->addHTML("<td>" . ((in_array($field, $sreg['required'])) ? wfMsg('openidrequired') : wfMsg('openidoptional')) . "</td>");
 693+ $wgOut->addHTML("<td><input name='wpAllow{$field}' id='wpAllow{$field}' type='checkbox'");
 694+ if (!is_null($value)) {
 695+ $wgOut->addHTML(" value='on' checked='checked' />");
 696+ } else {
 697+ $wgOut->addHTML(" disabled='disabled' />");
 698+ }
 699+ $wgOut->addHTML("</tr>");
 700+ }
 701+ $wgOut->addHTML('</table>');
 702+ }
 703+ $wgOut->addHTML("<input type='submit' name='wpOK' value='{$ok}' /> <input type='submit' name='wpCancel' value='{$cancel}' />");
 704+ return NULL;
 705+ }
 706+
 707+ function OpenIDServerTrust($request, $sreg) {
 708+ global $wgRequest, $wgUser;
 709+
 710+ assert(isset($request));
 711+ assert(isset($sreg));
 712+ assert(isset($wgRequest));
 713+
 714+ if ($wgRequest->getCheck('wpCancel')) {
 715+ return $request->answer(false);
 716+ }
 717+
 718+ $trust_root = $request->trust_root;
 719+
 720+ assert(isset($trust_root) && strlen($trust_root) > 0);
 721+
 722+ # If they don't want us to allow trust, save that.
 723+
 724+ if (!$wgRequest->getCheck('wpAllowTrust')) {
 725+
 726+ OpenIDSetUserTrust($wgUser, $trust_root, false);
 727+ # Set'em and sav'em
 728+ $wgUser->saveSettings();
 729+ } else {
 730+
 731+ $fields = array_filter(array_unique(array_merge($sreg['optional'], $sreg['required'])),
 732+ 'OpenIDValidField');
 733+
 734+ $allow = array();
 735+
 736+ foreach ($fields as $field) {
 737+ if ($wgRequest->getCheck('wpAllow' . $field)) {
 738+ $allow[] = $field;
 739+ }
 740+ }
 741+
 742+ OpenIDSetUserTrust($wgUser, $trust_root, $allow);
 743+ # Set'em and sav'em
 744+ $wgUser->saveSettings();
 745+ }
 746+
 747+ }
 748+
 749+ # Converts an URL to a user name, if possible
 750+
 751+ function OpenIDUrlToUserName($url) {
 752+
 753+ global $wgArticlePath, $wgServer;
 754+
 755+ # URL must be a string
 756+
 757+ if (!isset($url) || !is_string($url) || strlen($url) == 0) {
 758+ return null;
 759+ }
 760+
 761+ # it must start with our server, case doesn't matter
 762+
 763+ if (strpos(strtolower($url), strtolower($wgServer)) !== 0) {
 764+ return null;
 765+ }
 766+
 767+ $parts = parse_url($url);
 768+
 769+ $relative = $parts['path'];
 770+ if (!is_null($parts['query']) && strlen($parts['query']) > 0) {
 771+ $relative .= '?' . $parts['query'];
 772+ }
 773+
 774+ # Use regexps to extract user name
 775+
 776+ $pattern = str_replace('$1', '(.*)', $wgArticlePath);
 777+ $pattern = str_replace('?', '\?', $pattern);
 778+ # Can't have a pound-sign in the relative, since that's for fragments
 779+ if (!preg_match("#$pattern#", $relative, $matches)) {
 780+ return null;
 781+ } else {
 782+ $titletext = urldecode($matches[1]);
 783+ $nt = Title::newFromText($titletext);
 784+ if (is_null($nt) || $nt->getNamespace() != NS_USER) {
 785+ return null;
 786+ } else {
 787+ return $nt->getText();
 788+ }
 789+ }
 790+ }
 791+}
 792+
Property changes on: tags/extensions/OpenID/REL0_7_0/Server.php
___________________________________________________________________
Added: svn:eol-style
1793 + native
Index: tags/extensions/OpenID/REL0_7_0/README
@@ -0,0 +1,313 @@
 2+MediaWiki OpenID extension
 3+
 4+version 0.7.0
 5+14 Sep 2007
 6+
 7+This is the README file for the OpenID extension for MediaWiki
 8+software. The extension is only useful if you've got a MediaWiki
 9+installation; it can only be installed by the administrator of the site.
 10+
 11+The extension lets users log in with an OpenID
 12+(http://www.openid.net/) instead of a username and password. An OpenID
 13+is a special URL that people can use to log in to a Web site. The
 14+extension also lets users who have an account on the wiki log in to
 15+other OpenID-aware Web sites with their wiki user page as their OpenID.
 16+
 17+Typical uses:
 18+
 19+* Single-signon between multiple affiliated wikis and other sites. We
 20+ have 12 wikis that work together for Wikitravel, and users can login
 21+ to different Wikitravel wikis with their home wiki account.
 22+* Single-signon across the Internet. OpenID isn't that well known
 23+ ''yet'', but theoretically someone could login to their OpenID
 24+ identity server in the morning and not have to login to another site
 25+ for the rest of the day.
 26+
 27+This is an early version of the extension and it's almost sure to have
 28+bugs. (Don't despair, though: this is running in production on
 29+Wikitravel [http://wikitravel.org/], a fairly big MW installation.)
 30+See the BUGS section below for info on how to report problems.
 31+
 32+== License ==
 33+
 34+Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 35+
 36+This program is free software; you can redistribute it and/or modify
 37+it under the terms of the GNU General Public License as published by
 38+the Free Software Foundation; either version 2 of the License, or
 39+(at your option) any later version.
 40+
 41+This program is distributed in the hope that it will be useful,
 42+but WITHOUT ANY WARRANTY; without even the implied warranty of
 43+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 44+GNU General Public License for more details.
 45+
 46+You should have received a copy of the GNU General Public License
 47+along with this program; if not, write to the Free Software
 48+Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 49+
 50+== Author ==
 51+
 52+Evan Prodromou <evan@wikitravel.org>
 53+
 54+Patches for YADIS support and FileStore storage by Jonathan Daugherty
 55+<cygnus@janrain.com>.
 56+
 57+== Pre-requisites ==
 58+
 59+This software has been tested in production with MediaWiki 1.10.x. It
 60+may or may not work with earlier or later versions, but I'm interested
 61+in making later versions work, and I'd be happy to make minor changes
 62+to make older, unsupported versions work too.
 63+
 64+The software depends on the OpenIDEnabled.com PHP library for OpenID,
 65+which in turn depends on the OpenIDEnabled.com PHP library for YADIS.
 66+At the time of this writing, info on installing these libraries was
 67+available here:
 68+
 69+ http://www.openidenabled.com/php-openid/
 70+
 71+The last version tested with is 1.2.3 and 2.0.0 rc2. There are also
 72+some required extensions; see the OpenIDEnabled documentation for
 73+details. This software has been tested with the gmp and curl PHP
 74+extensions installed, and it's recommended that you install them, too.
 75+
 76+Note that some versions of MediaWiki overwrite the PHP library path in
 77+LocalSettings.php. You may need to add the path to your PHP library
 78+directory to the $path variable, like "/usr/share/php" or
 79+"/usr/local/share/php".
 80+
 81+== Installation ==
 82+
 83+To install, copy all the files in the archive you downloaded to the
 84+OpenID subdirectory of the extensions subdirectory of your MediaWiki
 85+installation. Note that the software depends on having its code all in
 86+the "OpenID" sub-directory; naming it "OpenID-Test" or "newextension1"
 87+or whatever won't work.
 88+
 89+You must create a table in your MediaWiki database to hold the OpenID
 90+URL mappings. The openid_table.sql script in this directory should do
 91+the trick. Typically you do this using the mysql command-line client,
 92+like so:
 93+
 94+ mysql -h yourdbhost -u youradminuser -p yourwikidb < openid_table.sql
 95+
 96+Version 0.3 and below used a different database structure that was
 97+pretty inefficient. If you installed this extension before, you should
 98+copy the optionToTable.php script to your MediaWiki "maintenance"
 99+directory and run it from the command line. This will copy the OpenID
 100+mappings from the user table to the new table (but it doesn't erase
 101+the old data... just in case).
 102+
 103+In your MediaWiki LocalSettings.php, add the following line some place
 104+towards the bottom of the file:
 105+
 106+ require_once("$IP/extensions/OpenID/OpenID.php");
 107+
 108+Theoretically it should work out of the box, but you'll almost
 109+definitely want to set the trust root and access controls (see
 110+Configuration below).
 111+
 112+=== Caching ===
 113+
 114+The extension stores some semi-persistent data in the $wgMemc object.
 115+Where that object stores its data is configured by $wgMainCacheType in
 116+LocalSettings.php. Often this is set at installation time by the
 117+MediaWiki configuration script.
 118+
 119+Typically for big wiki sites $wgMemc is a front-end for memcached
 120+(http://www.danga.com/memcached/), so $wgMainCacheType =
 121+CACHE_MEMCACHED.
 122+
 123+For smaller sites, $wgMemc typically uses eAccelerator to do both data
 124+caching and bytecode caching. In this case, $wgMainCacheType =
 125+CACHE_ACCEL.
 126+
 127+In case you don't have either installed, try $wgMainCacheType =
 128+CACHE_ANYTHING. There's usually some fallback mechanism to store this
 129+data for you.
 130+
 131+If none of that works, you can use a filesystem-based storage; see the
 132+Configuration options below.
 133+
 134+== Logging in using OpenID ==
 135+
 136+To log in to the wiki using an OpenID, go to the Special:OpenIDLogin
 137+page on the wiki. Add the OpenID identity URL to the login box, and
 138+click "Verify".
 139+
 140+This ''should'' take you to the OpenID server for your identity, where
 141+you can either log in (if you're not already) or approve allowing the
 142+wiki to use your OpenID for logging in. If the OpenID server supports
 143+the Simple Registration Extension ('sreg'), it may also ask you
 144+whether to share personal information like your preferred nickname,
 145+real name, email address, etc. Choose as you wish.
 146+
 147+Once you're logged in to your OpenID server, and you've finished
 148+approving the login, you should return to the wiki from whence you
 149+came automatically.
 150+
 151+Every user who logs in with an OpenID identity for the first time will
 152+be assigned a "fake" username in the local wiki. (This just makes
 153+things work better.)
 154+
 155+If you've allowed your nickname to be passed to the wiki, and it's not
 156+already taken, and it's a legal MediaWiki user name, then it should
 157+use that for your login automatically.
 158+
 159+If not, the extension will try to make up some good candidate
 160+usernames for you and present you with a choice. If you don't like any
 161+of them, you can make up your own.
 162+
 163+After you're logged in, you can edit, read, write, and do all the
 164+other things that MediaWiki users do. Since you've got a "real"
 165+account, you'll also have a home page and a message page and such. It
 166+should also be possible to assign extra permissions ('sysop',
 167+'bureaucrat') to the account. You can log out as normal.
 168+
 169+To log back in, use the OpenIDLogin page again. Don't try to login
 170+using the regular login page, since it won't work.
 171+
 172+You can log in with an Interwiki abbreviation of an URL right now, but
 173+that's experimental and may disappear in later versions. Don't fall in
 174+love with this convenient, useful feature. You may get hurt.
 175+
 176+== Using a MediaWiki account as an OpenID ==
 177+
 178+To log in to other sites with your MediaWiki account, your OpenID
 179+identity URL is the full URL of your MediaWiki user page. So, for
 180+example, the author's identity URL is:
 181+
 182+ http://wikitravel.org/en/User:Evan
 183+
 184+When you use this OpenID with another site, logging in should take you
 185+to the wiki site. You may need to enter your password if you're not
 186+already logged in.
 187+
 188+You'll then be asked if you want to let the other site log you in, and
 189+if you want the MediaWiki wiki to share your personal information
 190+(nickname, email, full name, language) with the other site. Choose
 191+what feels comfortable to you. For some sites, you may not be asked;
 192+see Configuration below.
 193+
 194+Once you've finished deciding, the other site will finish the login.
 195+
 196+You can't log in through OpenID on the same server. You can't use the
 197+user page for a fake account created for an OpenID login as an OpenID
 198+itself.
 199+
 200+== Configuration ==
 201+
 202+The administrator can configure these variables in the
 203+LocalSettings.php file. Please read carefully.
 204+
 205+* $wgTrustRoot -- This is an URL that identifies your site to OpenID
 206+ servers. Typically, it's the "root" url of the site, like
 207+ "http://en.wikipedia.org/" or "http://wikitravel.org/it/". If this is
 208+ not set, the software will make a half-hearted guess, but it's not
 209+ very good and you should probably just set it.
 210+
 211+* $wgOpenIDConsumerDenyByDefault -- The administrator can decide which
 212+ OpenIDs are allowed to login to their server. If this flag is
 213+ true, only those OpenIDs that match one of the $wgOpenIDConsumerAllow
 214+ and not one of the $wgOpenIDConsumerDeny patterns will be allowed to
 215+ log in. If it is false, all OpenIDs are allowed to log in, unless
 216+ they are matched by an $wgOpenIDConsumerDeny pattern and not an
 217+ $wgOpenIDConsumerAllow. Typically you'll set this to true for
 218+ testing and then false for general use.
 219+
 220+* $wgOpenIDConsumerAllow -- an array of regular expressions that match
 221+ OpenIDs you want to allow to log in. For example,
 222+ "@^(http://)?wikitravel.org/@" will allow OpenIDs from the Wikitravel
 223+ domain.
 224+
 225+* $wgOpenIDConsumerDeny -- an array of regular expressions that match
 226+ OpenIDs you want to deny access to. This is mostly useful for
 227+ servers that are known to be bad. Example: "#^(http://)?example.com/#".
 228+
 229+* $wgOpenIDServerForceAllowTrust -- an array of regular expressions
 230+ that match trust roots that you want to skip trust checks for when
 231+ the user logs in from those sites. A typical example would be a
 232+ closely federated cluster of sites (like Wikimedia, Wikia, or
 233+ Wikitravel) where the personal data is available to the trusting
 234+ server ''anyways''. Be very careful using this across organizational
 235+ boundaries.
 236+
 237+* $wgOpenIDConsumerStoreType and $wgOpenIDServerStoreType -- strings
 238+ denoting the type of storage to be used to store OpenID assocation
 239+ data when acting as an OpenID relying party (consumer) and server,
 240+ respectively. Valid values are "file" and "memc". If the value for
 241+ one or both is "file", $wgOpenIDConsumerStorePath or
 242+ $wgOpenIDServerStorePath must be set, respectively (see below). If
 243+ either of these variables is set to an invalid value, an error page
 244+ will be displayed. The default is "memc", unless the main cache is
 245+ set to CACHE_NONE (default for MW), in which case it is "file".
 246+
 247+* $wgOpenIDConsumerStorePath and $wgOpenIDServerStorePath -- strings
 248+ specifying the paths where OpenID assocation data should be stored
 249+ when acting as a relying party (consumer) or server, respectively.
 250+ Each of these need only be set if the store type settings (above)
 251+ are set to "file", respectively. These strings, if both are set,
 252+ MUST NOT be equal. If the store type is "file", the default here is
 253+ "/tmp/$wgDBname/openidconsumer/" and "/tmp/$wgDBname/openidserver/"
 254+ respectively. The path will be automatically created if it doesn't
 255+ exist at runtime.
 256+
 257+* $wgHideOpenIDLoginLink -- boolean that says whether or not to hide
 258+ the OpenID login link in the personal URLs. Typically you'd use this
 259+ if you've already got some other method for showing the OpenID login
 260+ link, like in your skin. Note that it will *not* prevent login if
 261+ the user navigates to Special:OpenIDLogin directly; it's simply
 262+ cosmetic. This is mostly a backwards-compatibility option.
 263+
 264+* $wgOpenIDLoginLogoUrl -- Url of the OpenID login logo. Defaults to
 265+ 'http://www.openid.net/login-bg.gif', but you may want to move it to
 266+ a local URL, or an URL on a CDN, if that kind of thing floats your
 267+ boat.
 268+
 269+* $wgOpenIDShowUrlOnUserPage -- whether to show the OpenID identity URL
 270+ on a user's home page. Possible values are 'always', 'never', or 'user'
 271+ (lets the user decide). Default is 'user'.
 272+
 273+== Skins ==
 274+
 275+If you are customizing a skin, and you want to show the OpenID
 276+identity for a user (say, on their user page), use the function
 277+OpenIDGetUserUrl($user). It takes a User object (not a name or an id!)
 278+and returns the user's OpenID identity if it exists, or null if it
 279+doesn't.
 280+
 281+== Translation ==
 282+
 283+The user interface strings for this extension are configurable through
 284+the same Special:Allmessages page as MediaWiki itself. They all start
 285+with "openid", and they're no more or less cryptic than MediaWiki's.
 286+
 287+== OpenID services ==
 288+
 289+These are some of the OpenID services I tested this extension with;
 290+all have free signup for identities if you want to test, too.
 291+
 292+* http://www.myopenid.com/ -- uses Simple Registration Extension
 293+* http://getopenid.com/
 294+* http://www.typekey.com/
 295+* http://www.claimid.com/
 296+* http://pip.verisignlabs.com/
 297+
 298+== Bugs and enhancements ==
 299+
 300+Bugs or feature requests can be sent to the author at
 301+evan@wikitravel.org. The TODO file in this distribution has stuff I
 302+think needs to be todone; + marks show things I've already done, and -
 303+shows things that are yet to be done.
 304+
 305+The big changes for the future:
 306+
 307+* Snazzier UI -- better HTML, sexier forms
 308+* Configure some stuff through Special:Preferences
 309+* Auto-login if you've logged in before with an OpenID, and are logged
 310+ into that account now
 311+
 312+Probably a ways down the line:
 313+
 314+* Allow delegation
Property changes on: tags/extensions/OpenID/REL0_7_0/README
___________________________________________________________________
Added: svn:eol-style
1315 + native
Index: tags/extensions/OpenID/REL0_7_0/Consumer.php
@@ -0,0 +1,807 @@
 2+<?php
 3+/**
 4+ * Consumer.php -- Consumer side of OpenID site
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+if (defined('MEDIAWIKI')) {
 27+
 28+ require_once("Auth/OpenID/Consumer.php");
 29+
 30+ # Defines the trust root for this server
 31+ # If null, we make a guess
 32+
 33+ $wgTrustRoot = null;
 34+
 35+ # When using deny and allow arrays, defines how the security works.
 36+ # If true, works like "Order Allow,Deny" in Apache; deny by default,
 37+ # allow items that match allow that don't match deny to pass.
 38+ # If false, works like "Order Deny,Allow" in Apache; allow by default,
 39+ # deny items in deny that aren't in allow.
 40+
 41+ $wgOpenIDConsumerDenyByDefault = false;
 42+
 43+ # Which partners to allow; regexps here. See above.
 44+
 45+ $wgOpenIDConsumerAllow = array();
 46+
 47+ # Which partners to deny; regexps here. See above.
 48+
 49+ $wgOpenIDConsumerDeny = array();
 50+
 51+ # Where to store transitory data. Can be 'memc' for the $wgMemc
 52+ # global caching object, or 'file' if caching is turned off
 53+ # completely and you need a fallback.
 54+
 55+ # Default is memc unless the global cache is disabled.
 56+
 57+ $wgOpenIDConsumerStoreType = ($wgMainCacheType == CACHE_NONE) ? 'file' : 'memc';
 58+
 59+ # If the store type is set to 'file', this is is the name of a
 60+ # directory to store the data in.
 61+
 62+ $wgOpenIDConsumerStorePath = ($wgMainCacheType == CACHE_NONE) ? "/tmp/$wgDBname/openidconsumer/" : NULL;
 63+
 64+ # Expiration time for the OpenID cookie. Lets the user re-authenticate
 65+ # automatically if their session is expired. Only really useful if
 66+ # it's much greater than $wgCookieExpiration. Default: about one year.
 67+
 68+ $wgOpenIDCookieExpiration = 365 * 24 * 60 * 60;
 69+
 70+ function wfSpecialOpenIDLogin($par) {
 71+ global $wgRequest, $wgUser, $wgOut;
 72+
 73+ if ($wgUser->getID() != 0) {
 74+ OpenIDAlreadyLoggedIn();
 75+ return;
 76+ }
 77+
 78+ if ($wgRequest->getText('returnto')) {
 79+ OpenIDConsumerSetReturnTo($wgRequest->getText('returnto'));
 80+ }
 81+
 82+ $openid_url = $wgRequest->getText('openid_url');
 83+ if (isset($openid_url) && strlen($openid_url) > 0) {
 84+ OpenIDLogin($openid_url);
 85+ } else {
 86+ OpenIDLoginForm();
 87+ }
 88+ }
 89+
 90+ function wfSpecialOpenIDFinish($par) {
 91+
 92+ global $wgUser, $wgOut, $wgRequest;
 93+
 94+ # Shouldn't work if you're already logged in.
 95+
 96+ if ($wgUser->getID() != 0) {
 97+ OpenIDAlreadyLoggedIn();
 98+ return;
 99+ }
 100+
 101+ $consumer = OpenIDConsumer();
 102+
 103+ switch ($par) {
 104+ case 'ChooseName':
 105+ list($response, $sreg) = OpenIDConsumerFetchValues();
 106+ if (!isset($response) ||
 107+ $response->status != Auth_OpenID_SUCCESS ||
 108+ !isset($response->identity_url)) {
 109+ OpenIDConsumerClearValues();
 110+ # No messing around, here
 111+ $wgOut->errorpage('openiderror', 'openiderrortext');
 112+ return;
 113+ }
 114+
 115+ if ($wgRequest->getCheck('wpCancel')) {
 116+ OpenIDConsumerClearValues();
 117+ $wgOut->errorpage('openidcancel', 'openidcanceltext');
 118+ return;
 119+ }
 120+
 121+ $choice = $wgRequest->getText('wpNameChoice');
 122+ $nameValue = $wgRequest->getText('wpNameValue');
 123+ wfDebug("OpenID: Got form values '$choice' and '$nameValue'\n");
 124+
 125+ $name = OpenIDGetName($response, $sreg, $choice, $nameValue);
 126+
 127+ if (!$name || !OpenIDUserNameOK($name)) {
 128+ OpenIDChooseNameForm($response, $sreg);
 129+ return;
 130+ }
 131+
 132+ $user = OpenIDCreateUser($response->identity_url, $sreg, $name);
 133+
 134+ if (!isset($user)) {
 135+ OpenIDConsumerClearValues();
 136+ $wgOut->errorpage('openiderror', 'openiderrortext');
 137+ return;
 138+ }
 139+
 140+ $wgUser = $user;
 141+ OpenIDConsumerClearValues();
 142+
 143+ OpenIDFinishLogin($response->identity_url);
 144+ break;
 145+
 146+ default: # No parameter, returning from a server
 147+
 148+ $response = $consumer->complete($_GET);
 149+
 150+ if (!isset($response)) {
 151+ $wgOut->errorpage('openiderror', 'openiderrortext');
 152+ return;
 153+ }
 154+
 155+ switch ($response->status) {
 156+ case Auth_OpenID_CANCEL:
 157+ // This means the authentication was cancelled.
 158+ $wgOut->errorpage('openidcancel', 'openidcanceltext');
 159+ break;
 160+ case Auth_OpenID_FAILURE:
 161+ $wgOut->errorpage('openidfailure', 'openidfailuretext');
 162+ break;
 163+ case Auth_OpenID_SUCCESS:
 164+ // This means the authentication succeeded.
 165+ $openid = $response->identity_url;
 166+ $sreg = $response->extensionResponse('sreg');
 167+
 168+ if (!isset($openid)) {
 169+ $wgOut->errorpage('openiderror', 'openiderrortext');
 170+ return;
 171+ }
 172+
 173+ $user = OpenIDGetUser($openid);
 174+
 175+ if (isset($user)) {
 176+ OpenIDUpdateUser($user, $sreg); # update from server
 177+ } else {
 178+ # For easy names
 179+ $name = OpenIDCreateName($openid, $sreg);
 180+ if ($name) {
 181+ $user = OpenIDCreateUser($openid, $sreg, $name);
 182+ } else {
 183+ # For hard names
 184+ OpenIDConsumerSaveValues($response, $sreg);
 185+ OpenIDChooseNameForm($response, $sreg);
 186+ return;
 187+ }
 188+ }
 189+
 190+ if (!isset($user)) {
 191+ $wgOut->errorpage('openiderror', 'openiderrortext');
 192+ } else {
 193+ $wgUser = $user;
 194+ OpenIDFinishLogin($openid);
 195+ }
 196+ }
 197+ }
 198+ }
 199+
 200+ function OpenIDFinishLogin($openid) {
 201+
 202+ global $wgUser, $wgOut;
 203+
 204+ $wgUser->SetupSession();
 205+ $wgUser->SetCookies();
 206+
 207+ # Run any hooks; ignore results
 208+
 209+ wfRunHooks('UserLoginComplete', array(&$wgUser));
 210+
 211+ # Set a cookie for later check-immediate use
 212+
 213+ OpenIDLoginSetCookie($openid);
 214+
 215+ $wgOut->setPageTitle( wfMsg( 'openidsuccess' ) );
 216+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
 217+ $wgOut->setArticleRelated( false );
 218+ $wgOut->addWikiText( wfMsg( 'openidsuccess', $wgUser->getName(), $openid ) );
 219+ $wgOut->returnToMain(false, OpenIDConsumerReturnTo());
 220+ }
 221+
 222+ function OpenIDLoginSetCookie($openid) {
 223+ global $wgCookiePath, $wgCookieDomain, $wgCookieSecure, $wgCookiePrefix;
 224+ global $wgOpenIDCookieExpiration;
 225+
 226+ $exp = time() + $wgOpenIDCookieExpiration;
 227+
 228+ setcookie($wgCookiePrefix.'OpenID', $openid, $exp, $wgCookiePath, $wgCookieDomain, $wgCookieSecure);
 229+ }
 230+
 231+ function OpenIDLoginForm() {
 232+ global $wgOut, $wgUser, $wgOpenIDLoginLogoUrl;
 233+ $sk = $wgUser->getSkin();
 234+ $instructions = wfMsg('openidlogininstructions');
 235+ $ok = wfMsg('login');
 236+ $wgOut->addHTML("<p>{$instructions}</p>" .
 237+ '<form action="' . $sk->makeSpecialUrl('OpenIDLogin') . '" method="POST">' .
 238+ '<input type="text" name="openid_url" size=30 ' .
 239+ ' style="background: url(' . $wgOpenIDLoginLogoUrl . ') ' .
 240+ ' no-repeat; background-color: #fff; background-position: 0 50%; ' .
 241+ ' color: #000; padding-left: 18px;" value="" />' .
 242+ '<input type="submit" value="' . $ok . '" />' .
 243+ '</form>');
 244+ }
 245+
 246+ function OpenIDChooseNameForm($response, $sreg) {
 247+
 248+ global $wgOut, $wgUser;
 249+ $sk = $wgUser->getSkin();
 250+ if (array_key_exists('nickname', $sreg)) {
 251+ $message = wfMsg('openidnotavailable', $sreg['nickname']);
 252+ } else {
 253+ $message = wfMsg('openidnotprovided');
 254+ }
 255+ $instructions = wfMsg('openidchooseinstructions');
 256+ $wgOut->addHTML("<p>{$message}</p>" .
 257+ "<p>{$instructions}</p>" .
 258+ '<form action="' . $sk->makeSpecialUrl('OpenIDFinish/ChooseName') . '" method="POST">');
 259+ $def = false;
 260+
 261+ # These options won't exist if we can't get them.
 262+
 263+ if (array_key_exists('fullname', $sreg) && OpenIDUserNameOK($sreg['fullname'])) {
 264+ $wgOut->addHTML("<input type='radio' name='wpNameChoice' id='wpNameChoiceFull' value='full' " .
 265+ ((!$def) ? "checked = 'checked'" : "") . " />" .
 266+ "<label for='wpNameChoiceFull'>" . wfMsg("openidchoosefull", $sreg['fullname']) . "</label><br />");
 267+ $def = true;
 268+ }
 269+
 270+ $idname = OpenIDToUserName($response->identity_url);
 271+
 272+ if ($idname && OpenIDUserNameOK($idname)) {
 273+ $wgOut->addHTML("<input type='radio' name='wpNameChoice' id='wpNameChoiceUrl' value='url' " .
 274+ ((!$def) ? "checked = 'checked'" : "") . " />" .
 275+ "<label for='wpNameChoiceUrl'>" . wfMsg("openidchooseurl", $idname) . "</label><br />");
 276+ $def = true;
 277+ }
 278+
 279+ # These are always available
 280+
 281+ $wgOut->addHTML("<input type='radio' name='wpNameChoice' id='wpNameChoiceAuto' value='auto' " .
 282+ ((!$def) ? "checked = 'checked'" : "") . " />" .
 283+ "<label for='wpNameChoiceAuto'>" . wfMsg("openidchooseauto", OpenIDAutomaticName($sreg)) . "</label><br />");
 284+
 285+ $def = true;
 286+
 287+ $wgOut->addHTML("<input type='radio' name='wpNameChoice' id='wpNameChoiceManual' value='manual' " .
 288+ " checked='off' />" .
 289+ "<label for='wpNameChoiceManual'>" . wfMsg("openidchoosemanual") . "</label> " .
 290+ "<input type='text' name='wpNameValue' id='wpNameChoice' size='30' /><br />");
 291+
 292+ $ok = wfMsg('login');
 293+ $cancel = wfMsg('cancel');
 294+
 295+ $wgOut->addHTML("<input type='submit' name='wpOK' value='{$ok}' /> <input type='submit' name='wpCancel' value='{$cancel}' />");
 296+ }
 297+
 298+ function OpenIDLogin($openid_url, $finish_page = 'OpenIDFinish') {
 299+
 300+ global $wgUser, $wgTrustRoot, $wgOut;
 301+
 302+ # If it's an interwiki link, expand it
 303+
 304+ $openid_url = OpenIDInterwikiExpand($openid_url);
 305+
 306+ wfDebug("New URL is '$openid_url'\n");
 307+
 308+ # Check if the URL is allowed
 309+
 310+ if (!OpenIDCanLogin($openid_url)) {
 311+ $wgOut->errorpage('openidpermission', 'openidpermissiontext');
 312+ return;
 313+ }
 314+
 315+ $sk = $wgUser->getSkin();
 316+
 317+ if (isset($wgTrustRoot)) {
 318+ $trust_root = $wgTrustRoot;
 319+ } else {
 320+ global $wgArticlePath, $wgServer;
 321+ $root_article = str_replace('$1', '', $wgArticlePath);
 322+ $trust_root = $wgServer . $root_article;
 323+ }
 324+
 325+ $consumer = OpenIDConsumer();
 326+
 327+ if (!$consumer) {
 328+ $wgOut->errorpage('openiderror', 'openiderrortext');
 329+ return;
 330+ }
 331+
 332+ # Make sure the user has a session!
 333+
 334+ global $wgSessionStarted;
 335+
 336+ if (!$wgSessionStarted) {
 337+ $wgUser->SetupSession();
 338+ }
 339+
 340+ $auth_request = $consumer->begin($openid_url);
 341+
 342+ // Handle failure status return values.
 343+ if (!$auth_request) {
 344+ $wgOut->errorpage('openiderror', 'openiderrortext');
 345+ return;
 346+ }
 347+
 348+ # Check the processed URLs, too
 349+
 350+ $endpoint = $auth_request->endpoint;
 351+
 352+ if (isset($endpoint)) {
 353+ # Check if the URL is allowed
 354+
 355+ if (isset($endpoint->identity_url) && !OpenIDCanLogin($endpoint->identity_url)) {
 356+ $wgOut->errorpage('openidpermission', 'openidpermissiontext');
 357+ return;
 358+ }
 359+
 360+ if (isset($endpoint->delegate) && !OpenIDCanLogin($endpoint->delegate)) {
 361+ $wgOut->errorpage('openidpermission', 'openidpermissiontext');
 362+ return;
 363+ }
 364+ }
 365+
 366+ $auth_request->addExtensionArg('sreg', 'optional', 'nickname,email,fullname,language,timezone');
 367+
 368+ $process_url = OpenIDFullUrl($finish_page);
 369+
 370+ $redirect_url = $auth_request->redirectURL($trust_root,
 371+ $process_url);
 372+
 373+ # OK, now go
 374+ $wgOut->redirect($redirect_url);
 375+ }
 376+
 377+ function OpenIDCanLogin($openid_url) {
 378+
 379+ global $wgOpenIDConsumerDenyByDefault, $wgOpenIDConsumerAllow, $wgOpenIDConsumerDeny;
 380+
 381+ if (OpenIDIsLocalUrl($openid_url)) {
 382+ return false;
 383+ }
 384+
 385+ if ($wgOpenIDConsumerDenyByDefault) {
 386+ $canLogin = false;
 387+ foreach ($wgOpenIDConsumerAllow as $allow) {
 388+ if (preg_match($allow, $openid_url)) {
 389+ wfDebug("OpenID: $openid_url matched allow pattern $allow.\n");
 390+ $canLogin = true;
 391+ foreach ($wgOpenIDConsumerDeny as $deny) {
 392+ if (preg_match($deny, $openid_url)) {
 393+ wfDebug("OpenID: $openid_url matched deny pattern $deny.\n");
 394+ $canLogin = false;
 395+ break;
 396+ }
 397+ }
 398+ break;
 399+ }
 400+ }
 401+ } else {
 402+ $canLogin = true;
 403+ foreach ($wgOpenIDConsumerDeny as $deny) {
 404+ if (preg_match($deny, $openid_url)) {
 405+ wfDebug("OpenID: $openid_url matched deny pattern $deny.\n");
 406+ $canLogin = false;
 407+ foreach ($wgOpenIDConsumerAllow as $allow) {
 408+ if (preg_match($allow, $openid_url)) {
 409+ wfDebug("OpenID: $openid_url matched allow pattern $allow.\n");
 410+ $canLogin = true;
 411+ break;
 412+ }
 413+ }
 414+ break;
 415+ }
 416+ }
 417+ }
 418+ return $canLogin;
 419+ }
 420+
 421+ function OpenIDIsLocalUrl($url) {
 422+
 423+ global $wgServer, $wgArticlePath;
 424+
 425+ $pattern = $wgServer . $wgArticlePath;
 426+ $pattern = str_replace('$1', '(.*)', $pattern);
 427+ $pattern = str_replace('?', '\?', $pattern);
 428+
 429+ return preg_match('|^' . $pattern . '$|', $url);
 430+ }
 431+
 432+ function OpenIDFullUrl($title) {
 433+ $nt = Title::makeTitleSafe(NS_SPECIAL, $title);
 434+ if (isset($nt)) {
 435+ return $nt->getFullURL();
 436+ } else {
 437+ return NULL;
 438+ }
 439+ }
 440+
 441+ function OpenIDConsumer() {
 442+ global $wgOpenIDConsumerStoreType, $wgOpenIDConsumerStorePath;
 443+
 444+ $store = getOpenIDStore($wgOpenIDConsumerStoreType,
 445+ 'consumer',
 446+ array('path' => $wgOpenIDConsumerStorePath));
 447+
 448+ return new Auth_OpenID_Consumer($store);
 449+ }
 450+
 451+ # Find the user with the given openid, if any
 452+
 453+ function OpenIDGetUser($openid) {
 454+ global $wgSharedDB, $wgDBprefix;
 455+
 456+ if (isset($wgSharedDB)) {
 457+ $tableName = "`$wgSharedDB`.${wgDBprefix}user_openid";
 458+ } else {
 459+ $tableName = 'user_openid';
 460+ }
 461+
 462+ $dbr =& wfGetDB( DB_SLAVE );
 463+ $id = $dbr->selectField($tableName, 'uoi_user',
 464+ array('uoi_openid' => $openid));
 465+ if ($id) {
 466+ $name = User::whoIs($id);
 467+ return User::newFromName($name);
 468+ } else {
 469+ return NULL;
 470+ }
 471+ }
 472+
 473+ function OpenIDUpdateUser($user, $sreg) {
 474+ global $wgAllowRealName;
 475+
 476+ # FIXME: only update if there's been a change
 477+
 478+ if (array_key_exists('nickname', $sreg)) {
 479+ $user->setOption('nickname', $sreg['nickname']);
 480+ } else {
 481+ $user->setOption('nickname', '');
 482+ }
 483+
 484+ if (array_key_exists('email', $sreg)) {
 485+ $user->setEmail( $sreg['email'] );
 486+ } else {
 487+ $user->setEmail(NULL);
 488+ }
 489+
 490+ if (array_key_exists('fullname', $sreg) && $wgAllowRealName) {
 491+ $user->setRealName($sreg['fullname']);
 492+ } else {
 493+ $user->setRealName(NULL);
 494+ }
 495+
 496+ if (array_key_exists('language', $sreg)) {
 497+ # FIXME: check and make sure the language exists
 498+ $user->setOption('language', $sreg['language']);
 499+ } else {
 500+ $user->setOption('language', NULL);
 501+ }
 502+
 503+ if (array_key_exists('timezone', $sreg)) {
 504+ # FIXME: do something with it.
 505+ # $offset = OpenIDTimezoneToTzoffset($sreg['timezone']);
 506+ # $user->setOption('timecorrection', $offset);
 507+ } else {
 508+ # $user->setOption('timecorrection', NULL);
 509+ }
 510+
 511+ $user->saveSettings();
 512+ }
 513+
 514+ function OpenIDCreateUser($openid, $sreg, $name) {
 515+
 516+ global $wgAuth, $wgAllowRealName;
 517+
 518+ $user = User::newFromName($name);
 519+
 520+ $user->addToDatabase();
 521+
 522+ if (!$user->getId()) {
 523+ wfDebug("OpenID: Error adding new user.\n");
 524+ } else {
 525+
 526+ OpenIDInsertUserUrl($user, $openid);
 527+
 528+ if (array_key_exists('nickname', $sreg)) {
 529+ $user->setOption('nickname', $sreg['nickname']);
 530+ }
 531+ if (array_key_exists('email', $sreg)) {
 532+ $user->setEmail( $sreg['email'] );
 533+ }
 534+ if ($wgAllowRealName && array_key_exists('fullname', $sreg)) {
 535+ $user->setRealName($sreg['fullname']);
 536+ }
 537+ if (array_key_exists('language', $sreg)) {
 538+ # FIXME: check and make sure the language exists
 539+ $user->setOption('language', $sreg['language']);
 540+ }
 541+ if (array_key_exists('timezone', $sreg)) {
 542+ # FIXME: do something with it.
 543+ # $offset = OpenIDTimezoneToTzoffset($sreg['timezone']);
 544+ # $user->setOption('timecorrection', $offset);
 545+ }
 546+ $user->saveSettings();
 547+ return $user;
 548+ }
 549+ }
 550+
 551+ function OpenIDCreateName($openid, $sreg) {
 552+
 553+ if (array_key_exists('nickname', $sreg) && # try nickname
 554+ OpenIDUserNameOK($sreg['nickname']))
 555+ {
 556+ return $sreg['nickname'];
 557+ }
 558+ }
 559+
 560+ function OpenIDToUserName($openid) {
 561+ if (Services_Yadis_identifierScheme($openid) == 'XRI') {
 562+ wfDebug("OpenID: Handling an XRI: $openid\n");
 563+ return OpenIDToUserNameXri($openid);
 564+ } else {
 565+ wfDebug("OpenID: Handling an URL: $openid\n");
 566+ return OpenIDToUserNameUrl($openid);
 567+ }
 568+ }
 569+
 570+ # We try to use an OpenID URL as a legal MediaWiki user name in this order
 571+ # 1. Plain hostname, like http://evanp.myopenid.com/
 572+ # 2. One element in path, like http://profile.typekey.com/EvanProdromou/
 573+ # or http://getopenid.com/evanprodromou
 574+
 575+ function OpenIDToUserNameUrl($openid) {
 576+ static $bad = array('query', 'user', 'password', 'port', 'fragment');
 577+
 578+ $parts = parse_url($openid);
 579+
 580+ # If any of these parts exist, this won't work
 581+
 582+ foreach ($bad as $badpart) {
 583+ if (array_key_exists($badpart, $parts)) {
 584+ return NULL;
 585+ }
 586+ }
 587+
 588+ # We just have host and/or path
 589+
 590+ # If it's just a host...
 591+ if (array_key_exists('host', $parts) &&
 592+ (!array_key_exists('path', $parts) || strcmp($parts['path'], '/') == 0))
 593+ {
 594+ $hostparts = explode('.', $parts['host']);
 595+
 596+ # Try to catch common idiom of nickname.service.tld
 597+
 598+ if ((count($hostparts) > 2) &&
 599+ (strlen($hostparts[count($hostparts) - 2]) > 3) && # try to skip .co.uk, .com.au
 600+ (strcmp($hostparts[0], 'www') != 0))
 601+ {
 602+ return $hostparts[0];
 603+ } else {
 604+ # Do the whole hostname
 605+ return $parts['host'];
 606+ }
 607+ } else {
 608+ if (array_key_exists('path', $parts)) {
 609+ # Strip starting, ending slashes
 610+ $path = preg_replace('@/$@', '', $parts['path']);
 611+ $path = preg_replace('@^/@', '', $path);
 612+ if (strpos($path, '/') === false) {
 613+ return $path;
 614+ }
 615+ }
 616+ }
 617+
 618+ return NULL;
 619+ }
 620+
 621+ function OpenIDToUserNameXri($xri) {
 622+ $base = OpenIDXriBase($xri);
 623+
 624+ if (!$base) {
 625+ return NULL;
 626+ } else {
 627+ # =evan.prodromou
 628+ # or @gratis*evan.prodromou
 629+ $parts = explode('*', substr($base, 1));
 630+ return array_pop($parts);
 631+ }
 632+ }
 633+
 634+ # Is this name OK to use as a user name?
 635+
 636+ function OpenIDUserNameOK($name) {
 637+ global $wgReservedUsernames;
 638+ return (0 == User::idFromName($name) &&
 639+ !in_array( $name, $wgReservedUsernames ));
 640+ }
 641+
 642+ # Get an auto-incremented name
 643+
 644+ function OpenIDFirstAvailable($prefix) {
 645+ for ($i = 2; ; $i++) { # FIXME: this is the DUMB WAY to do this
 646+ $name = "$prefix$i";
 647+ if (OpenIDUserNameOK($name)) {
 648+ return $name;
 649+ }
 650+ }
 651+ }
 652+
 653+ function OpenIDAlreadyLoggedIn() {
 654+
 655+ global $wgUser, $wgOut;
 656+
 657+ $wgOut->setPageTitle( wfMsg( 'openiderror' ) );
 658+ $wgOut->setRobotpolicy( 'noindex,nofollow' );
 659+ $wgOut->setArticleRelated( false );
 660+ $wgOut->addWikiText( wfMsg( 'openidalreadyloggedin', $wgUser->getName() ) );
 661+ $wgOut->returnToMain(false, OpenIDConsumerReturnTo() );
 662+ }
 663+
 664+ function OpenIDGetUserUrl($user) {
 665+ $openid_url = null;
 666+
 667+ if (isset($user) && $user->getId() != 0) {
 668+ global $wgSharedDB, $wgDBprefix;
 669+ if (isset($wgSharedDB)) {
 670+ $tableName = "`${wgSharedDB}`.${wgDBprefix}user_openid";
 671+ } else {
 672+ $tableName = 'user_openid';
 673+ }
 674+
 675+ $dbr =& wfGetDB( DB_SLAVE );
 676+ $res = $dbr->select(array($tableName),
 677+ array('uoi_openid'),
 678+ array('uoi_user' => $user->getId()),
 679+ 'OpenIDGetUserUrl');
 680+
 681+ # This should return 0 or 1 result, since user is unique
 682+ # in the table.
 683+
 684+ while ($res && $row = $dbr->fetchObject($res)) {
 685+ $openid_url = $row->uoi_openid;
 686+ }
 687+ $dbr->freeResult($res);
 688+ }
 689+ return $openid_url;
 690+ }
 691+
 692+ function OpenIDSetUserUrl($user, $url) {
 693+ $other = OpenIDGetUserUrl($user);
 694+ if (isset($other)) {
 695+ OpenIDUpdateUserUrl($user, $url);
 696+ } else {
 697+ OpenIDInsertUserUrl($user, $url);
 698+ }
 699+ }
 700+
 701+ function OpenIDInsertUserUrl($user, $url) {
 702+ global $wgSharedDB, $wgDBname;
 703+ $dbw =& wfGetDB( DB_MASTER );
 704+
 705+ if (isset($wgSharedDB)) {
 706+ # It would be nicer to get the existing dbname
 707+ # and save it, but it's not possible
 708+ $dbw->selectDB($wgSharedDB);
 709+ }
 710+
 711+ $dbw->insert('user_openid', array('uoi_user' => $user->getId(),
 712+ 'uoi_openid' => $url));
 713+
 714+ if (isset($wgSharedDB)) {
 715+ $dbw->selectDB($wgDBname);
 716+ }
 717+ }
 718+
 719+ function OpenIDUpdateUserUrl($user, $url) {
 720+ global $wgSharedDB, $wgDBname;
 721+ $dbw =& wfGetDB( DB_MASTER );
 722+
 723+ if (isset($wgSharedDB)) {
 724+ # It would be nicer to get the existing dbname
 725+ # and save it, but it's not possible
 726+ $dbw->selectDB($wgSharedDB);
 727+ }
 728+
 729+ $dbw->set('user_openid', 'uoi_openid', $url,
 730+ 'uoi_user = ' . $user->getID());
 731+
 732+ if (isset($wgSharedDB)) {
 733+ $dbw->selectDB($wgDBname);
 734+ }
 735+ }
 736+
 737+ function OpenIDInterwikiExpand($openid_url) {
 738+ # try to make it into a title object
 739+ $nt = Title::newFromText($openid_url);
 740+ # If it's got an iw, return that
 741+ if (!is_null($nt) && !is_null($nt->getInterwiki())
 742+ && strlen($nt->getInterwiki()) > 0) {
 743+ return $nt->getFullUrl();
 744+ } else {
 745+ return $openid_url;
 746+ }
 747+ }
 748+
 749+ function OpenIDConsumerSaveValues($response, $sreg) {
 750+ global $wgSessionStarted, $wgUser;
 751+
 752+ if (!$wgSessionStarted) {
 753+ $wgUser->SetupSession();
 754+ }
 755+
 756+ $_SESSION['openid_consumer_response'] = $response;
 757+ $_SESSION['openid_consumer_sreg'] = $sreg;
 758+
 759+ return true;
 760+ }
 761+
 762+ function OpenIDConsumerClearValues() {
 763+ unset($_SESSION['openid_consumer_response']);
 764+ unset($_SESSION['openid_consumer_sreg']);
 765+ return true;
 766+ }
 767+
 768+ function OpenIDConsumerFetchValues() {
 769+ return array($_SESSION['openid_consumer_response'], $_SESSION['openid_consumer_sreg']);
 770+ }
 771+
 772+ function OpenIDConsumerReturnTo() {
 773+ return $_SESSION['openid_consumer_returnto'];
 774+ }
 775+
 776+ function OpenIDConsumerSetReturnTo($returnto) {
 777+ $_SESSION['openid_consumer_returnto'] = $returnto;
 778+ }
 779+
 780+ function OpenIDGetName($response, $sreg, $choice, $nameValue) {
 781+ switch ($choice) {
 782+ case 'full':
 783+ return ((array_key_exists('fullname', $sreg)) ? $sreg['fullname'] : null);
 784+ break;
 785+ case 'url':
 786+ return OpenIDToUserName($response->identity_url);
 787+ break;
 788+ case 'auto':
 789+ return OpenIDAutomaticName($sreg);
 790+ break;
 791+ case 'manual':
 792+ return $nameValue;
 793+ default:
 794+ return null;
 795+ }
 796+ }
 797+
 798+ function OpenIDAutomaticName($sreg) {
 799+ if (array_key_exists('nickname', $sreg) && # try auto-generated from nickname
 800+ strlen($sreg['nickname']) > 0) {
 801+ return OpenIDFirstAvailable($sreg['nickname']);
 802+ } else { # try auto-generated
 803+ return OpenIDFirstAvailable(wfMsg('openidusernameprefix'));
 804+ }
 805+ }
 806+}
 807+
 808+?>
Property changes on: tags/extensions/OpenID/REL0_7_0/Consumer.php
___________________________________________________________________
Added: svn:eol-style
1809 + native
Index: tags/extensions/OpenID/REL0_7_0/openid_table.sql
@@ -0,0 +1,8 @@
 2+CREATE TABLE /*$wgDBprefix*/user_openid (
 3+ uoi_openid varchar(255) NOT NULL,
 4+ uoi_user int(5) unsigned NOT NULL,
 5+
 6+ PRIMARY KEY uoi_openid (uoi_openid),
 7+ UNIQUE INDEX uoi_user (uoi_user)
 8+) TYPE=InnoDB;
 9+
Property changes on: tags/extensions/OpenID/REL0_7_0/openid_table.sql
___________________________________________________________________
Added: svn:eol-style
110 + native
Index: tags/extensions/OpenID/REL0_7_0/MemcStore.php
@@ -0,0 +1,258 @@
 2+<?php
 3+/**
 4+ * MemcStore.php -- An OpenID store using MediaWiki's $wgMemc object
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+# Nonces expire in 6 hours
 27+
 28+define('MEMCSTORE_NONCE_EXPIRY', 3600 * 6);
 29+define('MEMCSTORE_RS', "\x1E");
 30+define('MEMCSTORE_US', "\x1F");
 31+
 32+if (defined('MEDIAWIKI')) {
 33+
 34+ require_once('Auth/OpenID/Interface.php');
 35+ require_once('Auth/OpenID/HMACSHA1.php');
 36+ require_once('Auth/OpenID/CryptUtil.php');
 37+
 38+ class OpenID_MemcStore extends Auth_OpenID_OpenIDStore {
 39+
 40+ var $prefix = '';
 41+
 42+ function OpenID_MemcStore($prefix_part = null)
 43+ {
 44+ global $wgMemc, $wgDBname;
 45+ if (isset($prefix_part)) {
 46+ $this->prefix = $prefix_part . ':';
 47+ }
 48+
 49+ $auth_key =
 50+ Auth_OpenID_CryptUtil::randomString($this->AUTH_KEY_LEN);
 51+
 52+ $k = $this->_authKeyKey();
 53+ $res = $wgMemc->add($k, $auth_key);
 54+ }
 55+
 56+ function storeAssociation($server_url, $association)
 57+ {
 58+ global $wgMemc;
 59+ $h = $association->handle;
 60+ $k = $this->_associationKey($server_url, $h);
 61+ $assoc_s = $association->serialize();
 62+ $res = $wgMemc->set($k, $assoc_s);
 63+ $handles = $this->_getHandles($server_url);
 64+ $handles[$h] = $association->issued + $association->lifetime;
 65+ $this->_setHandles($server_url, $handles);
 66+ return true;
 67+ }
 68+
 69+ function getAssociation($server_url, $handle = null)
 70+ {
 71+ if (isset($handle)) {
 72+ return $this->_getKnownAssociation($server_url, $handle);
 73+ } else { # $handle is null, get the assoc with greatest time left
 74+ return $this->_getBestAssociation($server_url);
 75+ }
 76+ }
 77+
 78+ function removeAssociation($server_url, $handle)
 79+ {
 80+ global $wgMemc;
 81+
 82+ # First, delete it from the list of handles
 83+ $handles = $this->_getHandles($server_url);
 84+ if (array_key_exists($handle, $handles)) {
 85+ unset($handles[$handle]);
 86+ $this->_setHandles($server_url, $handles);
 87+ }
 88+
 89+ # Now, delete the association record
 90+ $k = $this->_associationKey($server_url, $handle);
 91+ $v = $wgMemc->get($k);
 92+
 93+ if ($v === false || strlen($v) == 0) {
 94+ return false;
 95+ } else {
 96+ $res = $wgMemc->delete($k);
 97+ return true;
 98+ }
 99+ }
 100+
 101+ function storeNonce($nonce)
 102+ {
 103+ $nonces = $this->_getNonces();
 104+ $nonces[$nonce] = time() + MEMCSTORE_NONCE_EXPIRY;
 105+ $this->_setNonces($nonces);
 106+ }
 107+
 108+ function useNonce($nonce)
 109+ {
 110+ $nonces = $this->_getNonces();
 111+ if (!array_key_exists($nonce, $nonces)) {
 112+ return false;
 113+ } else {
 114+ unset($nonces[$nonce]);
 115+ $this->_setNonces($nonces);
 116+ return true;
 117+ }
 118+ }
 119+
 120+ function getAuthKey()
 121+ {
 122+ global $wgMemc;
 123+ $k = $this->_authKeyKey();
 124+ return $wgMemc->get($k);
 125+ }
 126+
 127+ function isDumb()
 128+ {
 129+ return false;
 130+ }
 131+
 132+ function _getNonces() {
 133+ global $wgMemc;
 134+ $nonces = array();
 135+ $k = $this->_nonceKey();
 136+ $v = $wgMemc->get($k);
 137+ if ($v !== false && strlen($v) > 0) {
 138+ $records = explode(MEMCSTORE_RS, $v);
 139+ $now = time();
 140+ foreach ($records as $record) {
 141+ list($nonce, $expiry) = explode(MEMCSTORE_US, $record);
 142+ if ($expiry > $now) {
 143+ $nonces[$nonce] = $expiry;
 144+ }
 145+ }
 146+ }
 147+ return $nonces;
 148+ }
 149+
 150+ function _setNonces($nonces) {
 151+ global $wgMemc;
 152+ $records = array();
 153+ foreach ($nonces as $nonce => $expiry) {
 154+ $records[] = implode(MEMCSTORE_US, array($nonce, $expiry));
 155+ }
 156+ $v = implode(MEMCSTORE_RS, $records);
 157+ $k = $this->_nonceKey();
 158+ $wgMemc->set($k, $v);
 159+ }
 160+
 161+ function _getKnownAssociation($server_url, $handle) {
 162+ global $wgMemc;
 163+ $k = $this->_associationKey($server_url, $handle);
 164+ $v = $wgMemc->get($k);
 165+ if ($v !== false && strlen($v) > 0) {
 166+ # FIXME: why is this nl getting lost?
 167+ $v .= "\n";
 168+ $assoc =
 169+ Auth_OpenID_Association::deserialize('Auth_OpenID_Association',
 170+ $v);
 171+ if ($assoc->getExpiresIn() > 0) {
 172+ return $assoc;
 173+ } else {
 174+ return null;
 175+ }
 176+ }
 177+ }
 178+
 179+ function _getBestAssociation($server_url) {
 180+ $handles = $this->_getHandles($server_url);
 181+ $maxissue = -1;
 182+ $best = null;
 183+ foreach ($handles as $handle => $expiry) {
 184+ $assoc = $this->_getKnownAssociation($server_url, $handle);
 185+ if ($assoc->issued > $maxissue) {
 186+ $best = $assoc;
 187+ $maxissue = $assoc->issued;
 188+ }
 189+ }
 190+ return $best;
 191+ }
 192+
 193+ function _associationKey($url, $handle) {
 194+ global $wgDBname;
 195+ $uhash = sprintf("%x", crc32($url));
 196+ $hhash = sprintf("%x", crc32($handle));
 197+ return "$wgDBname:openid:memcstore:" . $this->prefix . "assoc:$uhash:$hhash";
 198+ }
 199+
 200+ function _associationNullKey($url) {
 201+ global $wgDBname;
 202+ $uhash = sprintf("%x", crc32($url));
 203+ return "$wgDBname:openid:memcstore:" . $this->prefix . "assoc:$uhash";
 204+ }
 205+
 206+ function _nonceKey() {
 207+ global $wgDBname;
 208+ return "$wgDBname:openid:memcstore:" . $this->prefix . "nonces";
 209+ }
 210+
 211+ function _authKeyKey() {
 212+ global $wgDBname;
 213+ return "$wgDBname:openid:memcstore:" . $this->prefix . "authkey";
 214+ }
 215+
 216+ function _getHandles($server_url) {
 217+ global $wgMemc;
 218+ $nk = $this->_associationNullKey($server_url);
 219+ $v = $wgMemc->get($nk);
 220+ if ($v === false || strlen($v) == 0) {
 221+ # XXX
 222+ return array();
 223+ } else {
 224+ $handles = array();
 225+ $records = explode(MEMCSTORE_RS, $v);
 226+ $now = time();
 227+ foreach ($records as $record) {
 228+ list($handle, $expiry) = explode(MEMCSTORE_US, $record);
 229+ if ($expiry > $now) {
 230+ $handles[$handle] = $expiry;
 231+ } else {
 232+ $this->_expireHandle($server_url, $handle);
 233+ }
 234+ }
 235+ return $handles;
 236+ }
 237+ }
 238+
 239+ function _setHandles($server_url, $handles) {
 240+ global $wgMemc;
 241+ $records = array();
 242+ foreach ($handles as $handle => $expiry) {
 243+ $records[] = implode(MEMCSTORE_US, array($handle, $expiry));
 244+ }
 245+ $nv = implode(MEMCSTORE_RS, $records);
 246+ $nk = $this->_associationNullKey($server_url);
 247+ $wgMemc->set($nk, $nv);
 248+ }
 249+
 250+ # Garbage-collects expired handles
 251+
 252+ function _expireHandle($server_url, $handles) {
 253+ global $wgMemc;
 254+ $k = $this->_associationKey($server_url, $handle);
 255+ $wgMemc->delete($k);
 256+ }
 257+ }
 258+}
 259+
Property changes on: tags/extensions/OpenID/REL0_7_0/MemcStore.php
___________________________________________________________________
Added: svn:eol-style
1260 + native
Index: tags/extensions/OpenID/REL0_7_0/OpenID.php
@@ -0,0 +1,277 @@
 2+<?php
 3+/**
 4+ * OpenID.php -- Make MediaWiki and OpenID consumer and server
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ */
 25+
 26+if (defined('MEDIAWIKI')) {
 27+
 28+ require_once("$IP/extensions/OpenID/Consumer.php");
 29+ require_once("$IP/extensions/OpenID/Convert.php");
 30+ require_once("$IP/extensions/OpenID/Server.php");
 31+ require_once("$IP/extensions/OpenID/MemcStore.php");
 32+
 33+ require_once("Auth/OpenID/FileStore.php");
 34+
 35+ require_once("SpecialPage.php");
 36+
 37+ define('MEDIAWIKI_OPENID_VERSION', '0.7.0');
 38+
 39+ $wgExtensionFunctions[] = 'setupOpenID';
 40+ $wgExtensionCredits['other'][] = array('name' => 'OpenID',
 41+ 'version' => MEDIAWIKI_OPENID_VERSION,
 42+ 'author' => 'Evan Prodromou',
 43+ 'url' => 'http://www.mediawiki.org/wiki/Extension:OpenID',
 44+ 'description' => 'lets users login to the wiki with an [http://openid.net/ OpenID] ' .
 45+ 'and login to other OpenID-aware Web sites with their wiki user account');
 46+
 47+ # Whether to hide the "Login with OpenID link" link; set to true if you already have this link in your skin.
 48+
 49+ $wgHideOpenIDLoginLink = false;
 50+
 51+ # Location of the OpenID login logo. You can copy this to your server if you want.
 52+
 53+ $wgOpenIDLoginLogoUrl = 'http://www.openid.net/login-bg.gif';
 54+
 55+ # Whether to show the OpenID identity URL on a user's home page. Possible values are 'always', 'never', or 'user'
 56+ # 'user' lets the user decide.
 57+
 58+ $wgOpenIDShowUrlOnUserPage = 'user';
 59+
 60+ function setupOpenID() {
 61+ global $wgMessageCache, $wgOut, $wgRequest, $wgHooks;
 62+
 63+ $wgMessageCache->addMessages(array('openidlogin' => 'Login with OpenID',
 64+ 'openidfinish' => 'Finish OpenID login',
 65+ 'openidserver' => 'OpenID server',
 66+ 'openidconvert' => 'OpenID converter',
 67+ 'openidlogininstructions' => 'Enter your OpenID identifier to log in:',
 68+ 'openiderror' => 'Verification error',
 69+ 'openiderrortext' => 'An error occured during verification of the OpenID URL.',
 70+ 'openidconfigerror' => 'OpenID Configuration Error',
 71+ 'openidconfigerrortext' => 'The OpenID storage configuration for this wiki is invalid. Please consult this site\'s administrator.',
 72+ 'openidpermission' => 'OpenID permissions error',
 73+ 'openidpermissiontext' => 'The OpenID you provided is not allowed to login to this server.',
 74+ 'openidcancel' => 'Verification cancelled',
 75+ 'openidcanceltext' => 'Verification of the OpenID URL was cancelled.',
 76+ 'openidfailure' => 'Verification failed',
 77+ 'openidfailuretext' => 'Verification of the OpenID URL failed.',
 78+ 'openidsuccess' => 'Verification succeeded',
 79+ 'openidsuccesstext' => 'Verification of the OpenID URL succeeded.',
 80+ 'openidusernameprefix' => 'OpenIDUser',
 81+ 'openidserverlogininstructions' => 'Enter your password below to log in to $3 as user $2 (user page $1).',
 82+ 'openidtrustinstructions' => 'Check if you want to share data with $1.',
 83+ 'openidallowtrust' => 'Allow $1 to trust this user account.',
 84+ 'openidnopolicy' => 'Site has not specified a privacy policy.',
 85+ 'openidpolicy' => 'Check the <a target="_new" href="$1">privacy policy</a> for more information.',
 86+ 'openidoptional' => 'Optional',
 87+ 'openidrequired' => 'Required',
 88+ 'openidnickname' => 'Nickname',
 89+ 'openidfullname' => 'Fullname',
 90+ 'openidemail' => 'Email address',
 91+ 'openidlanguage' => 'Language',
 92+ 'openidnotavailable' => 'Your preferred nickname ($1) is already in use by another user.',
 93+ 'openidnotprovided' => 'Your OpenID server did not provide a nickname (either because it can\'t, or because you told it not to).',
 94+ 'openidchooseinstructions' => 'All users need a nickname; you can choose one from the options below.',
 95+ 'openidchoosefull' => 'Your full name ($1)',
 96+ 'openidchooseurl' => 'A name picked from your OpenID ($1)',
 97+ 'openidchooseauto' => 'An auto-generated name ($1)',
 98+ 'openidchoosemanual' => 'A name of your choice: ',
 99+ 'openidconvertinstructions' => 'This form lets you change your user account to use an OpenID URL.',
 100+ 'openidconvertsuccess' => 'Successfully converted to OpenID',
 101+ 'openidconvertsuccesstext' => 'You have successfully converted your OpenID to $1.',
 102+ 'openidconvertyourstext' => 'That is already your OpenID.',
 103+ 'openidconvertothertext' => 'That is someone else\'s OpenID.',
 104+ 'openidalreadyloggedin' => '<strong>User $1, you are already logged in!</strong>',
 105+ 'tog-hideopenid' => 'Hide your <a href="http://openid.net/">OpenID</a> on your user page, if you log in with OpenID.',
 106+ ));
 107+
 108+ SpecialPage::AddPage(new UnlistedSpecialPage('OpenIDLogin'));
 109+ SpecialPage::AddPage(new UnlistedSpecialPage('OpenIDFinish'));
 110+ SpecialPage::AddPage(new UnlistedSpecialPage('OpenIDServer'));
 111+ SpecialPage::AddPage(new UnlistedSpecialPage('OpenIDConvert'));
 112+ SpecialPage::AddPage(new UnlistedSpecialPage('OpenIDXRDS'));
 113+
 114+ $wgHooks['PersonalUrls'][] = 'OpenIDPersonalUrls';
 115+ $wgHooks['UserToggles'][] = 'OpenIDUserToggles';
 116+
 117+ $wgOut->addHeadItem('openidloginstyle', OpenIDLoginStyle());
 118+
 119+ $action = $wgRequest->getText('action', 'view');
 120+
 121+ if ($action == 'view') {
 122+
 123+ $title = $wgRequest->getText('title');
 124+
 125+ if (!isset($title) || strlen($title) == 0) {
 126+ # If there's no title, and Cache404 is in use, check using its stuff
 127+ if (defined('CACHE404_VERSION')) {
 128+ if ($_SERVER['REDIRECT_STATUS'] == 404) {
 129+ $url = getRedirectUrl($_SERVER);
 130+ if (isset($url)) {
 131+ $title = cacheUrlToTitle($url);
 132+ }
 133+ }
 134+ } else {
 135+ $title = wfMsg('mainpage');
 136+ }
 137+ }
 138+
 139+ $nt = Title::newFromText($title);
 140+
 141+ // If the page being viewed is a user page,
 142+ // generate the openid.server META tag and output
 143+ // the X-XRDS-Location. See the OpenIDXRDS
 144+ // special page for the XRDS output / generation
 145+ // logic.
 146+ if ($nt &&
 147+ ($nt->getNamespace() == NS_USER) &&
 148+ strpos($nt->getText(), '/') === false)
 149+ {
 150+ $user = User::newFromName($nt->getText());
 151+ if ($user && $user->getID() != 0) {
 152+ $openid = OpenIdGetUserUrl($user);
 153+ if (isset($openid) && strlen($openid) != 0) {
 154+ global $wgOpenIDShowUrlOnUserPage;
 155+
 156+ if ($wgOpenIDShowUrlOnUserPage == 'always' ||
 157+ ($wgOpenIDShowUrlOnUserPage == 'user' && !$user->getOption('hideopenid')))
 158+ {
 159+ global $wgOpenIDLoginLogoUrl;
 160+
 161+ $url = OpenIDToUrl($openid);
 162+ $disp = htmlspecialchars($openid);
 163+ $wgOut->setSubtitle("<span class='subpages'>" .
 164+ "<img src='$wgOpenIDLoginLogoUrl' alt='OpenID' />" .
 165+ "<a href='$url'>$disp</a>" .
 166+ "</span>");
 167+ }
 168+ } else {
 169+ $wgOut->addLink(array('rel' => 'openid.server',
 170+ 'href' => OpenIDServerUrl()));
 171+ $rt = Title::makeTitle(NS_SPECIAL, 'OpenIDXRDS/'.$user->getName());
 172+ $wgOut->addMeta('http:X-XRDS-Location', $rt->getFullURL());
 173+ header('X-XRDS-Location', $rt->getFullURL());
 174+ }
 175+ }
 176+ }
 177+ }
 178+
 179+ // Verify the config file settings. FIXME: How to
 180+ // report error?
 181+ global $wgOpenIDServerStorePath, $wgOpenIDServerStoreType,
 182+ $wgOpenIDConsumerStorePath, $wgOpenIDConsumerStoreType;
 183+
 184+ if ($wgOpenIDConsumerStoreType == 'file') {
 185+ assert($wgOpenIDConsumerStorePath != false);
 186+ }
 187+
 188+ if ($wgOpenIDServerStoreType == 'file') {
 189+ assert($wgOpenIDServerStorePath != false);
 190+ }
 191+ }
 192+
 193+ function getOpenIDStore($storeType, $prefix, $options) {
 194+ global $wgOut;
 195+
 196+ switch ($storeType) {
 197+ case 'memcached':
 198+ case 'memc':
 199+ return new OpenID_MemcStore($prefix);
 200+
 201+ case 'file':
 202+ # Auto-create path if it doesn't exist
 203+ if (!is_dir($options['path'])) {
 204+ if (!mkdir($options['path'], 0770, true)) {
 205+ $wgOut->errorPage('openidconfigerror', 'openidconfigerrortext');
 206+ return NULL;
 207+ }
 208+ }
 209+ return new Auth_OpenID_FileStore($options['path']);
 210+
 211+ default:
 212+ $wgOut->errorPage('openidconfigerror', 'openidconfigerrortext');
 213+ }
 214+ }
 215+
 216+ function OpenIDXriBase($xri) {
 217+ if (substr($xri, 0, 6) == 'xri://') {
 218+ return substr($xri, 6);
 219+ } else {
 220+ return $xri;
 221+ }
 222+ }
 223+
 224+ function OpenIDXriToUrl($xri) {
 225+ return 'http://xri.net/' . OpenIDXriBase($xri);
 226+ }
 227+
 228+ function OpenIDToUrl($openid) {
 229+ /* ID is either an URL already or an i-name */
 230+ if (Services_Yadis_identifierScheme($openid) == 'XRI') {
 231+ return OpenIDXriToUrl($openid);
 232+ } else {
 233+ return $openid;
 234+ }
 235+ }
 236+
 237+ function OpenIDPersonalUrls(&$personal_urls, &$title) {
 238+ global $wgHideOpenIDLoginLink, $wgUser, $wgLang;
 239+
 240+ if (!$wgHideOpenIDLoginLink && $wgUser->getID() == 0) {
 241+ $sk = $wgUser->getSkin();
 242+ $returnto = ($title->getPrefixedUrl() == $wgLang->specialPage( 'Userlogout' )) ?
 243+ '' : ('returnto=' . $title->getPrefixedURL());
 244+
 245+ $personal_urls['openidlogin'] = array(
 246+ 'text' => wfMsg('openidlogin'),
 247+ 'href' => $sk->makeSpecialUrl( 'OpenIDLogin', $returnto ),
 248+ 'active' => $title->isSpecial( 'OpenIDLogin' )
 249+ );
 250+ }
 251+
 252+ return true;
 253+ }
 254+
 255+ function OpenIDUserToggles(&$extraToggles) {
 256+ global $wgOpenIDShowUrlOnUserPage;
 257+
 258+ if ($wgOpenIDShowUrlOnUserPage == 'user') {
 259+ $extraToggles[] = 'hideopenid';
 260+ }
 261+
 262+ return true;
 263+ }
 264+
 265+ function OpenIDLoginStyle() {
 266+ global $wgOpenIDLoginLogoUrl;
 267+ return <<<EOS
 268+<style type='text/css'>
 269+li#pt-openidlogin {
 270+ background: url($wgOpenIDLoginLogoUrl) top left no-repeat;
 271+ padding-left: 20px;
 272+ text-transform: none;
 273+}
 274+</style>
 275+EOS;
 276+ }
 277+}
 278+
Property changes on: tags/extensions/OpenID/REL0_7_0/OpenID.php
___________________________________________________________________
Added: svn:eol-style
1279 + native
Index: tags/extensions/OpenID/REL0_7_0/testMemcStore.php
@@ -0,0 +1,312 @@
 2+<?php
 3+/**
 4+ * testMemcStore.php -- Command-line test tool for MemcStore
 5+ * Copyright 2006,2007 Internet Brands (http://www.internetbrands.com/)
 6+ * By Evan Prodromou <evan@wikitravel.org>
 7+ *
 8+ * This program is free software; you can redistribute it and/or modify
 9+ * it under the terms of the GNU General Public License as published by
 10+ * the Free Software Foundation; either version 2 of the License, or
 11+ * (at your option) any later version.
 12+ *
 13+ * This program is distributed in the hope that it will be useful,
 14+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
 15+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 16+ * GNU General Public License for more details.
 17+ *
 18+ * You should have received a copy of the GNU General Public License
 19+ * along with this program; if not, write to the Free Software
 20+ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 21+ *
 22+ * @author Evan Prodromou <evan@wikitravel.org>
 23+ * @addtogroup Extensions
 24+ *
 25+ * Based in part on Tests/Auth/OpenID/StoreTest.php from PHP-openid package
 26+ * From JanRain, Inc.
 27+ *
 28+ * @package OpenID
 29+ * @author JanRain, Inc. <openid@janrain.com>
 30+ * @copyright 2005 Janrain, Inc.
 31+ * @license http://www.gnu.org/copyleft/lesser.html LGPL
 32+ */
 33+
 34+/**
 35+ * To use this test file, you must have PHPUnit (the old one for
 36+ * PHP4!) installed. Copy this file to the "maintenance" subdirectory
 37+ * of your MediaWiki source directory and run it from the command line.
 38+ *
 39+ */
 40+
 41+require_once('commandLine.inc');
 42+ini_set( "include_path", "/usr/share/php:" . ini_get("include_path"));
 43+
 44+require_once("$IP/extensions/OpenID/MemcStore.php");
 45+require_once('Auth/OpenID/Association.php');
 46+require_once('Auth/OpenID/CryptUtil.php');
 47+require_once('PHPUnit.php');
 48+
 49+class Tests_OpenID_MemcStore extends PHPUnit_TestCase {
 50+
 51+ function Tests_OpenID_MemcStore($name) {
 52+ $this->PHPUnit_TestCase($name);
 53+ }
 54+
 55+ /**
 56+ * Prepares for the SQL store tests.
 57+ */
 58+ function setUp()
 59+ {
 60+ $this->letters = Auth_OpenID_letters;
 61+ $this->digits = Auth_OpenID_digits;
 62+ $this->punct = Auth_OpenID_punct;
 63+ $this->allowed_nonce = $this->letters . $this->digits;
 64+ $this->allowed_handle = $this->letters . $this->digits . $this->punct;
 65+ }
 66+
 67+ /**
 68+ * Generates a nonce value.
 69+ */
 70+ function generateNonce()
 71+ {
 72+ return Auth_OpenID_CryptUtil::randomString(8, $this->allowed_nonce);
 73+ }
 74+
 75+ /**
 76+ * Generates an association with the specified parameters.
 77+ */
 78+ function genAssoc($now, $issued = 0, $lifetime = 600)
 79+ {
 80+ $sec = Auth_OpenID_CryptUtil::randomString(20);
 81+ $hdl = Auth_OpenID_CryptUtil::randomString(128, $this->allowed_handle);
 82+ return new Auth_OpenID_Association($hdl, $sec, $now + $issued,
 83+ $lifetime, 'HMAC-SHA1');
 84+ }
 85+
 86+ /**
 87+ * @access private
 88+ */
 89+ function _checkRetrieve(&$store, $url, $handle, $expected, $name = null)
 90+ {
 91+ $retrieved_assoc = $store->getAssociation($url, $handle);
 92+ if (($expected === null) || ($store->isDumb())) {
 93+ $this->assertNull($retrieved_assoc, "Retrieved association " .
 94+ "was non-null");
 95+ } else {
 96+ if ($retrieved_assoc === null) {
 97+ $this->fail("$name: Got null when expecting " .
 98+ $expected->serialize());
 99+ } else {
 100+ $this->assertEquals($expected->serialize(),
 101+ $retrieved_assoc->serialize(), $name);
 102+ }
 103+ }
 104+ }
 105+
 106+ function _checkRemove(&$store, $url, $handle, $expected, $name = null)
 107+ {
 108+ $present = $store->removeAssociation($url, $handle);
 109+ $expectedPresent = (!$store->isDumb() && $expected);
 110+ $this->assertTrue((!$expectedPresent && !$present) ||
 111+ ($expectedPresent && $present),
 112+ $name);
 113+ }
 114+
 115+ /**
 116+ * Make sure a given store has a minimum of API compliance. Call
 117+ * this function with an empty store.
 118+ *
 119+ * Raises AssertionError if the store does not work as expected.
 120+ *
 121+ * OpenIDStore -> NoneType
 122+ */
 123+ function _testStore($store)
 124+ {
 125+
 126+ // Association functions
 127+ $now = time();
 128+
 129+ $server_url = 'http://www.myopenid.com/openid';
 130+
 131+ $assoc = $this->genAssoc($now);
 132+
 133+ $this->_checkRetrieve($store, $server_url, null, null,
 134+ 'Make sure that a missing association returns no result');
 135+
 136+ $store->storeAssociation($server_url, $assoc);
 137+ $this->_checkRetrieve($store, $server_url, null, $assoc,
 138+ 'Check that after storage, getting returns the same result');
 139+
 140+ $this->_checkRetrieve($store, $server_url, null, $assoc,
 141+ 'more than once');
 142+
 143+ $store->storeAssociation($server_url, $assoc);
 144+ $this->_checkRetrieve($store, $server_url, null, $assoc,
 145+ 'Storing more than once has no ill effect');
 146+
 147+ // Removing an association that does not exist returns not present
 148+ $this->_checkRemove($store, $server_url, $assoc->handle . 'x', false,
 149+ "Remove nonexistent association (1)");
 150+
 151+ // Removing an association that does not exist returns not present
 152+ $this->_checkRemove($store, $server_url . 'x', $assoc->handle, false,
 153+ "Remove nonexistent association (2)");
 154+
 155+ // Removing an association that is present returns present
 156+ $this->_checkRemove($store, $server_url, $assoc->handle, true,
 157+ "Remove existent association");
 158+
 159+ // but not present on subsequent calls
 160+ $this->_checkRemove($store, $server_url, $assoc->handle, false,
 161+ "Remove nonexistent association after removal");
 162+
 163+ // Put assoc back in the store
 164+ $store->storeAssociation($server_url, $assoc);
 165+
 166+ // More recent and expires after assoc
 167+ $assoc2 = $this->genAssoc($now, $issued = 1);
 168+ $store->storeAssociation($server_url, $assoc2);
 169+
 170+ $this->_checkRetrieve($store, $server_url, null, $assoc2,
 171+ 'After storing an association with a different handle, but the same $server_url, the handle with the later expiration isreturned.');
 172+
 173+ $this->_checkRetrieve($store, $server_url, $assoc->handle, $assoc,
 174+ 'We can still retrieve the older association');
 175+
 176+ $this->_checkRetrieve($store, $server_url, $assoc2->handle, $assoc2,
 177+ 'Plus we can retrieve the association with the later expiration explicitly');
 178+
 179+ $assoc3 = $this->genAssoc($now, $issued = 2, $lifetime = 100);
 180+ $store->storeAssociation($server_url, $assoc3);
 181+
 182+ // More recent issued time, so assoc3 is expected.
 183+ $this->_checkRetrieve($store, $server_url, null, $assoc3, "(1)");
 184+
 185+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
 186+ $assoc, "(2)");
 187+
 188+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
 189+ $assoc2, "(3)");
 190+
 191+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
 192+ $assoc3, "(4)");
 193+
 194+ $this->_checkRemove($store, $server_url, $assoc2->handle, true, "(5)");
 195+
 196+ $this->_checkRetrieve($store, $server_url, null, $assoc3, "(6)");
 197+
 198+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
 199+ $assoc, "(7)");
 200+
 201+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
 202+ null, "(8)");
 203+
 204+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
 205+ $assoc3, "(9)");
 206+
 207+ $this->_checkRemove($store, $server_url, $assoc2->handle,
 208+ false, "(10)");
 209+
 210+ $this->_checkRemove($store, $server_url, $assoc3->handle,
 211+ true, "(11)");
 212+
 213+ $this->_checkRetrieve($store, $server_url, null, $assoc, "(12)");
 214+
 215+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
 216+ $assoc, "(13)");
 217+
 218+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
 219+ null, "(14)");
 220+
 221+ $this->_checkRetrieve($store, $server_url, $assoc3->handle,
 222+ null, "(15)");
 223+
 224+ $this->_checkRemove($store, $server_url, $assoc2->handle,
 225+ false, "(16)");
 226+
 227+ $this->_checkRemove($store, $server_url, $assoc->handle,
 228+ true, "(17)");
 229+
 230+ $this->_checkRemove($store, $server_url, $assoc3->handle,
 231+ false, "(18)");
 232+
 233+ $this->_checkRetrieve($store, $server_url, null, null, "(19)");
 234+
 235+ $this->_checkRetrieve($store, $server_url, $assoc->handle,
 236+ null, "(20)");
 237+
 238+ $this->_checkRetrieve($store, $server_url, $assoc2->handle,
 239+ null, "(21)");
 240+
 241+ $this->_checkRetrieve($store, $server_url,$assoc3->handle,
 242+ null, "(22)");
 243+
 244+ $this->_checkRemove($store, $server_url, $assoc2->handle,
 245+ false, "(23)");
 246+
 247+ $this->_checkRemove($store, $server_url, $assoc->handle,
 248+ false, "(24)");
 249+
 250+ $this->_checkRemove($store, $server_url, $assoc3->handle,
 251+ false, "(25)");
 252+ }
 253+
 254+ function _checkUseNonce(&$store, $nonce, $expected, $msg=null)
 255+ {
 256+ $actual = $store->useNonce($nonce);
 257+ $expected = $store->isDumb() || $expected;
 258+ $this->assertTrue(($actual && $expected) || (!$actual && !$expected),
 259+ "_checkUseNonce failed: $msg");
 260+ }
 261+
 262+ function _testNonce(&$store)
 263+ {
 264+ // Nonce functions
 265+
 266+ // Random nonce (not in store)
 267+ $nonce1 = $this->generateNonce();
 268+
 269+ // A nonce is not present by default
 270+ $this->_checkUseNonce($store, $nonce1, false, 1);
 271+
 272+ // Storing once causes useNonce to return true the first, and
 273+ // only the first, time it is called after the $store->
 274+ $store->storeNonce($nonce1);
 275+ $this->_checkUseNonce($store, $nonce1, true, 2);
 276+ $this->_checkUseNonce($store, $nonce1, false, 3);
 277+ $this->_checkUseNonce($store, $nonce1, false, 4);
 278+
 279+ // Storing twice has the same effect as storing once.
 280+ $store->storeNonce($nonce1);
 281+ $store->storeNonce($nonce1);
 282+ $this->_checkUseNonce($store, $nonce1, true, 5);
 283+ $this->_checkUseNonce($store, $nonce1, false, 6);
 284+ $this->_checkUseNonce($store, $nonce1, false, 7);
 285+
 286+ // Auth key functions
 287+
 288+ // There is no key to start with, so generate a new key and
 289+ // return it.
 290+ $key = $store->getAuthKey();
 291+
 292+ // The second time around should return the same as last time.
 293+ $key2 = $store->getAuthKey();
 294+ $this->assertEquals($key, $key2, "Auth keys differ");
 295+ $this->assertEquals(strlen($key), $store->AUTH_KEY_LEN,
 296+ "Key length not equals AUTH_KEY_LEN");
 297+ }
 298+
 299+ function testMemcStore() {
 300+ # Unique prefix for this test
 301+ $prefix = sprintf("test-%x", time());
 302+ $store = new OpenID_MemcStore($prefix);
 303+ $this->_testStore($store);
 304+ $this->_testNonce($store);
 305+ }
 306+}
 307+
 308+$suite = new PHPUnit_TestSuite();
 309+$suite->addTest(new Tests_OpenID_MemcStore('testMemcStore'));
 310+
 311+$result = PHPUnit::run($suite);
 312+print $result->toString();
 313+
Property changes on: tags/extensions/OpenID/REL0_7_0/testMemcStore.php
___________________________________________________________________
Added: svn:eol-style
1314 + native
Property changes on: tags/extensions/OpenID/REL0_7_0
___________________________________________________________________
Added: svn:ignore
2315 + _darcs
testopenid.sh

Status & tagging log