CSteipp has submitted this change and it was merged.

Change subject: Initial WMF extension commit
......................................................................


Initial WMF extension commit

Moved from https://github.com/Stype/OAuthAuthentication.git

Change-Id: I8a51690828ab09c7e49fa53a8c46497c861f58e3
---
A LICENSE
A OAuthAuthentication.notranslate-alias.php
A OAuthAuthentication.php
A README.md
A TODO.txt
A handlers/AuthenticationHandler.php
A handlers/OAuth1Handler.php
A i18n/en.json
A i18n/qqq.json
A libs/mwoauth-php/MWOAuthClient.php
A libs/mwoauth-php/OAuth.php
A specials/SpecialOAuthLogin.php
A store/PhpSessionStore.php
A store/SessionStore.php
A store/oauthauth.sql
A tests/OAuthAuthConfigTest.php
A tests/OAuthAuthDBTest.php
A tests/OAuthAuthExternalUserTest.php
A tests/OAuthAuthHooksTest.php
A utils/Config.php
A utils/Exception.php
A utils/Hooks.php
A utils/OAuthExternalUser.php
A utils/Policy.php
24 files changed, 2,617 insertions(+), 0 deletions(-)

Approvals:
  CSteipp: Verified; Looks good to me, approved



diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d7f1051
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,339 @@
+GNU GENERAL PUBLIC LICENSE
+                       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc., <http://fsf.org/>
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+                            Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Lesser General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+                    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+                            NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+                     END OF TERMS AND CONDITIONS
+
+            How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    {description}
+    Copyright (C) {year}  {fullname}
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License along
+    with this program; if not, write to the Free Software Foundation, Inc.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  {signature of Ty Coon}, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Lesser General
+Public License instead of this License.
diff --git a/OAuthAuthentication.notranslate-alias.php 
b/OAuthAuthentication.notranslate-alias.php
new file mode 100644
index 0000000..03e9113
--- /dev/null
+++ b/OAuthAuthentication.notranslate-alias.php
@@ -0,0 +1,21 @@
+<?php
+/**
+ * Aliases for special page which the user shouldn't access directly, so
+ * no need to translate (and translation will hurt the cache).
+ *
+ * Do not add this file to translatewiki.
+ *
+ * @file
+ * @ingroup Extensions
+ */
+// @codingStandardsIgnoreFile
+
+$specialPageAliases = array();
+
+/** English (English) */
+$specialPageAliases['en'] = array(
+       // Localizing Special:CentralAutoLogin causes issues (bug 54195) and is 
of
+       // miniscule benefit to users, so don't do so.
+       'OAuthLogin' => array( 'OAuthLogin' ),
+);
+
diff --git a/OAuthAuthentication.php b/OAuthAuthentication.php
new file mode 100644
index 0000000..a0e68d6
--- /dev/null
+++ b/OAuthAuthentication.php
@@ -0,0 +1,134 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+if ( !defined( 'MEDIAWIKI' ) ) {
+       echo "OAuth extension\n";
+       exit( 1 ) ;
+}
+
+$wgExtensionCredits['other'][] = array(
+       'path'           => __FILE__,
+       'name'           => 'OAuthAuthentication',
+       'description'    => 'This extension allows wikis to delegate their 
authentication to anther wiki',
+       'author'         => array( 'Chris Steipp' ),
+       'url'            => 
'https://www.mediawiki.org/wiki/Extension:OAuthAuthentication',
+       'version'        => '0.1.0'
+);
+
+/**
+ * Must be configured in LocalSettings.php!
+ * The OAuth special page on the wiki. Passing the title as a parameter
+ * is usually more reliable E.g., 
http://en.wikipedia.org/w/index.php?title=Special:OAuth
+ */
+$wgOAuthAuthenticationUrl = null;
+
+/**
+ * Must be configured in LocalSettings.php!
+ * The Key and Secret that were generated for you when you registered
+ * your consumer. RSA private key isn't currently supported.
+ */
+$wgOAuthAuthenticationConsumerKey = null;
+$wgOAuthAuthenticationConsumerSecret = null;
+
+/**
+ * Optionally set the Canonical url that the server will return,
+ * if it's different from the OAuth endpoint. OAuth will use
+ * wgCannonicalServer when generating the identity JWT, and this
+ * code will compare the iss to this value, or $wgOAuthAuthenticationUrl
+ * if this isn't set.
+ */
+$wgOAuthAuthenticationCanonicalUrl = null;
+
+/**
+ * Allow usurpation of accounts. If accounts on the OAuth provider have the 
same
+ * name as an already created local account, this flag decides if the user is 
allowed
+ * to login, or if the login will fail with an error message.
+ */
+$wgOAuthAuthenticationAccountUsurpation = false;
+
+/**
+ * Only allow creation/login of usernames that are on a whitelist. Setting 
this to
+ * false allows any username to register and login.
+ */
+$wgOAuthAuthenticationUsernameWhitelist = false;
+
+/**
+ * Only allow creation/login of users who are in groups on the remote wiki. 
Setting
+ * this to false allows any username to register and login.
+ */
+$wgOAuthAuthenticationGroupWhitelist = false;
+
+/**
+ * Allow local account creation. Set this to false if you only want
+ * to use remote accounts.
+ * Note: Once local accounts exist, this extension will not prevent
+ * them from logging in.
+ */
+$wgOAuthAuthenticationAllowLocalUsers = true;
+
+/**
+ * A simple text string, naming the remote wiki (used for text like, "Login on 
<wikiname>". If
+ * this is false, a generic "Remote OAuth Wiki" is used, which users may not 
understand.
+ */
+$wgOAuthAuthenticationRemoteName = false;
+
+/**
+ * Max age that a session can go without re-validating the user's identity.
+ */
+$wgOAuthAuthenticationMaxIdentityAge = 3600;
+
+/**
+ * If $wgOAuthAuthenticationUrl uses https, do we validate the certificate?
+ * This should always be true in production, but sometimes useful to disable
+ * while testing.
+ */
+$wgOAuthAuthenticationValidateSSL = true;
+
+$dir = __DIR__;
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\SpecialOAuthLogin']
 = "$dir/specials/SpecialOAuthLogin.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\Config'] = 
"$dir/utils/Config.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\Exception'] = 
"$dir/utils/Exception.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\Hooks'] = 
"$dir/utils/Hooks.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\Policy'] = 
"$dir/utils/Policy.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\OAuthExternalUser']
 = "$dir/utils/OAuthExternalUser.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\AuthenticationHandler']
 = "$dir/handlers/AuthenticationHandler.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\OAuth1Handler'] = 
