Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15280324
D9202.id21848.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
4 KB
Referenced Files
None
Subscribers
None
D9202.id21848.diff
View Options
diff --git a/src/auth/PhutilAuthAdapterOAuthMediaWiki.php b/src/auth/PhutilAuthAdapterOAuthMediaWiki.php
new file mode 100644
--- /dev/null
+++ b/src/auth/PhutilAuthAdapterOAuthMediaWiki.php
@@ -0,0 +1,151 @@
+<?php
+
+/**
+ * Authentication adapter for MediaWiki OAuth1.
+ */
+final class PhutilAuthAdapterOAuthMediaWiki extends PhutilAuthAdapterOAuth1 {
+
+ private $userinfo;
+
+ public function getAccountID() {
+ $this->getHandshakeData();
+ return idx($this->getUserInfo(), 'userid');
+ }
+
+ public function getAccountName() {
+ return idx($this->getUserInfo(), 'username');
+ }
+
+ public function getAccountURI() {
+ $name = $this->getAccountName();
+ if (strlen($name)) {
+ return 'http://en.wikipedia.beta.wmflabs.org/w/index.php?title=User:'.$name;
+ }
+ return null;
+ }
+
+ public function getCallbackURI() {
+ return 'oob';
+ }
+
+ public function getAccountImageURI() {
+ $info = $this->getUserInfo();
+ return idx($info, 'profile_image_url');
+ }
+
+ public function getAccountRealName() {
+ $info = $this->getUserInfo();
+ return idx($info, 'name');
+ }
+
+ public function getAdapterType() {
+ return 'mediawiki';
+ }
+
+ public function getAdapterDomain() {
+ return 'mediawiki.org';
+ }
+
+ protected function getRequestTokenURI() {
+ return 'http://en.wikipedia.beta.wmflabs.org/w/index.php?title=Special:OAuth/initiate';
+ }
+
+ protected function getAuthorizeTokenURI() {
+ return 'http://en.wikipedia.beta.wmflabs.org/w/index.php?title=Special:OAuth/authorize';
+ }
+
+ public function getClientRedirectURI() {
+ $p = parent::getClientRedirectURI();
+ $token = $this->getToken();
+ $token_secret = $this->getTokenSecret();
+
+ // MediaWiki requires the call to /authorize be signed with the temp
+ // secret. This also forces the connected app to prevent CSRF.
+ setcookie('mwoauth', "$token:$token_secret");
+ return $p . "&oauth_consumer_key={$this->getConsumerKey()}";
+ }
+
+ protected function getValidateTokenURI() {
+ return 'http://en.wikipedia.beta.wmflabs.org/w/index.php?title=Special:OAuth/token';
+ }
+
+
+ private function getUserInfo() {
+ if ($this->userinfo === null) {
+ $uri = new PhutilURI('http://en.wikipedia.beta.wmflabs.org/w/index.php?title=Special:OAuth/identify&format=json');
+ $nonce = Filesystem::readRandomCharacters(32); // We gen this so we can check for replay below
+ list($body) = $this->newOAuth1Future($uri)
+ ->setMethod('GET')
+ ->setNonce($nonce)
+ ->resolvex();
+ $this->userinfo = $this->decodeAndVerifyJWT($body, $nonce);
+ }
+ return $this->userinfo;
+ }
+
+ /**
+ * MediaWiki uses a signed JWT to assert the user's identity
+ */
+ private function decodeAndVerifyJWT($jwt, $nonce) {
+ $userinfo = array();
+ $identity = $this->decodeJWT($jwt);
+ $expected_connonical_server = 'http://en.wikipedia.beta.wmflabs.org';
+ $now = time();
+
+ if ($identity->iss !== $expected_connonical_server) {
+ throw new Exception("OAuth JWT iss didn't match expected server name");
+ }
+ if ($identity->aud !== $this->getConsumerKey()) {
+ throw new Exception("OAuth JWT aud didn't match expected consumer key");
+ }
+ if ($identity->iat > $now || $identity->exp < $now) {
+ throw new Exception("OAuth JWT wasn't valid at this time");
+ }
+ if ($identity->nonce !== $nonce) {
+ throw new Exception("OAuth JWT nonce didn't match what we sent. MITM?");
+ }
+ $userinfo['userid'] = $identity->sub;
+ $userinfo['username'] = $identity->username;
+ $userinfo['groups'] = $identity->groups;
+ $userinfo['blocked'] = $identity->blocked;
+ $userinfo['editcount'] = $identity->editcount;
+ return $userinfo;
+ }
+
+ private function decodeJWT($jwt) {
+ list($headb64, $bodyb64, $sigb64) = explode('.', $jwt);
+
+ $header = json_decode($this->urlsafeB64Decode($headb64));
+ $payload = json_decode($this->urlsafeB64Decode($bodyb64));
+ $sig = $this->urlsafeB64Decode($sigb64);
+
+ $expect_sig = hash_hmac(
+ 'sha256',
+ "$headb64.$bodyb64",
+ $this->getConsumerSecret()->openEnvelope(),
+ true);
+
+ if ($header->alg !== 'HS256' || !$this->compareHash($sig, $expect_sig)) {
+ throw new Exception('Invalid JWT signature from /identify.');
+ }
+ return $payload;
+ }
+
+ private function urlsafeB64Decode($input) {
+ $remainder = strlen($input) % 4;
+ if ($remainder) {
+ $padlen = 4 - $remainder;
+ $input .= str_repeat('=', $padlen);
+ }
+ return base64_decode(strtr($input, '-_', '+/'));
+ }
+
+ 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;
+ }
+}
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Tue, Mar 4, 9:52 AM (12 h, 25 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7221364
Default Alt Text
D9202.id21848.diff (4 KB)
Attached To
Mode
D9202: MediaWiki oauth1 adaptor for phabricator
Attached
Detach File
Event Timeline
Log In to Comment