"$dir/handlers/OAuth1Handler.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\SessionStore'] = 
"$dir/store/SessionStore.php";
+$wgAutoloadClasses['MediaWiki\Extensions\OAuthAuthentication\PhpSessionStore'] 
= "$dir/store/PhpSessionStore.php";
+
+## i18n
+$wgMessagesDirs['OAuthAuthentication'] = "$dir/i18n";
+#$messagesFiles['OAuthAuthentication'] = 
"$langDir/OAuthAuthentication.alias.php";
+$wgExtensionMessagesFiles['SpecialOAuthLoginNoTranslate'] = 
"$dir/OAuthAuthentication.notranslate-alias.php";
+
+
+
+## Use mwoauth-php. Cool Kids can use composer to do this.
+$wgAutoloadClasses['MWOAuthClientConfig'] = 
"$dir/libs/mwoauth-php/MWOAuthClient.php";
+$wgAutoloadClasses['MWOAuthClient'] = 
"$dir/libs/mwoauth-php/MWOAuthClient.php";
+$wgAutoloadClasses['OAuthToken'] = "$dir/libs/mwoauth-php/OAuth.php";
+
+
+$wgSpecialPages['OAuthLogin'] = 
'MediaWiki\Extensions\OAuthAuthentication\SpecialOAuthLogin';
+
+$wgHooks['PersonalUrls'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onPersonalUrls';
+$wgHooks['PostLoginRedirect'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onPostLoginRedirect';
+$wgHooks['LoadExtensionSchemaUpdates'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onLoadExtensionSchemaUpdates';
+$wgHooks['GetPreferences'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onGetPreferences';
+$wgHooks['AbortNewAccount'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onAbortNewAccount';
+$wgHooks['UserLoadAfterLoadFromSession'][] = 
'MediaWiki\Extensions\OAuthAuthentication\Hooks::onUserLoadAfterLoadFromSession';
+
+$wgHooks['UnitTestsList'][] = function( array &$files ) {
+       $directoryIterator = new \RecursiveDirectoryIterator( __DIR__ . 
'/tests/' );
+       foreach ( new \RecursiveIteratorIterator( $directoryIterator ) as 
$fileInfo ) {
+               if ( substr( $fileInfo->getFilename(), -8 ) === 'Test.php' ) {
+                       $files[] = $fileInfo->getPathname();
+               }
+       }
+       return true;
+};
+
+
+
+
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..3b7f210
--- /dev/null
+++ b/README.md
@@ -0,0 +1,4 @@
+OAuthAuthentication
+===================
+
+MediaWiki OAuth Authentication client
diff --git a/TODO.txt b/TODO.txt
new file mode 100644
index 0000000..b6dbd9b
--- /dev/null
+++ b/TODO.txt
@@ -0,0 +1,24 @@
+
+* Move Utils -> Hoos {{done}}
+** Add DB update hook {{done}}
+* Prefs {{done}}
+* Configuration {{done}}
+* i18n {{done}}
+* github {{done}}
+* Usurpation {{done}}
+* Whitelists {{done}}
+* Debugs {{done}}
+* Config flag to disallow local creates {{done}}
+** remove create account link in toolbar {{done}}
+* max session without revalidation when whitelisted {{done}}
+
+* Tests
+** Refactor special
+*** Divide finish handling for flow steps
+*** Location from Special page
+
+* comments / docs
+
+* Reorg directories?
+
+* Change returnto url when clicking login on Special:UserLogout
diff --git a/handlers/AuthenticationHandler.php 
b/handlers/AuthenticationHandler.php
new file mode 100644
index 0000000..c701d47
--- /dev/null
+++ b/handlers/AuthenticationHandler.php
@@ -0,0 +1,121 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class AuthenticationHandler {
+
+
+       public static function handleIdentity( \WebRequest $request, $identity, 
\OAuthToken $accessToken ) {
+               $exUser = OAuthExternalUser::newFromRemoteId(
+                       $identity->sub,
+                       $identity->username,
+                       wfGetDB( DB_MASTER )  #TODO: don't do this
+               );
+               $exUser->setAccessToken( $accessToken );
+               $exUser->setIdentifyTS( new \MWTimestamp() );
+
+               if ( $exUser->attached() ) {
+                       $status = AuthenticationHandler::doLogin( $exUser, 
$request );
+                       $s = \Status::newGood( array( 'successfulLogin', 
$status->getValue() ) );
+                       $s->merge( $status );
+               } else {
+                       $status = AuthenticationHandler::doCreateAndLogin( 
$exUser, $request );
+                       $s = \Status::newGood( array( 'successfulCreation', 
$status->getValue() ) );
+                       $s->merge( $status );
+               }
+
+               wfDebugLog( "OAuthAuth", __METHOD__ . " returning Status: " . 
(int) $s->isGood() );
+               return $s;
+       }
+
+       public static function doCreateAndLogin( OAuthExternalUser $exUser ) {
+               global $wgAuth, $wgOAuthAuthenticationAccountUsurpation;
+               wfDebugLog( "OAuthAuth", "Doing create & login for user " . 
$exUser->getName() );
+
+               $u = \User::newFromName( $exUser->getName(), 'creatable' );
+
+               if ( !is_object( $u ) ) {
+                       wfDebugLog( "OAuthAuth",
+                               __METHOD__ . ": Bad username 
'{$exUser->getName()}'" );
+                       return Status::newFatal( 'oauthauth-create-noname' );
+               } elseif ( 0 !== $u->idForName() ) {
+                       wfDebugLog( "OAuthAuth",
+                               __METHOD__ . ": User already exists, but no 
usurpation. Aborting." );
+                       if ( !$wgOAuthAuthenticationAccountUsurpation ) {
+                               return \Status::newFatal( 
'oauthauth-create-userexists' );
+                       }
+                       $exUser->setLocalId( $u->idForName() );
+               } else {
+                       wfDebugLog( "OAuthAuth",
+                               __METHOD__ . ": Creating user 
'{$exUser->getName()}'" );
+
+                       # TODO: Does this need to call $wgAuth->addUser? This 
could potentially coexist
+                       # with another auth plugin.
+
+                       $status = $u->addToDatabase();
+                       if ( !$status->isOK() ) {
+                               return $status;
+                       }
+
+                       /* TODO: Set email, realname, and language, once we can 
get them via /identify
+                       $u->setEmail( $exUser->getEmail() );
+                       $u->setRealName( $exUser->getRealName() );
+                       $u->setOption( 'language', $exUser->getLanguage() );
+                       */
+
+                       $u->setToken();
+                       \DeferredUpdates::addUpdate( new \SiteStatsUpdate( 0, 
0, 0, 0, 1 ) );
+                       $u->addWatch( $u->getUserPage(), 
\WatchedItem::IGNORE_USER_RIGHTS );
+                       $u->saveSettings();
+
+                       wfRunHooks( 'AddNewAccount', array( $u, false ) );
+
+                       $exUser->setLocalId( $u->getId() );
+               }
+
+               $exUser->addToDatabase( wfGetDB( DB_MASTER ) ); //TODO: di
+               $u->setCookies();
+               $u->addNewUserLogEntry( 'create' );
+
+               wfResetSessionID();
+
+               return \Status::newGood( $u );
+       }
+
+
+       public static function doLogin( OAuthExternalUser $exUser, \WebRequest 
$request ) {
+               global $wgSecureLogin, $wgCookieSecure;
+
+               wfDebugLog( "OAuthAuth",
+                       __METHOD__ . ": Logging in associated user 
'{$exUser->getName()}'" );
+
+               $u = \User::newFromId( $exUser->getLocalId() );
+
+               if ( !is_object( $u ) ) {
+                       wfDebugLog( "OAuthAuth",
+                               __METHOD__ . ": Associated user doesn't exist. 
Aborting." );
+                       return Status::newFatal( 'oauthauth-login-noname' );
+               } elseif ( $u->isAnon() ) {
+                       wfDebugLog( "OAuthAuth",
+                               __METHOD__ . ": Associated user is Anon. 
Aborting." );
+                       return \Status::newFatal( 
'oauthauth-login-usernotexists' );
+               }
+wfDebugLog( "OAA", __METHOD__ . " updating exuser: " . print_r( $exUser, true 
) );
+               $exUser->updateInDatabase( wfGetDB( DB_MASTER ) );
+
+               $u->invalidateCache();
+
+               if ( !$wgSecureLogin ) {
+                       $u->setCookies( $request, null );
+               } elseif ( $u->requiresHTTPS() ) {
+                       $u->setCookies( $request, true );
+               } else {
+                       $u->setCookies( $request, false );
+                       $wgCookieSecure = false;
+               }
+
+               wfResetSessionID();
+
+               return \Status::newGood( $u );
+       }
+}
diff --git a/handlers/OAuth1Handler.php b/handlers/OAuth1Handler.php
new file mode 100644
index 0000000..7027bf9
--- /dev/null
+++ b/handlers/OAuth1Handler.php
@@ -0,0 +1,53 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class OAuth1Handler {
+
+
+       public function init( SessionStore $session, $client ) {
+               // Step 1 - Get a request token
+               list( $redir, $requestToken ) = $client->initiate();
+               $session->set( 'oauthreqtoken', 
"{$requestToken->key}:{$requestToken->secret}" );
+               return $redir;
+       }
+
+       public function authorize( \WebResponse $response, $url ) {
+               $response->header( "Location: $url", true );
+       }
+
+
+       public function finish( \WebRequest $request, SessionStore $session, 
$client ) {
+               $verifyCode = $request->getVal( 'oauth_verifier', false );
+               $recKey = $request->getVal( 'oauth_token', false );
+
+               if ( !$verifyCode || ! $recKey ) {
+                       throw new Exception( 'oauthauth-failed-handshake' );
+               }
+
+               list( $requestKey, $requestSecret ) = explode( ':', 
$session->get( 'oauthreqtoken' ) );
+               $requestToken = new \OAuthToken( $requestKey, $requestSecret );
+
+               $session->delete( 'oauthreqtoken' );
+
+               //check for csrf
+               if ( $requestKey !== $recKey ) {
+                       throw new Exception( "oauthauth-csrf-detected" );
+               }
+
+               // Step 3 - Get access token
+               $accessToken = $client->complete( $requestToken,  $verifyCode );
+
+               return $accessToken;
+       }
+
+
+       public function identify( \OAuthToken $accessToken, $client ) {
+               // Get Identity
+               $identity = $client->identify( $accessToken );
+
+               return $identity;
+       }
+
+
+}
diff --git a/i18n/en.json b/i18n/en.json
new file mode 100644
index 0000000..460f488
--- /dev/null
+++ b/i18n/en.json
@@ -0,0 +1,20 @@
+{
+       "@metadata": {
+               "authors": [
+                       "CSteipp"
+               ]
+       },
+       "oauthauth-already-logged-in": "You are already logged in. $1 to 
continue",
+       "oauthauth-error": "OAuth Login Error",
+       "ouathauth-logout": "Logout",
+       "oauthauth-failed-handshake": "There was an OAuth protocol error 
logging you in. The remote wiki may be experiencing technical issues",
+       "oauthauth-csrf-detected": "OAuth session mismatch. Please try logging 
in again.",
+       "oauthauth-create-noname": "OAuth error logging you in. The username on 
the remote wiki isn't valid on this wiki.",
+       "oauthauth-create-userexists": "OAuth error logging you in. The 
username on the remote wiki already exists on this wiki. You must contact a 
bureaucrat to usurp the account.",
+       "oauthauth-login-noname": "The OAuth user is connected, but the name 
isn't valid on this wiki.",
+       "oauthauth-login": "Log in on $1",
+       "oauthauth-login-usernotexists": "The OAuth user listed as connected, 
but the user doesn't exist on this wiki",
+       "oauthauth-nologin-policy": "This wiki's policy will not let you 
login.",
+       "oauthauth-localuser-not-allowed": "The site administrator has disabled 
local account creation. You should $1 to create an account with OAuth.",
+       "oauthauth-loggout-policy": "You have been logged out because the site 
policy no longer allows you to be logged in."
+}
diff --git a/i18n/qqq.json b/i18n/qqq.json
new file mode 100644
index 0000000..d4c8872
--- /dev/null
+++ b/i18n/qqq.json
@@ -0,0 +1,20 @@
+{
+       "@metadata": {
+               "authors": [
+                       "CSteipp"
+               ]
+       },
+       "oauthauth-already-logged-in": "You are already logged in. $1 to 
continue",
+       "oauthauth-error": "Title for OAuth login page errors",
+       "ouathauth-logout": "Link text for logout link",
+       "oauthauth-failed-handshake": "Error message to users when an unknown 
issue occured.",
+       "oauthauth-csrf-detected": "Error when anti-csrf tokens do not match, 
and the user needs to login again.",
+       "oauthauth-create-noname": "Error when the username on the OAuth-server 
wiki isn't valid on this client wiki.",
+       "oauthauth-create-userexists": "Error when the login account is already 
created on the client wiki, preventing the user from logging in.",
+       "oauthauth-login-noname": "Error when the username on the OAuth-server 
wiki isn't valid on this client wiki.",
+       "oauthauth-login": "Text for link to login on remote 
wiki.\n\nParameters:\n* $1 - the name of the remote wiki where the user will 
log in.",
+       "oauthauth-login-usernotexists": "Error when the user logs in with an 
account that was attached on this wiki, but no longer exists.",
+       "oauthauth-nologin-policy": "Error the user sees hwne the local wiki's 
administrator has prevented their login with a policy.",
+       "oauthauth-localuser-not-allowed": "Error when the user attempts to 
create an account, but the wiki isn't configured to allow it, and instructing 
the user to login via OAuth.\n\nParameters:\n* $1 - link text {{msg-mw|login}}. 
A link to login.",
+       "oauthauth-loggout-policy": "Error message when a user is logged out 
because their user no longer complies with the requirements set by the site 
administrator"
+}
diff --git a/libs/mwoauth-php/MWOAuthClient.php 
b/libs/mwoauth-php/MWOAuthClient.php
new file mode 100644
index 0000000..b116ee1
--- /dev/null
+++ b/libs/mwoauth-php/MWOAuthClient.php
@@ -0,0 +1,288 @@
+<?php
+
+include_once 'OAuth.php'; // reference php library from oauth.net
+
+if ( !function_exists( 'wfDebugLog' ) ) {
+       function wfDebugLog( $method, $msg) {
+               // Uncomment this if you want debuggging info from the OAuth 
library
+               //echo "[$method] $msg\n";
+       }
+}
+
+
+class MWOAuthClientConfig {
+
+       // Url to the OAuth special page
+       public $endpointURL;
+
+       // Canonical server url, used to check /identify's iss
+       public $canonicalServerUrl;
+
+       // Url that the user is sent to. Can be different from
+       // $endpointURL to play nice with MobileFrontend, etc.
+       public $redirURL = null;
+
+       // Use https when calling the server.
+       // TODO: detect this from $endpointURL
+       public $useSSL = true;
+
+       // If you're testing against a server with self-signed certificates, you
+       // can turn this off but don't do this in production.
+       public $verifySSL = true;
+
+       function __construct( $url, $useSSL, $verifySSL ) {
+               $this->endpointURL = $url;
+               $this->useSSL = $useSSL;
+               $this->verifySSL = $verifySSL;
+       }
+
+}
+
+class MWOAuthClient {
+
+       // MWOAuthClientConfig
+       private $config;
+
+       // TODO: move this to $config
+       private $consumerToken;
+
+       // Any extra params in the call that need to be signed
+       private $extraParams = array();
+
+       // Track the last random nonce generated by the OAuth lib, used to
+       // verify /identity response isn't a replay
+       private $lastNonce;
+
+       function __construct( MWOAuthClientConfig $config, OAuthToken $cmrToken 
) {
+               $this->consumerToken = $cmrToken;
+               $this->config = $config;
+       }
+
+
+       public static function newFromKeyAndSecret( $url, $key, $secret ) {
+               $cmrToken = new OAuthToken( $key, $secret );
+               $config = new MWOAuthClientConfig( $url, true, true );
+               return new self( $config, $cmrToken );
+       }
+
+       public function setExtraParam( $key, $value ) {
+               $this->extraParams[$key] = $value;
+       }
+
+       public function setExtraParams( $params ) {
+               $this->extraParams = $params;
+       }
+
+       /**
+        * First part of 3-legged OAuth, get the request Token.
+        * Redirect your authorizing users to the redirect url, and keep
+        * track of the request token since you need to pass it into complete()
+        *
+        * @return array (redirect, request/temp token)
+        */
+       public function initiate() {
+               $initUrl = $this->config->endpointURL . 
'/initiate&format=json&oauth_callback=oob';
+               $data = $this->makeOAuthCall( null, $initUrl );
+               $return = json_decode( $data );
+               if ( $return->oauth_callback_confirmed !== 'true' ) {
+                       throw new Exception( "Callback wasn't confirmed" );
+               }
+               $requestToken = new OAuthToken( $return->key, $return->secret );
+               $url = $this->config->redirURL ?: $this->config->endpointURL . 
"/authorize&";
+               $url .= 
"oauth_token={$requestToken->key}&oauth_consumer_key={$this->consumerToken->key}";
+
+               return array( $url, $requestToken );
+       }
+
+       /**
+        * The final leg of the OAuth handshake. Exchange the request Token from
+        * initiate() and the verification code that the user submitted back to 
you
+        * for an access token, which you'll use for all API calls.
+        *
+        * @param the authorization code sent to the callback url
+        * @param the temp/request token obtained from initiate, or null if this
+        *      object was used and the token is already set.
+        * @return OAuthToken The access token
+        */
+       public function complete( OAuthToken $requestToken, $verifyCode ) {
+               $tokenUrl = $this->config->endpointURL . '/token&format=json';
+               $this->setExtraParam( 'oauth_verifier', $verifyCode );
+               $data = $this->makeOAuthCall( $requestToken , $tokenUrl );
+               $return = json_decode( $data );
+               $accessToken = new OAuthToken( $return->key, $return->secret );
+               $this->setExtraParams = array(); // cleanup after ourselves
+               return $accessToken;
+       }
+
+
+       /**
+        * Optional step. This call the MediaWiki specific /identify method, 
which
+        * returns a signed statement of the authorizing user's identity. Use 
this
+        * if you are authenticating users in your application, and you need to
+        * know their username, groups, rights, etc in MediaWiki.
+        *
+        * @param OAuthToken access token from complete()
+        * @return object containing attributes of the user
+        */
+       public function identify( OAuthToken $accessToken ) {
+               $identifyUrl = $this->config->endpointURL . '/identify';
+               $data = $this->makeOAuthCall( $accessToken, $identifyUrl );
+               $identity = $this->decodeJWT( $data, 
$this->consumerToken->secret );
+
+               if ( !$this->validateJWT(
+                       $identity,
+                       $this->consumerToken->key,
+                       $this->config->canonicalServerUrl,
+                       $this->lastNonce
+               ) ) {
+                       throw new Exception( "JWT didn't validate" );
+               }
+
+               return $identity;
+       }
+
+       /**
+        * Make a signed request to MediaWiki
+        *
+        * @param OAuthToken $token additional token to use in signature, 
besides the consumer token.
+        *      In most cases, this will be the access token you got from 
complete(), but we set it
+        *      to the request token when finishing the handshake.
+        * @param $url string url to call
+        * @param $isPost bool true if this should be a POST request
+        * @param $postFields array of POST parameters, only if $isPost is also 
true
+        * @return body from the curl request
+        */
+       public function makeOAuthCall( $token, $url, $isPost = false, 
$postFields = false  ) {
+
+               $params = array();
+
+               // Get any params from the url
+               if ( strpos( $url, '?' ) ) {
+                       $parsed = parse_url( $url );
+                       parse_str($parsed['query'], $params);
+               }
+               $params += $this->extraParams;
+
+               $method = $isPost ? 'POST' : 'GET';
+               $req = OAuthRequest::from_consumer_and_token(
+                       $this->consumerToken,
+                       $token,
+                       $method,
+                       $url,
+                       $params
+               );
+               $req->sign_request(
+                       new OAuthSignatureMethod_HMAC_SHA1(),
+                       $this->consumerToken,
+                       $token
+               );
+
+               $this->lastNonce = $req->get_parameter( 'oauth_nonce' );
+
+               return $this->makeCurlCall(
+                       $url,
+                       $req->to_header(),
+                       $isPost,
+                       $postFields,
+                       $this->config
+               );
+
+       }
+
+
+       private function makeCurlCall( $url, $headers, $isPost, $postFields, 
MWOAuthClientConfig $config ) {
+
+               $ch = curl_init();
+               curl_setopt( $ch, CURLOPT_URL, (string) $url );
+               curl_setopt( $ch, CURLOPT_HEADER, 0 );
+               curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 );
+               curl_setopt( $ch, CURLOPT_HTTPHEADER, array( $headers ) );
+
+               if ( $isPost ) {
+                       curl_setopt( $ch, CURLOPT_POST, true );
+                       curl_setopt( $ch, CURLOPT_POSTFIELDS, http_build_query( 
$postFields ) );
+               }
+
+               if ( $config->useSSL ) {
+                       curl_setopt( $ch, CURLOPT_PORT , 443 );
+               }
+
+               if ( $config->verifySSL ) {
+                       curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, true );
+                       curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 2 );
+               } else {
+                       curl_setopt( $ch, CURLOPT_SSL_VERIFYPEER, false );
+                       curl_setopt( $ch, CURLOPT_SSL_VERIFYHOST, 0 );
+               }
+
+               $data = curl_exec( $ch );
+               if( !$data ) {
+                       throw new Exception ( 'Curl error: ' . curl_error( $ch 
) );
+               }
+
+               return $data;
+       }
+
+
+       private function decodeJWT( $JWT, $secret ) {
+               list( $headb64, $bodyb64, $sigb64 ) = explode( '.', $JWT );
+
+               $header = json_decode( $this->urlsafeB64Decode( $headb64 ) );
+               $payload = json_decode( $this->urlsafeB64Decode( $bodyb64 ) );
+               $sig = $this->urlsafeB64Decode( $sigb64 );
+
+               // MediaWiki will only use sha256 hmac (HS256) for now. This 
check makes sure
+               // an attacker doesn't return a JWT with 'none' signature type.
+               $expectSig = hash_hmac( 'sha256', "$headb64.$bodyb64", $secret, 
true);
+               if ( $header->alg !== 'HS256' || !$this->compareHash( $sig, 
$expectSig ) ) {
+                       throw new Exception( "Invalid JWT signature from 
/identify." );
+               }
+               return $payload;
+       }
+
+       protected function validateJWT( $identity, $consumerKey, 
$expectedConnonicalServer, $nonce ) {
+               // Verify the issuer is who we expect (server sends 
$wgCanonicalServer)
+               if ( $identity->iss !== $expectedConnonicalServer ) {
+                       print "Invalid Issuer: {$identity->iss} !== 
{$expectedConnonicalServer}";
+                       return false;
+               }
+               // Verify we are the intended audience
+               if ( $identity->aud !== $consumerKey ) {
+                       print "Invalid Audience";
+                       return false;
+               }
+               // Verify we are within the time limits of the token. Issued at 
(iat) should be
+               // in the past, Expiration (exp) should be in the future.
+               $now = time();
+               if ( $identity->iat > $now || $identity->exp < $now ) {
+                       print "Invalid Time";
+                       return false;
+               }
+               // Verify we haven't seen this nonce before, which would 
indicate a replay attack
+               if ( $identity->nonce !== $nonce ) {
+                       print "Invalid Nonce";
+                       return false;
+               }
+               return true;
+       }
+
+       private function urlsafeB64Decode( $input ) {
+               $remainder = strlen( $input ) % 4;
+               if ( $remainder ) {
+                       $padlen = 4 - $remainder;
+                       $input .= str_repeat( '=', $padlen );
+               }
+               return base64_decode( strtr( $input, '-_', '+/' ) );
+       }
+
+       // Constant time comparison
+       private function compareHash( $hash1, $hash2 ) {
+               $result = strlen( $hash1 ) ^ strlen( $hash2 );
+               $len = min( strlen( $hash1 ), strlen( $hash2 ) ) - 1;
+               for ( $i = 0; $i < $len; $i++ ) {
+                       $result |= ord( $hash1{$i} ) ^ ord( $hash2{$i} );
+               }
+               return $result == 0;
+       }
+
+}
diff --git a/libs/mwoauth-php/OAuth.php b/libs/mwoauth-php/OAuth.php
new file mode 100644
index 0000000..2856003
--- /dev/null
+++ b/libs/mwoauth-php/OAuth.php
@@ -0,0 +1,925 @@
+<?php
+// vim: foldmethod=marker
+/**
+ * The MIT License
+ *
+ * Copyright (c) 2007 Andy Smith
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files ( the "Software" ), to 
deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+*/
+/* Generic exception class
+ */
+class OAuthException extends Exception {
+       // pass
+}
+
+class OAuthConsumer {
+       public $key;
+       public $secret;
+       public $callback_url;
+
+       function __construct( $key, $secret, $callback_url = NULL ) {
+               $this->key = $key;
+               $this->secret = $secret;
+               $this->callback_url = $callback_url;
+       }
+
+       function __toString() {
+               return "OAuthConsumer[key=$this->key,secret=$this->secret]";
+       }
+}
+
+class OAuthToken {
+       // access tokens and request tokens
+       public $key;
+       public $secret;
+
+       /**
+        * key = the token
+        * secret = the token secret
+        */
+       function __construct( $key, $secret ) {
+               $this->key = $key;
+               $this->secret = $secret;
+       }
+
+       /**
+        * generates the basic string serialization of a token that a server
+        * would respond to request_token and access_token calls with
+        */
+       function to_string() {
+               return "oauth_token=" .
+                        OAuthUtil::urlencode_rfc3986( $this->key ) .
+                        "&oauth_token_secret=" .
+                        OAuthUtil::urlencode_rfc3986( $this->secret );
+       }
+
+       function __toString() {
+               return $this->to_string();
+       }
+}
+
+/**
+ * A class for implementing a Signature Method
+ * See section 9 ( "Signing Requests" ) in the spec
+ */
+abstract class OAuthSignatureMethod {
+       /**
+        * Needs to return the name of the Signature Method ( ie HMAC-SHA1 )
+        * @return string
+        */
+       abstract public function get_name();
+
+       /**
+        * Build up the signature
+        * NOTE: The output of this function MUST NOT be urlencoded.
+        * the encoding is handled in OAuthRequest when the final
+        * request is serialized
+        * @param OAuthRequest $request
+        * @param OAuthConsumer $consumer
+        * @param OAuthToken $token
+        * @return string
+        */
+       abstract public function build_signature( $request, $consumer, $token );
+
+       /**
+        * Verifies that a given signature is correct
+        * @param OAuthRequest $request
+        * @param OAuthConsumer $consumer
+        * @param OAuthToken $token
+        * @param string $signature
+        * @return bool
+        */
+       public function check_signature( $request, $consumer, $token, 
$signature ) {
+               wfDebugLog( 'OAuth', __METHOD__ . ": Expecting: '$signature'" );
+               $built = $this->build_signature( $request, $consumer, $token );
+               wfDebugLog( 'OAuth', __METHOD__ . ": Built: '$built'" );
+               // Check for zero length, although unlikely here
+               if ( strlen( $built ) == 0 || strlen( $signature ) == 0 ) {
+                       return false;
+               }
+
+               if ( strlen( $built ) != strlen( $signature ) ) {
+                       return false;
+               }
+
+               // Avoid a timing leak with a ( hopefully ) time insensitive 
compare
+               $result = 0;
+               for ( $i = 0; $i < strlen( $signature ); $i++ ) {
+                       $result |= ord( $built{$i} ) ^ ord( $signature{$i} );
+               }
+
+               return $result == 0;
+       }
+}
+
+/**
+ * The HMAC-SHA1 signature method uses the HMAC-SHA1 signature algorithm as 
defined in [RFC2104] 
+ * where the Signature Base String is the text and the key is the concatenated 
values ( each first 
+ * encoded per Parameter Encoding ) of the Consumer Secret and Token Secret, 
separated by an '&' 
+ * character ( ASCII code 38 ) even if empty.
+ *      - Chapter 9.2 ( "HMAC-SHA1" )
+ */
+class OAuthSignatureMethod_HMAC_SHA1 extends OAuthSignatureMethod {
+       function get_name() {
+               return "HMAC-SHA1";
+       }
+
+       public function build_signature( $request, $consumer, $token ) {
+               $base_string = $request->get_signature_base_string();
+               wfDebugLog( 'OAuth', __METHOD__ . ": Base string: 
'$base_string'" );
+               $request->base_string = $base_string;
+
+               $key_parts = array(
+                       $consumer->secret,
+                       ( $token ) ? $token->secret : ""
+                );
+
+               $key_parts = OAuthUtil::urlencode_rfc3986( $key_parts );
+               $key = implode( '&', $key_parts );
+               wfDebugLog( 'OAuth', __METHOD__ . ": HMAC Key: '$key'" );
+               return base64_encode( hash_hmac( 'sha1', $base_string, $key, 
true ) );
+       }
+}
+
+/**
+ * The PLAINTEXT method does not provide any security protection and SHOULD 
only be used 
+ * over a secure channel such as HTTPS. It does not use the Signature Base 
String.
+ *      - Chapter 9.4 ( "PLAINTEXT" )
+ */
+class OAuthSignatureMethod_PLAINTEXT extends OAuthSignatureMethod {
+       public function get_name() {
+               return "PLAINTEXT";
+       }
+
+       /**
+        * oauth_signature is set to the concatenated encoded values of the 
Consumer Secret and 
+        * Token Secret, separated by a '&' character ( ASCII code 38 ), even 
if either secret is 
+        * empty. The result MUST be encoded again.
+        *       - Chapter 9.4.1 ( "Generating Signatures" )
+        *
+        * Please note that the second encoding MUST NOT happen in the 
SignatureMethod, as
+        * OAuthRequest handles this!
+        */
+       public function build_signature( $request, $consumer, $token ) {
+               $key_parts = array(
+                       $consumer->secret,
+                       ( $token ) ? $token->secret : ""
+                );
+
+               $key_parts = OAuthUtil::urlencode_rfc3986( $key_parts );
+               $key = implode( '&', $key_parts );
+               $request->base_string = $key;
+
+               return $key;
+       }
+}
+
+/**
+ * The RSA-SHA1 signature method uses the RSASSA-PKCS1-v1_5 signature 
algorithm as defined in 
+ * [RFC3447] section 8.2 ( more simply known as PKCS#1 ), using SHA-1 as the 
hash function for 
+ * EMSA-PKCS1-v1_5. It is assumed that the Consumer has provided its RSA 
public key in a 
+ * verified way to the Service Provider, in a manner which is beyond the scope 
of this 
+ * specification.
+ *      - Chapter 9.3 ( "RSA-SHA1" )
+ */
+abstract class OAuthSignatureMethod_RSA_SHA1 extends OAuthSignatureMethod {
+       public function get_name() {
+               return "RSA-SHA1";
+       }
+
+       // Up to the SP to implement this lookup of keys. Possible ideas are:
+       // ( 1 ) do a lookup in a table of trusted certs keyed off of consumer
+       // ( 2 ) fetch via http using a url provided by the requester
+       // ( 3 ) some sort of specific discovery code based on request
+       //
+       // Either way should return a string representation of the certificate
+       protected abstract function fetch_public_cert( &$request );
+
+       // Up to the SP to implement this lookup of keys. Possible ideas are:
+       // ( 1 ) do a lookup in a table of trusted certs keyed off of consumer
+       //
+       // Either way should return a string representation of the certificate
+       protected abstract function fetch_private_cert( &$request );
+
+       public function build_signature( $request, $consumer, $token ) {
+               $base_string = $request->get_signature_base_string();
+               $request->base_string = $base_string;
+
+               // Fetch the private key cert based on the request
+               $cert = $this->fetch_private_cert( $request );
+
+               // Pull the private key ID from the certificate
+               $privatekeyid = openssl_get_privatekey( $cert );
+
+               // Sign using the key
+               $ok = openssl_sign( $base_string, $signature, $privatekeyid );
+
+               // Release the key resource
+               openssl_free_key( $privatekeyid );
+
+               return base64_encode( $signature );
+       }
+
+       public function check_signature( $request, $consumer, $token, 
$signature ) {
+               $decoded_sig = base64_decode( $signature );
+
+               $base_string = $request->get_signature_base_string();
+
+               // Fetch the public key cert based on the request
+               $cert = $this->fetch_public_cert( $request );
+
+               // Pull the public key ID from the certificate
+               $publickeyid = openssl_get_publickey( $cert );
+
+               // Check the computed signature against the one passed in the 
query
+               $ok = openssl_verify( $base_string, $decoded_sig, $publickeyid 
);
+
+               // Release the key resource
+               openssl_free_key( $publickeyid );
+
+               return $ok == 1;
+       }
+}
+
+class OAuthRequest {
+       protected $parameters;
+       protected $http_method;
+       protected $http_url;
+       // for debug purposes
+       public $base_string;
+       public static $version = '1.0';
+       public static $POST_INPUT = 'php://input';
+
+       function __construct( $http_method, $http_url, $parameters = NULL ) {
+               $parameters = ($parameters) ? $parameters : array();
+               $parameters = array_merge( 
OAuthUtil::parse_parameters(parse_url($http_url, PHP_URL_QUERY)), $parameters);
+               $this->parameters = $parameters;
+               $this->http_method = $http_method;
+               $this->http_url = $http_url;
+       }
+
+
+       /**
+        * attempt to build up a request from what was passed to the server
+        */
+       public static function from_request( $http_method = NULL, $http_url = 
NULL, $parameters = NULL ) {
+               $scheme = ( !isset( $_SERVER['HTTPS'] ) || $_SERVER['HTTPS'] != 
"on" )
+                       ? 'http'
+                       : 'https';
+               $http_url = ( $http_url ) ? $http_url : $scheme .
+                       '://' . $_SERVER['SERVER_NAME'] .
+                       ':' .
+                       $_SERVER['SERVER_PORT'] .
+                       $_SERVER['REQUEST_URI'];
+               $http_method = ( $http_method ) ? $http_method : 
$_SERVER['REQUEST_METHOD'];
+
+               // We weren't handed any parameters, so let's find the ones 
relevant to
+               // this request.
+               // If you run XML-RPC or similar you should use this to provide 
your own
+               // parsed parameter-list
+               if ( !$parameters ) {
+                       // Find request headers
+                       $request_headers = OAuthUtil::get_headers();
+
+                       // Parse the query-string to find GET parameters
+                       $parameters = OAuthUtil::parse_parameters( 
$_SERVER['QUERY_STRING'] );
+
+                       // It's a POST request of the proper content-type, so 
parse POST
+                       // parameters and add those overriding any duplicates 
from GET
+                       if ( $http_method == "POST"
+                               && isset( $request_headers['Content-Type'] )
+                               && strstr( $request_headers['Content-Type'],
+                                        'application/x-www-form-urlencoded'
+                               )
+                       ) {
+                               $post_data = OAuthUtil::parse_parameters( 
file_get_contents( self::$POST_INPUT ) );
+                               $parameters = array_merge( $parameters, 
$post_data );
+                       }
+
+                       // We have a Authorization-header with OAuth data. 
Parse the header
+                       // and add those overriding any duplicates from GET or 
POST
+                       if ( isset( $request_headers['Authorization'] )
+                               && substr( $request_headers['Authorization'], 
0, 6 ) == 'OAuth '
+                       ) {
+                               $header_parameters = OAuthUtil::split_header( 
$request_headers['Authorization'] );
+                               $parameters = array_merge( $parameters, 
$header_parameters );
+                       }
+
+               }
+
+               return new OAuthRequest( $http_method, $http_url, $parameters );
+       }
+
+       /**
+        * pretty much a helper function to set up the request
+        */
+       public static function from_consumer_and_token( $consumer, $token, 
$http_method, $http_url, $parameters = NULL ) {
+               $parameters = ( $parameters ) ? $parameters : array();
+               $defaults = array( "oauth_version" => OAuthRequest::$version,
+                       "oauth_nonce" => OAuthRequest::generate_nonce(),
+                       "oauth_timestamp" => OAuthRequest::generate_timestamp(),
+                       "oauth_consumer_key" => $consumer->key );
+               if ( $token ) {
+                       $defaults['oauth_token'] = $token->key;
+               }
+
+               $parameters = array_merge( $defaults, $parameters );
+
+               return new OAuthRequest( $http_method, $http_url, $parameters );
+       }
+
+       public function set_parameter( $name, $value, $allow_duplicates = true 
) {
+               if ( $allow_duplicates && isset( $this->parameters[$name] ) ) {
+                       // We have already added parameter( s ) with this name, 
so add to the list
+                       if ( is_scalar( $this->parameters[$name] ) ) {
+                               // This is the first duplicate, so transform 
scalar ( string )
+                               // into an array so we can add the duplicates
+                               $this->parameters[$name] = array( 
$this->parameters[$name] );
+                       }
+
+                       $this->parameters[$name][] = $value;
+               } else {
+                       $this->parameters[$name] = $value;
+               }
+       }
+
+       public function get_parameter( $name ) {
+               return isset( $this->parameters[$name] ) ? 
$this->parameters[$name] : null;
+       }
+
+       public function get_parameters() {
+               return $this->parameters;
+       }
+
+       public function unset_parameter( $name ) {
+               unset( $this->parameters[$name] );
+       }
+
+       /**
+        * The request parameters, sorted and concatenated into a normalized 
string.
+        * @return string
+        */
+       public function get_signable_parameters() {
+               // Grab all parameters
+               $params = $this->parameters;
+
+               // Remove oauth_signature if present
+               // Ref: Spec: 9.1.1 ( "The oauth_signature parameter MUST be 
excluded." )
+               if ( isset( $params['oauth_signature'] ) ) {
+                       unset( $params['oauth_signature'] );
+               }
+
+               return OAuthUtil::build_http_query( $params );
+       }
+
+       /**
+        * Returns the base string of this request
+        *
+        * The base string defined as the method, the url
+        * and the parameters ( normalized ), each urlencoded
+        * and the concated with &.
+        */
+       public function get_signature_base_string() {
+               //wfDebugLog( 'OAuth', __METHOD__ . ": Generating base string 
when this->paramters:\n" . print_r( $this->parameters, true ) );
+               $parts = array(
+                       $this->get_normalized_http_method(),
+                       $this->get_normalized_http_url(),
+                       $this->get_signable_parameters()
+                );
+
+               $parts = OAuthUtil::urlencode_rfc3986( $parts );
+
+               return implode( '&', $parts );
+       }
+
+       /**
+        * just uppercases the http method
+        */
+       public function get_normalized_http_method() {
+               return strtoupper( $this->http_method );
+       }
+
+       /**
+        * parses the url and rebuilds it to be
+        * scheme://host/path
+        */
+       public function get_normalized_http_url() {
+               $parts = parse_url( $this->http_url );
+
+               $scheme = ( isset( $parts['scheme'] ) ) ? $parts['scheme'] : 
'http';
+               $port = ( isset( $parts['port'] ) ) ? $parts['port'] : ( ( 
$scheme == 'https' ) ? '443' : '80' );
+               $host = ( isset( $parts['host'] ) ) ? strtolower( 
$parts['host'] ) : '';
+               $path = ( isset( $parts['path'] ) ) ? $parts['path'] : '';
+
+               if ( ( $scheme == 'https' && $port != '443' )
+                               || ( $scheme == 'http' && $port != '80' ) ) {
+                       $host = "$host:$port";
+               }
+               return "$scheme://$host$path";
+       }
+
+       /**
+        * builds a url usable for a GET request
+        */
+       public function to_url() {
+               $post_data = $this->to_postdata();
+               $out = $this->get_normalized_http_url();
+               if ( $post_data ) {
+                       $out .= '?'.$post_data;
+               }
+               return $out;
+       }
+
+       /**
+        * builds the data one would send in a POST request
+        */
+       public function to_postdata() {
+               return OAuthUtil::build_http_query( $this->parameters );
+       }
+
+       /**
+        * builds the Authorization: header
+        */
+       public function to_header( $realm = null ) {
+               $first = true;
+               if( $realm ) {
+                       $out = 'Authorization: OAuth realm="' . 
OAuthUtil::urlencode_rfc3986( $realm ) . '"';
+                       $first = false;
+               } else {
+                       $out = 'Authorization: OAuth';
+               }
+               $total = array();
+               foreach ( $this->parameters as $k => $v ) {
+                       if ( substr( $k, 0, 5 ) != "oauth" ) continue;
+                       if ( is_array( $v ) ) {
+                               throw new OAuthException( 'Arrays not supported 
in headers' );
+                       }
+                       $out .= ( $first ) ? ' ' : ',';
+                       $out .= OAuthUtil::urlencode_rfc3986( $k ) .
+                               '="' .
+                               OAuthUtil::urlencode_rfc3986( $v ) .
+                               '"';
+                       $first = false;
+               }
+               return $out;
+       }
+
+       public function __toString() {
+               return $this->to_url();
+       }
+
+
+       public function sign_request( $signature_method, $consumer, $token ) {
+               $this->set_parameter(
+                       "oauth_signature_method",
+                       $signature_method->get_name(),
+                       false
+                );
+               $signature = $this->build_signature( $signature_method, 
$consumer, $token );
+               $this->set_parameter( "oauth_signature", $signature, false );
+       }
+
+       public function build_signature( $signature_method, $consumer, $token ) 
{
+               $signature = $signature_method->build_signature( $this, 
$consumer, $token );
+               return $signature;
+       }
+
+       /**
+        * util function: current timestamp
+        */
+       private static function generate_timestamp() {
+               return time();
+       }
+
+       /**
+        * util function: current nonce
+        */
+       private static function generate_nonce() {
+               $mt = microtime();
+               $rand = mt_rand();
+
+               return md5( $mt . $rand ); // md5s look nicer than numbers
+       }
+}
+
+class OAuthServer {
+       protected $timestamp_threshold = 300; // in seconds, five minutes
+       protected $version = '1.0'; // hi blaine
+       protected $signature_methods = array();
+
+       protected $data_store;
+
+       function __construct( $data_store ) {
+               $this->data_store = $data_store;
+       }
+
+       public function add_signature_method( $signature_method ) {
+               $this->signature_methods[$signature_method->get_name()] = 
+                       $signature_method;
+       }
+
+       // high level functions
+
+       /**
+        * process a request_token request
+        * returns the request token on success
+        */
+       public function fetch_request_token( &$request ) {
+               $this->get_version( $request );
+
+               $consumer = $this->get_consumer( $request );
+
+               // no token required for the initial token request
+               $token = NULL;
+
+               $this->check_signature( $request, $consumer, $token );
+
+               // Rev A change
+               $callback = $request->get_parameter( 'oauth_callback' );
+               $new_token = $this->data_store->new_request_token( $consumer, 
$callback );
+
+               return $new_token;
+       }
+
+       /**
+        * process an access_token request
+        * returns the access token on success
+        */
+       public function fetch_access_token( &$request ) {
+               $this->get_version( $request );
+
+               $consumer = $this->get_consumer( $request );
+
+               // requires authorized request token
+               $token = $this->get_token( $request, $consumer, "request" );
+
+               $this->check_signature( $request, $consumer, $token );
+
+               // Rev A change
+               $verifier = $request->get_parameter( 'oauth_verifier' );
+               $new_token = $this->data_store->new_access_token( $token, 
$consumer, $verifier );
+
+               return $new_token;
+       }
+
+       /**
+        * verify an api call, checks all the parameters
+        */
+       public function verify_request( &$request ) {
+               $this->get_version( $request );
+               $consumer = $this->get_consumer( $request );
+               $token = $this->get_token( $request, $consumer, "access" );
+               $this->check_signature( $request, $consumer, $token );
+               return array( $consumer, $token );
+       }
+
+       // Internals from here
+       /**
+        * version 1
+        */
+       protected function get_version( &$request ) {
+               $version = $request->get_parameter( "oauth_version" );
+               if ( !$version ) {
+                       // Service Providers MUST assume the protocol version 
to be 1.0 if this parameter is not present. 
+                       // Chapter 7.0 ( "Accessing Protected Ressources" )
+                       $version = '1.0';
+               }
+               if ( $version !== $this->version ) {
+                       throw new OAuthException( "OAuth version '$version' not 
supported" );
+               }
+               return $version;
+       }
+
+       /**
+        * figure out the signature with some defaults
+        */
+       private function get_signature_method( $request ) {
+               $signature_method = $request instanceof OAuthRequest
+                       ? $request->get_parameter( "oauth_signature_method" )
+                       : NULL;
+
+               if ( !$signature_method ) {
+                       // According to chapter 7 ( "Accessing Protected 
Ressources" ) the signature-method
+                       // parameter is required, and we can't just fallback to 
PLAINTEXT
+                       throw new OAuthException( 'No signature method 
parameter. This parameter is required' );
+               }
+
+               if ( !in_array( $signature_method, array_keys( 
$this->signature_methods ) ) ) {
+                       throw new OAuthException(
+                               "Signature method '$signature_method' not 
supported " .
+                               "try one of the following: " .
+                               implode( ", ", array_keys( 
$this->signature_methods ) )
+                        );
+               }
+               return $this->signature_methods[$signature_method];
+       }
+
+       /**
+        * try to find the consumer for the provided request's consumer key
+        */
+       protected function get_consumer( $request ) {
+               $consumer_key = $request instanceof OAuthRequest
+                               ? $request->get_parameter( "oauth_consumer_key" 
)
+                               : NULL;
+
+               if ( !$consumer_key ) {
+                       throw new OAuthException( "Invalid consumer key" );
+               }
+               wfDebugLog( 'OAuth', __METHOD__ . ": getting consumer for 
'$consumer_key'" );
+               $consumer = $this->data_store->lookup_consumer( $consumer_key );
+               if ( !$consumer ) {
+                       throw new OAuthException( "Invalid consumer" );
+               }
+
+               return $consumer;
+       }
+
+       /**
+        * try to find the token for the provided request's token key
+        */
+       protected function get_token( $request, $consumer, $token_type = 
"access" ) {
+               $token_field = $request instanceof OAuthRequest
+                                ? $request->get_parameter( 'oauth_token' )
+                                : NULL;
+
+               $token = $this->data_store->lookup_token(
+                       $consumer, $token_type, $token_field
+               );
+               if ( !$token ) {
+                       throw new OAuthException( "Invalid $token_type token: 
$token_field" );
+               }
+               return $token;
+       }
+
+       /**
+        * all-in-one function to check the signature on a request
+        * should guess the signature method appropriately
+        */
+       protected function check_signature( $request, $consumer, $token ) {
+               // this should probably be in a different method
+               $timestamp = $request instanceof OAuthRequest
+                       ? $request->get_parameter( 'oauth_timestamp' )
+                       : NULL;
+               $nonce = $request instanceof OAuthRequest
+                       ? $request->get_parameter( 'oauth_nonce' )
+                       : NULL;
+
+               $this->check_timestamp( $timestamp );
+               $this->check_nonce( $consumer, $token, $nonce, $timestamp );
+
+               $signature_method = $this->get_signature_method( $request );
+               $signature = $request->get_parameter( 'oauth_signature' );
+               $valid_sig = $signature_method->check_signature(
+                       $request,
+                       $consumer,
+                       $token,
+                       $signature
+                );
+
+               if ( !$valid_sig ) {
+                       wfDebugLog( 'OAuth', __METHOD__ . ': Signature check (' 
. get_class( $signature_method ) . ') failed' );
+                       throw new OAuthException( "Invalid signature" );
+               }
+       }
+
+       /**
+        * check that the timestamp is new enough
+        */
+       private function check_timestamp( $timestamp ) {
+               if( !$timestamp ) {
+                       throw new OAuthException(
+                               'Missing timestamp parameter. The parameter is 
required'
+                        );
+               }
+
+               // verify that timestamp is recentish
+               $now = time();
+               if ( abs( $now - $timestamp ) > $this->timestamp_threshold ) {
+                       throw new OAuthException(
+                               "Expired timestamp, yours $timestamp, ours $now"
+                        );
+               }
+       }
+
+       /**
+        * check that the nonce is not repeated
+        */
+       private function check_nonce( $consumer, $token, $nonce, $timestamp ) {
+               if( !$nonce ) {
+                       throw new OAuthException(
+                               'Missing nonce parameter. The parameter is 
required'
+                        );
+               }
+
+               // verify that the nonce is uniqueish
+               $found = $this->data_store->lookup_nonce(
+                       $consumer,
+                       $token,
+                       $nonce,
+                       $timestamp
+                );
+               if ( $found ) {
+                       throw new OAuthException( "Nonce already used: $nonce" 
);
+               }
+       }
+
+}
+
+class OAuthDataStore {
+       function lookup_consumer( $consumer_key ) {
+               // implement me
+       }
+
+       function lookup_token( $consumer, $token_type, $token ) {
+               // implement me
+       }
+
+       function lookup_nonce( $consumer, $token, $nonce, $timestamp ) {
+               // implement me
+       }
+
+       function new_request_token( $consumer, $callback = null ) {
+               // return a new token attached to this consumer
+       }
+
+       function new_access_token( $token, $consumer, $verifier = null ) {
+               // return a new access token attached to this consumer
+               // for the user associated with this token if the request token
+               // is authorized
+               // should also invalidate the request token
+       }
+
+}
+
+class OAuthUtil {
+       public static function urlencode_rfc3986( $input ) {
+               if ( is_array( $input ) ) {
+                       return array_map( array( 'OAuthUtil', 
'urlencode_rfc3986' ), $input );
+               } else if ( is_scalar( $input ) ) {
+                       return str_replace(
+                               '+',
+                               ' ',
+                               str_replace( '%7E', '~', rawurlencode( $input ) 
)
+                        );
+               } else {
+                       return '';
+               }
+       }
+
+
+       // This decode function isn't taking into consideration the above
+       // modifications to the encoding process. However, this method doesn't
+       // seem to be used anywhere so leaving it as is.
+       public static function urldecode_rfc3986( $string ) {
+               return urldecode( $string );
+       }
+
+       // Utility function for turning the Authorization: header into
+       // parameters, has to do some unescaping
+       // Can filter out any non-oauth parameters if needed ( default 
behaviour )
+       // May 28th, 2010 - method updated to tjerk.meesters for a speed 
improvement.
+       //                                                                      
see http://code.google.com/p/oauth/issues/detail?id = 163
+       public static function split_header( $header, 
$only_allow_oauth_parameters = true ) {
+               wfDebugLog( 'OAuth', __METHOD__ . ": pulling headers from 
'$header'" );
+               $params = array();
+               if ( preg_match_all( '/(' . ( $only_allow_oauth_parameters ? 
'oauth_' : '' ) . '[a-z_-]*)=(:?"([^"]*)"|([^,]*))/', $header, $matches ) ) {
+                       foreach ( $matches[1] as $i => $h ) {
+                               wfDebugLog( 'OAuth', __METHOD__ . ": '$i' => 
'$h'" );
+                               $params[$h] = OAuthUtil::urldecode_rfc3986( 
empty( $matches[3][$i] ) ? $matches[4][$i] : $matches[3][$i] );
+                       }
+                       if ( isset( $params['realm'] ) ) {
+                               unset( $params['realm'] );
+                       }
+               }
+               return $params;
+       }
+
+       // helper to try to sort out headers for people who aren't running 
apache
+       public static function get_headers() {
+               if ( function_exists( 'apache_request_headers' ) ) {
+                       // we need this to get the actual Authorization: header
+                       // because apache tends to tell us it doesn't exist
+                       $headers = apache_request_headers();
+
+                       // sanitize the output of apache_request_headers because
+                       // we always want the keys to be Cased-Like-This and 
arh()
+                       // returns the headers in the same case as they are in 
the
+                       // request
+                       $out = array();
+                       foreach ( $headers AS $key => $value ) {
+                               $key = str_replace(
+                                       " ",
+                                       "-",
+                                       ucwords( strtolower( str_replace( "-", 
" ", $key ) ) )
+                                );
+                               $out[$key] = $value;
+                       }
+               } else {
+                       // otherwise we don't have apache and are just going to 
have to hope
+                       // that $_SERVER actually contains what we need
+                       $out = array();
+                       if( isset( $_SERVER['CONTENT_TYPE'] ) )
+                               $out['Content-Type'] = $_SERVER['CONTENT_TYPE'];
+                       if( isset( $_ENV['CONTENT_TYPE'] ) )
+                               $out['Content-Type'] = $_ENV['CONTENT_TYPE'];
+
+                       foreach ( $_SERVER as $key => $value ) {
+                               if ( substr( $key, 0, 5 ) == "HTTP_" ) {
+                                       // this is chaos, basically it is just 
there to capitalize the first
+                                       // letter of every word that is not an 
initial HTTP and strip HTTP
+                                       // code from przemek
+                                       $key = str_replace(
+                                               " ",
+                                               "-",
+                                               ucwords( strtolower( 
str_replace( "_", " ", substr( $key, 5 ) ) ) )
+                                        );
+                                       $out[$key] = $value;
+                               }
+                       }
+               }
+               return $out;
+       }
+
+       // This function takes a input like a=b&a=c&d=e and returns the parsed
+       // parameters like this
+       // array( 'a' => array( 'b','c' ), 'd' => 'e' )
+       public static function parse_parameters( $input ) {
+               if ( !isset( $input ) || !$input ) return array();
+
+               $pairs = explode( '&', $input );
+
+               $parsed_parameters = array();
+               foreach ( $pairs as $pair ) {
+                       $split = explode( '=', $pair, 2 );
+                       $parameter = OAuthUtil::urldecode_rfc3986( $split[0] );
+                       $value = isset( $split[1] ) ? 
OAuthUtil::urldecode_rfc3986( $split[1] ) : '';
+
+                       if ( isset( $parsed_parameters[$parameter] ) ) {
+                               // We have already recieved parameter( s ) with 
this name, so add to the list
+                               // of parameters with this name
+
+                               if ( is_scalar( $parsed_parameters[$parameter] 
) ) {
+                                       // This is the first duplicate, so 
transform scalar ( string ) into an array
+                                       // so we can add the duplicates
+                                       $parsed_parameters[$parameter] = array( 
$parsed_parameters[$parameter] );
+                               }
+
+                               $parsed_parameters[$parameter][] = $value;
+                       } else {
+                               $parsed_parameters[$parameter] = $value;
+                       }
+               }
+               return $parsed_parameters;
+       }
+
+       public static function build_http_query( $params ) {
+               wfDebugLog( 'OAuth', __METHOD__ . " called with params:\n" . 
print_r( $params, true ) );
+               if ( !$params ) return '';
+
+               // Urlencode both keys and values
+               $keys = OAuthUtil::urlencode_rfc3986( array_keys( $params ) );
+               $values = OAuthUtil::urlencode_rfc3986( array_values( $params ) 
);
+               $params = array_combine( $keys, $values );
+
+               // Parameters are sorted by name, using lexicographical byte 
value ordering.
+               // Ref: Spec: 9.1.1 ( 1 )
+               uksort( $params, 'strcmp' );
+
+               $pairs = array();
+               foreach ( $params as $parameter => $value ) {
+                       if ( is_array( $value ) ) {
+                               // If two or more parameters share the same 
name, they are sorted by their value
+                               // Ref: Spec: 9.1.1 ( 1 )
+                               // June 12th, 2010 - changed to sort because of 
issue 164 by hidetaka
+                               sort( $value, SORT_STRING );
+                               foreach ( $value as $duplicate_value ) {
+                                       $pairs[] = $parameter . '=' . 
$duplicate_value;
+                               }
+                       } else {
+                               $pairs[] = $parameter . '=' . $value;
+                       }
+               }
+               // For each parameter, the name is separated from the 
corresponding value by an ' = ' character ( ASCII code 61 )
+               // Each name-value pair is separated by an '&' character ( 
ASCII code 38 )
+               return implode( '&', $pairs );
+       }
+}
+
+?>
diff --git a/specials/SpecialOAuthLogin.php b/specials/SpecialOAuthLogin.php
new file mode 100644
index 0000000..c427e7a
--- /dev/null
+++ b/specials/SpecialOAuthLogin.php
@@ -0,0 +1,106 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class SpecialOAuthLogin extends \UnlistedSpecialPage {
+
+       function __construct() {
+               parent::__construct( 'OAuthLogin' );
+       }
+
+
+       public function execute( $subpage ) {
+               global $wgUser;
+               $request = $this->getRequest();
+
+               $this->setHeaders();
+
+               if ( !$this->getUser()->isAnon() ) {
+                       throw new \ErrorPageError( 'oauthauth-error', 
'oauthauth-already-logged-in' );
+               }
+
+               $handler = false;
+               $session = new PhpSessionStore( $request );
+
+               list( $config, $cmrToken ) = Config::getDefaultConfigAndToken();
+               $client = new \MWOAuthClient( $config, $cmrToken );
+               $handler = new OAuth1Handler();
+
+               switch ( trim( $subpage ) ) {
+                       case 'init':
+
+                               // Keep around returnto/returntoquery and set 
with PostLoginRedirect hook
+                               $session->set(
+                                       'oauth-init-returnto',
+                                       $request->getVal( 'returnto', 
'Main_Page' )
+                               );
+                               $session->set(
+                                       'oauth-init-returntoquery',
+                                       $request->getVal( 'returntoquery' )
+                               );
+
+                               try {
+                                       $redir = $handler->init(
+                                               $session,
+                                               $client
+                                       );
+
+                                       $handler->authorize( 
$this->getRequest()->response(), $redir );
+
+                               } catch ( Exception $e ) {
+                                       throw new \ErrorPageError( 
'oauthauth-error', $e->getMessage() );
+                               }
+                               if ( !$status->isGood() ) {
+                                       throw new \ErrorPageError( 
'oauthauth-error', $status->getMessage() );
+                               }
+
+                               break;
+                       case 'finish':
+                               try {
+                                       $accessToken = $handler->finish(
+                                               $this->getRequest(),
+                                               $session,
+                                               $client
+                                       );
+
+                                       $identity = $handler->identify(
+                                               $accessToken,
+                                               $client
+                                       );
+
+                                       if ( !Policy::checkWhitelists( 
$identity ) ) {
+                                               throw new \ErrorPageError( 
'oauthauth-error', 'oauthauth-nologin-policy' );
+                                       }
+
+                                       $status = 
AuthenticationHandler::handleIdentity(
+                                               $this->getRequest(),
+                                               $identity,
+                                               $accessToken
+                                       );
+
+                                       if ( !$status->isGood() ) {
+                                               throw new \ErrorPageError( 
'oauthauth-error', $status->getMessage() );
+                                       }
+                               } catch ( \ErrorPageError $epe ) {
+                                       throw $epe;
+                               } catch ( Exception $e ) {
+                                       throw new \ErrorPageError( 
'oauthauth-error', $e->getMessage() );
+                               }
+
+                               list( $method, $u ) = $status->getValue();
+
+                               $this->getContext()->setUser( $u );
+                               $wgUser = $u;
+
+                               $lp = new \LoginForm();
+
+                               // Call LoginForm::successfulCreation() on 
create, or successfulLogin()
+                               $lp->$method();
+                                               break;
+                       default:
+                               throw new \ErrorPageError( 'oauthauth-error', 
'oauthauth-invalid-subpage' );
+               }
+
+       }
+
+}
diff --git a/store/PhpSessionStore.php b/store/PhpSessionStore.php
new file mode 100644
index 0000000..7d2399f
--- /dev/null
+++ b/store/PhpSessionStore.php
@@ -0,0 +1,25 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class PhpSessionStore extends SessionStore {
+
+       private $request;
+
+       public function __construct( \WebRequest $request ) {
+               wfSetupSession();
+               $this->request = $request;
+       }
+
+       public function get( $key ) {
+               return $this->request->getSessionData( $key );
+       }
+
+       public function set( $key, $value ) {
+               $this->request->setSessionData( $key, $value );
+       }
+
+       public function delete( $key ) {
+               $this->request->setSessionData( $key, null );
+       }
+}
diff --git a/store/SessionStore.php b/store/SessionStore.php
new file mode 100644
index 0000000..72f1a80
--- /dev/null
+++ b/store/SessionStore.php
@@ -0,0 +1,12 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+abstract class SessionStore {
+
+       abstract public function get( $key );
+
+       abstract public function set( $key, $value );
+
+       abstract public function delete( $key );
+}
diff --git a/store/oauthauth.sql b/store/oauthauth.sql
new file mode 100644
index 0000000..56fc6f5
--- /dev/null
+++ b/store/oauthauth.sql
@@ -0,0 +1,11 @@
+CREATE TABLE /*_*/oauthauth_user (
+  `oaau_rid` int(10) unsigned NOT NULL,
+  `oaau_uid` int(10) unsigned NOT NULL PRIMARY KEY,
+  `oaau_username` varchar(255) binary not null,
+  `oaau_access_token` varchar(127) binary not null default '',
+  `oaau_access_secret` varchar(127) binary not null default '',
+  `oaau_identify_timestamp` binary(14) not null default '',
+) /*$wgDBTableOptions*/;
+
+CREATE UNIQUE INDEX /*i*/idx_rid ON /*_*/oauthauth_user (`oaau_rid`);
+
diff --git a/tests/OAuthAuthConfigTest.php b/tests/OAuthAuthConfigTest.php
new file mode 100644
index 0000000..07a87b2
--- /dev/null
+++ b/tests/OAuthAuthConfigTest.php
@@ -0,0 +1,13 @@
+<?php
+/**
+ * @group OAuthAuthentication
+ */
+class OAuthAuthConfigTest extends OAuthAuthDBTest {
+
+       public function testGetDefaultConfigAndToken() {
+               list( $config, $token ) = 
\MediaWiki\Extensions\OAuthAuthentication\Config::getDefaultConfigAndToken();
+               $this->assertInstanceOf( 'MWOAuthClientConfig', $config );
+               $this->assertInstanceOf( 'OAuthToken', $token );
+       }
+
+}
diff --git a/tests/OAuthAuthDBTest.php b/tests/OAuthAuthDBTest.php
new file mode 100644
index 0000000..8080ceb
--- /dev/null
+++ b/tests/OAuthAuthDBTest.php
@@ -0,0 +1,43 @@
+<?php
+/**
+ * @group OAuthAuthentication
+ */
+class OAuthAuthDBTest extends MediaWikiTestCase {
+
+       public function __construct( $name = null, array $data = array(), 
$dataName = '' ) {
+               parent::__construct( $name, $data, $dataName );
+       }
+
+       protected function setUp() {
+               parent::setUp();
+               if ( $this->db->tableExists( 'oauthauth_user' ) ) {
+                       $this->db->dropTable( 'oauthauth_user' );
+               }
+               $this->db->sourceFile( __DIR__ . '/../store/oauthauth.sql' );
+
+               // TODO: Setup some test data
+               $user = User::newFromName( 'OAuthUser' );
+               if ( $user->idForName() == 0 ) {
+                       $user->addToDatabase();
+                       $user->setPassword( 'OAUP@ssword' );
+                       $user->saveSettings();
+               }
+               $exUser = new 
\MediaWiki\Extensions\OAuthAuthentication\OAuthExternalUser( 100, 
$user->getId(), 'OAuthUser' );
+               $exUser->addToDatabase( $this->db );
+       }
+
+       protected function tearDown() {
+               $this->db->dropTable( 'oauthauth_user' );
+               parent::tearDown();
+       }
+
+       public function needsDB() {
+               return true;
+       }
+
+       // Stub to make sure db handling is working
+       public function testInit() {
+               $this->assertSame( true, true );
+       }
+
+}
diff --git a/tests/OAuthAuthExternalUserTest.php 
b/tests/OAuthAuthExternalUserTest.php
new file mode 100644
index 0000000..d739b28
--- /dev/null
+++ b/tests/OAuthAuthExternalUserTest.php
@@ -0,0 +1,20 @@
+<?php
+/**
+ * @group OAuthAuthentication
+ */
+class OAuthAuthExternalUserTest extends OAuthAuthDBTest {
+
+       public function testExternalUser() {
+               $exUser = new 
\MediaWiki\Extensions\OAuthAuthentication\OAuthExternalUser( 20, 30, 'ExUser' );
+               $this->assertEquals( 'ExUser', $exUser->getName() );
+               $this->assertEquals( 30, $exUser->getLocalId() );
+       }
+
+       public function testNewFromRemoteId() {
+               // We added remoteId 120 in parent class
+               $exUser = 
\MediaWiki\Extensions\OAuthAuthentication\OAuthExternalUser::newFromRemoteId( 
120, 'OAuthUser', $this->db );
+               $this->assertInstanceOf( 
'MediaWiki\Extensions\OAuthAuthentication\OAuthExternalUser', $exUser );
+               $this->assertEquals( 'OAuthUser', $exUser->getName() );
+       }
+
+}
diff --git a/tests/OAuthAuthHooksTest.php b/tests/OAuthAuthHooksTest.php
new file mode 100644
index 0000000..b3b8735
--- /dev/null
+++ b/tests/OAuthAuthHooksTest.php
@@ -0,0 +1,24 @@
+<?php
+/**
+ * @group OAuthAuthentication
+ */
+class OAuthAuthHooksTest extends OAuthAuthDBTest {
+
+       public function testOnPersonalUrls() {
+
+               $this->setMwGlobals( array(
+                       'wgUser' => \User::newFromName( '127.0.0.1', false ),
+               ) );
+
+               $personal_urls = array( 'login' => array( 'href' => 'fail' ) );
+               $title = new Title();
+
+               
\MediaWiki\Extensions\OAuthAuthentication\Hooks::onPersonalUrls( 
$personal_urls, $title );
+
+               $this->assertSame(
+                       true,
+                       strpos( $personal_urls['login']['href'], 
'Special:OAuthLogin/init' ) !== false
+               );
+       }
+
+}
diff --git a/utils/Config.php b/utils/Config.php
new file mode 100644
index 0000000..bb415ae
--- /dev/null
+++ b/utils/Config.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class Config {
+
+       public static function getDefaultConfigAndToken() {
+               global $wgOAuthAuthenticationConsumerKey,
+                       $wgOAuthAuthenticationConsumerSecret,
+                       $wgOAuthAuthenticationUrl,
+                       $wgOAuthAuthenticationCanonicalUrl,
+                       $wgOAuthAuthenticationValidateSSL;
+
+               $validateSSL = false;
+               $useSSL = false;
+
+               if ( preg_match( '!^https://!i', $wgOAuthAuthenticationUrl ) ) {
+                       $validateSSL = $wgOAuthAuthenticationValidateSSL;
+                       $useSSL = true;
+               }
+
+               $config = new \MWOAuthClientConfig(
+                       $wgOAuthAuthenticationUrl, // url to use
+                       $useSSL, // do we use SSL? (we should probably detect 
that from the url)
+                       $validateSSL // do we validate the SSL certificate? 
Always use 'true' in production.
+               );
+
+               if ( $wgOAuthAuthenticationCanonicalUrl ) {
+                       $config->canonicalServerUrl = 
$wgOAuthAuthenticationCanonicalUrl;
+               }
+               // Optional clean url here (i.e., to work with mobile), 
otherwise the
+               // base url just has /authorize& added
+               #$config->redirURL = 
'http://en.wikipedia.beta.wmflabs.org/wiki/Special:OAuth/authorize?';
+
+               $cmrToken = new \OAuthToken( $wgOAuthAuthenticationConsumerKey, 
$wgOAuthAuthenticationConsumerSecret );
+
+               return array( $config, $cmrToken );
+       }
+
+}
diff --git a/utils/Exception.php b/utils/Exception.php
new file mode 100644
index 0000000..0ccfeae
--- /dev/null
+++ b/utils/Exception.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class Exception extends \MWException {
+
+}
diff --git a/utils/Hooks.php b/utils/Hooks.php
new file mode 100644
index 0000000..6df4a81
--- /dev/null
+++ b/utils/Hooks.php
@@ -0,0 +1,156 @@
+<?php
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class Hooks {
+
+       public static function onPersonalUrls( &$personal_urls, &$title ) {
+               global $wgUser, $wgRequest,
+                       $wgOAuthAuthenticationAllowLocalUsers, 
$wgOAuthAuthenticationRemoteName;
+
+               if ( $wgUser->getID() == 0 ) {
+                       $query = array();
+                       $query['returnto'] = $title->getPrefixedText();
+                       $returntoquery = $wgRequest->getValues();
+                       unset( $returntoquery['title'] );
+                       unset( $returntoquery['returnto'] );
+                       unset( $returntoquery['returntoquery'] );
+                       $query['returntoquery'] = wfArrayToCgi( $returntoquery 
);
+                       $personal_urls['login']['href'] = 
\SpecialPage::getTitleFor( 'OAuthLogin', 'init' )->getFullURL( $query );
+                       if ( $wgOAuthAuthenticationRemoteName ) {
+                               $personal_urls['login']['text'] = wfMessage( 
'oauthauth-login',
+                                       $wgOAuthAuthenticationRemoteName 
)->text();
+                       }
+
+                       if ( $wgOAuthAuthenticationAllowLocalUsers === false ) {
+                               unset( $personal_urls['createaccount'] );
+                       }
+               }
+               return true;
+       }
+
+       public static function onPostLoginRedirect( &$returnTo, 
&$returnToQuery, &$type ) {
+               global $wgRequest;
+               $session = new PhpSessionStore( $wgRequest );
+
+               $title = $session->get( 'oauth-init-returnto' );
+               $query = $session->get( 'oauth-init-returntoquery' );
+
+               if ( $title ) {
+                       $returnTo = $title;
+               }
+
+               if ( $query ) {
+                       $returnToQuery = $query;
+               }
+       }
+
+       public static function onLoadExtensionSchemaUpdates( $updater = null ) {
+               $updater->addExtensionTable( 'oauthauth_user', __DIR__ . 
'../store/oauthauth.sql' );
+       }
+
+       public static function onGetPreferences( $user, &$preferences ) {
+               global $wgRequirePasswordforEmailChange;
+
+               $resetlink = \Linker::link(
+                       \SpecialPage::getTitleFor( 'PasswordReset' ),
+                       wfMessage( 'passwordreset' )->escaped(),
+                       array(),
+                       array( 'returnto' => \SpecialPage::getTitleFor( 
'Preferences' ) )
+               );
+
+               if ( empty( $user->mPassword ) && empty( $user->mNewpassword ) 
) {
+
+                       if ( $user->isEmailConfirmed() ) {
+                               $preferences['password'] = array(
+                                       'section' => 'personal/info',
+                                       'type' => 'info',
+                                       'raw' => true,
+                                       'default' => $resetlink,
+                                       'label-message' => 'yourpassword',
+                               );
+                       } else {
+                               unset( $preferences['password'] );
+                       }
+
+                       if ( $wgRequirePasswordforEmailChange ) {
+                               $preferences['emailaddress'] = array(
+                                       'type' => 'info',
+                                       'raw' => 1,
+                                       'default' => wfMessage( 
'oauthauth-set-email' )->escaped(),
+                                       'section' => 'personal/email',
+                                       'label-message' => 'youremail',
+                                       'cssclass' => 'mw-email-none',
+                               );
+                       }
+
+               } else {
+                       $preferences['resetpassword'] = array(
+                               'section' => 'personal/info',
+                               'type' => 'info',
+                               'raw' => true,
+                               'default' => $resetlink,
+                               'label-message' => null,
+                       );
+               }
+       }
+
+       /**
+        * Check that the identity complies with the site policy
+        *
+        */
+       public static function onUserLoadAfterLoadFromSession( $user ) {
+               global $wgOAuthAuthenticationMaxIdentityAge;
+
+               if ( Policy::policyToEnforce() ) {
+                       if ( !isset( $user->extAuthObj ) ) {
+                               $user->extAuthObj = 
OAuthExternalUser::newFromUser( $user, wfGetDB( DB_MASTER ) );
+                       }
+
+                       if ( $user->extAuthObj ) {
+                               $lastVerify = new \MWTimestamp( 
$user->extAuthObj->getIdentifyTS() );
+                               $minVerify = new \MWTimestamp( time() - 
$wgOAuthAuthenticationMaxIdentityAge );
+
+                               if ( $lastVerify->getTimestamp() <= 
$minVerify->getTimestamp() ) {
+                                       list( $config, $cmrToken ) = 
Config::getDefaultConfigAndToken();
+                                       $client = new \MWOAuthClient( $config, 
$cmrToken );
+                                       $handler = new OAuth1Handler();
+                                       $identity = $handler->identify( 
$user->extAuthObj->getAccessToken(), $client );
+                                       $user->extAuthObj->setIdentifyTS( new 
\MWTimestamp() );
+                                       $user->extAuthObj->updateInDatabase( 
wfGetDB( DB_MASTER ) );
+                                       if ( !Policy::checkWhitelists( 
$identity ) ) {
+                                               $user->logout();
+                                               throw new \ErrorPageError( 
'oauthauth-error', 'oauthauth-loggout-policy' );
+                                       }
+                               }
+                       }
+               }
+
+               return true;
+       }
+
+       /**
+        * @param $user User
+        * @param $abortError
+        * @return bool
+        */
+       static function onAbortNewAccount( $user, &$abortError ) {
+               global $wgOAuthAuthenticationAllowLocalUsers, $wgRequest;
+
+               if ( $wgOAuthAuthenticationAllowLocalUsers === false ) {
+                       $query = array();
+                       $query['returnto'] = $wgRequest->getVal( 'returnto' );
+                       $query['returntoquery'] = $wgRequest->getVal( 
'returntoquery' );
+                       $loginTitle = \SpecialPage::getTitleFor( 'OAuthLogin', 
'init' );
+                       $loginlink = \Linker::Link(
+                               $loginTitle,
+                               wfMessage( 'login' )->escaped(),
+                               array(),
+                               $query
+                       );
+                       $msg = wfMessage( 'oauthauth-localuser-not-allowed' 
)->rawParams( $loginlink );
+                       $abortError = $msg->escaped();
+                       return false;
+               }
+       }
+
+}
diff --git a/utils/OAuthExternalUser.php b/utils/OAuthExternalUser.php
new file mode 100644
index 0000000..2b9d21e
--- /dev/null
+++ b/utils/OAuthExternalUser.php
@@ -0,0 +1,174 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class OAuthExternalUser {
+
+       // Local user_id
+       private $userId;
+
+       // Remote Username
+       private $username;
+
+       // Remote unique id
+       private $remoteId;
+
+       // OAuth Access Token
+       private $accessToken = null;
+
+       // Timestamp of last identity validation
+       private $identifyTS = null;
+
+       public function __construct( $rid, $uid, $name, $accessKey = '', 
$accessSecret = '', $idts = null ) {
+               $this->remoteId = $rid;
+               $this->userId = $uid; // OIDC specifies this is unique for the 
IdP
+               $this->username = $name;
+
+               if ( $accessKey && $accessSecret ) {
+                       $this->accessToken = new \OAuthToken( $accessKey, 
$accessSecret );
+               }
+
+               $this->identifyTS = $idts;
+       }
+
+       public static function newFromRemoteId( $rid, $username, \DatabaseBase 
$db ) {
+               $row = $db->selectRow(
+                       'oauthauth_user',
+                       array( 'oaau_rid', 'oaau_uid', 'oaau_username', 
'oaau_access_token',
+                               'oaau_access_secret', 'oaau_identify_timestamp' 
),
+                       array( 'oaau_rid' => $rid ),
+                       __METHOD__
+               );
+
+               if ( !$row ) {
+                       return new self( $rid, 0, $username );
+               } else {
+                       return new self( $rid, $row->oaau_uid, 
$row->oaau_username,
+                               $row->oaau_access_token, 
$row->oaau_access_secret,
+                               $row->oaau_identify_timestamp );
+               }
+       }
+
+       public static function newFromUser( \User $user, \DatabaseBase $db ) {
+               $row = $db->selectRow(
+                       'oauthauth_user',
+                       array( 'oaau_rid', 'oaau_uid', 'oaau_username', 
'oaau_access_token',
+                               'oaau_access_secret', 'oaau_identify_timestamp' 
),
+                       array( 'oaau_username' => $user->getName() ),
+                       __METHOD__
+               );
+
+               if ( !$row ) {
+                       return false;
+               } else {
+                       return new self( $row->oaau_rid, $row->oaau_uid, 
$row->oaau_username,
+                               $row->oaau_access_token, 
$row->oaau_access_secret,
+                               $row->oaau_identify_timestamp );
+               }
+       }
+
+       public function addToDatabase( \DatabaseBase $db ) {
+               $row = array(
+                       'oaau_rid' => $this->remoteId,
+                       'oaau_uid' => $this->userId,
+                       'oaau_username' => $this->username,
+               );
+
+               if ( $this->accessToken ) {
+                       $row += array(
+                               'oaau_access_token' => $this->accessToken->key,
+                               'oaau_access_secret' => 
$this->accessToken->secret,
+                       );
+               }
+
+               if ( $this->identifyTS ) {
+                       $row += array(
+                               'oaau_identify_timestamp' => 
$db->timestampOrNull( (string)$this->identifyTS ),
+                       );
+               }
+
+               $db->insert(
+                       'oauthauth_user',
+                       $row,
+                       __METHOD__
+               );
+       }
+
+       public function updateInDatabase( \DatabaseBase $db ) {
+               if ( !$this->userId > 0 ) {
+                       throw new Exception( 'Error updating External User that 
isn\'t in the DB' );
+               }
+               $row = array(
+                       'oaau_rid' => $this->remoteId,
+                       'oaau_username' => $this->username,
+               );
+
+               if ( $this->accessToken ) {
+                       $row += array(
+                               'oaau_access_token' => $this->accessToken->key,
+                               'oaau_access_secret' => 
$this->accessToken->secret,
+                       );
+               }
+
+               if ( $this->identifyTS ) {
+                       $row += array(
+                               'oaau_identify_timestamp' => 
$db->timestampOrNull( (string)$this->identifyTS ),
+                       );
+               }
+
+               $db->update(
+                       'oauthauth_user',
+                       /* SET */ $row,
+                       /* WHERE */ array( 'oaau_uid' => $this->userId ),
+                       __METHOD__
+               );
+
+       }
+
+       public function removeAccessTokens( \DatabaseBase $db ) {
+               if ( !$this->userId > 0 ) {
+                       throw new Exception( 'Error updating External User that 
isn\'t in the DB' );
+               }
+               $db->update(
+                       'oauthauth_user',
+                       array(
+                               'oaau_access_token' => '',
+                               'oaau_access_secret' => '',
+                       ),
+                       array( 'oaau_uid' => $this->userId ),
+                       __METHOD__
+               );
+       }
+
+       public function getName() {
+               return $this->username;
+       }
+
+       public function getLocalId() {
+               return $this->userId;
+       }
+
+       public function setLocalId( $uid ) {
+               $this->userId = $uid;
+       }
+
+       public function attached() {
+               return ( $this->userId !== 0 );
+       }
+
+       public function setAccessToken( \OAuthToken $accessToken ) {
+               $this->accessToken = $accessToken;
+       }
+
+       public function getAccessToken() {
+               return $this->accessToken;
+       }
+
+       public function setIdentifyTS( \MWTimestamp $ts ) {
+               $this->identifyTS = $ts;
+       }
+
+       public function getIdentifyTS() {
+               return $this->identifyTS;
+       }
+}
diff --git a/utils/Policy.php b/utils/Policy.php
new file mode 100644
index 0000000..ef085d1
--- /dev/null
+++ b/utils/Policy.php
@@ -0,0 +1,37 @@
+<?php
+
+namespace MediaWiki\Extensions\OAuthAuthentication;
+
+class Policy {
+
+       public static function policyToEnforce() {
+               global $wgOAuthAuthenticationUsernameWhitelist,
+                       $wgOAuthAuthenticationGroupWhitelist;
+
+               return ( $wgOAuthAuthenticationUsernameWhitelist !== false
+                       || $wgOAuthAuthenticationGroupWhitelist !== false
+               );
+       }
+
+       /**
+        * @param $identity jwt identity object
+        * @return bool true if the user should be allowed according to 
whitelists. False otherwise.
+        */
+       public static function checkWhitelists( $identity ) {
+               global $wgOAuthAuthenticationUsernameWhitelist,
+                       $wgOAuthAuthenticationGroupWhitelist;
+
+               return self::checkUserWhitelist( $identity->username, 
$wgOAuthAuthenticationUsernameWhitelist )
+                       && self::checkGroupWhitelist( $identity->groups, 
$wgOAuthAuthenticationGroupWhitelist );
+       }
+
+
+       private static function checkUserWhitelist( $username, $whitelist ) {
+               return !$whitelist || in_array( $username, $whitelist );
+       }
+
+       private static function checkGroupWhitelist( $groups, $whitelist ) {
+               return !$whitelist || count( array_intersect( $groups, 
$whitelist ) ) > 0;
+       }
+
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/173303
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: merged
Gerrit-Change-Id: I8a51690828ab09c7e49fa53a8c46497c861f58e3
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/OAuthAuthentication
Gerrit-Branch: master
Gerrit-Owner: CSteipp <[email protected]>
Gerrit-Reviewer: CSteipp <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: Springle <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to