From 5044fd7dcb3811cf4acc3540a059eb498c5517ba Mon Sep 17 00:00:00 2001 From: Xymph Date: Sat, 19 Jun 2021 15:50:47 +0200 Subject: [PATCH 1/6] Add new Wikimate method: token --- Wikimate.php | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/Wikimate.php b/Wikimate.php index 8347b58..c281d3c 100644 --- a/Wikimate.php +++ b/Wikimate.php @@ -19,6 +19,9 @@ class Wikimate */ const VERSION = '0.12.0'; + const TOKEN_DEFAULT = 'csrf'; + const TOKEN_LOGIN = 'login'; + protected $api; protected $username; protected $password; @@ -58,6 +61,59 @@ protected function initRequests() $this->session->useragent = $this->useragent; } + /** + * Obtains a wiki token for logging in or data-modifying actions. + * + * @param string $type The token type + * @return string The requested token + */ + protected function token($type = self::TOKEN_DEFAULT) + { + // Check for supported token types + if ($type != self::TOKEN_DEFAULT && $type != self::TOKEN_LOGIN) { + $this->error = array(); + $this->error['login'] = 'The API does not support the token type'; + return false; + } + + $details = array( + 'action' => 'query', + 'meta' => 'tokens', + 'type' => $type, + 'format' => 'json' + ); + + // Send the token request + $response = $this->session->post($this->api, array(), $details); + // Check if we got an API result or the API doc page (invalid request) + if (strpos($response->body, "This is an auto-generated MediaWiki API documentation page") !== false) { + $this->error = array(); + $this->error['login'] = 'The API could not understand the token request'; + return false; + } + + $tokenResult = json_decode($response->body); + // Check if we got a JSON result + if ($tokenResult === null) { + $this->error = array(); + $this->error['login'] = 'The API did not return the token response'; + return false; + } + + if ($this->debugMode) { + echo "Token request:\n"; + print_r($details); + echo "Token request response:\n"; + print_r($tokenResult); + } + + if ($type == self::TOKEN_LOGIN) { + return $tokenResult->query->tokens->logintoken; + } else { + return $tokenResult->query->tokens->csrftoken; + } + } + /** * Logs in to the wiki. * From 3ec00fb1defd356ad9cf8a15fb8f8d960ccd36bf Mon Sep 17 00:00:00 2001 From: Xymph Date: Sat, 19 Jun 2021 16:13:29 +0200 Subject: [PATCH 2/6] Apply token method during login --- Wikimate.php | 62 ++++++++++++++++++++++------------------------------ 1 file changed, 26 insertions(+), 36 deletions(-) diff --git a/Wikimate.php b/Wikimate.php index c281d3c..7bf5461 100644 --- a/Wikimate.php +++ b/Wikimate.php @@ -103,7 +103,7 @@ protected function token($type = self::TOKEN_DEFAULT) if ($this->debugMode) { echo "Token request:\n"; print_r($details); - echo "Token request response:\n"; + echo "Token response:\n"; print_r($tokenResult); } @@ -126,10 +126,16 @@ public function login($username, $password, $domain = null) { //Logger::log("Logging in"); + // Obtain login token first + if (($logintoken = $this->token(self::TOKEN_LOGIN)) === false) { + return false; + } + $details = array( 'action' => 'login', 'lgname' => $username, 'lgpassword' => $password, + 'lgtoken' => $logintoken, 'format' => 'json' ); @@ -143,55 +149,39 @@ public function login($username, $password, $domain = null) // Check if we got an API result or the API doc page (invalid request) if (strpos($response->body, "This is an auto-generated MediaWiki API documentation page") !== false) { $this->error = array(); - $this->error['login'] = 'The API could not understand the first login request'; + $this->error['login'] = 'The API could not understand the login request'; return false; } $loginResult = json_decode($response->body); + // Check if we got a JSON result + if ($loginResult === null) { + $this->error = array(); + $this->error['login'] = 'The API did not return the login response'; + return false; + } if ($this->debugMode) { echo "Login request:\n"; print_r($details); - echo "Login request response:\n"; + echo "Login response:\n"; print_r($loginResult); } - if (isset($loginResult->login->result) && $loginResult->login->result == 'NeedToken') { + if (isset($loginResult->login->result) && $loginResult->login->result != 'Success') { //Logger::log("Sending token {$loginResult->login->token}"); - $details['lgtoken'] = strtolower(trim($loginResult->login->token)); - - // Send the confirm token request - $loginResult = $this->session->post($this->api, array(), $details)->body; - - // Check if we got an API result or the API doc page (invalid request) - if (strpos($loginResult, "This is an auto-generated MediaWiki API documentation page") !== false) { - $this->error = array(); - $this->error['login'] = 'The API could not understand the confirm token request'; - return false; - } - - $loginResult = json_decode($loginResult); - if ($this->debugMode) { - echo "Confirm token request:\n"; - print_r($details); - echo "Confirm token response:\n"; - print_r($loginResult); - } - - if ($loginResult->login->result != 'Success') { - // Some more comprehensive error checking - $this->error = array(); - switch ($loginResult->login->result) { - case 'NotExists': - $this->error['login'] = 'The username does not exist'; - break; - default: - $this->error['login'] = 'The API result was: ' . $loginResult->login->result; - break; - } - return false; + // Some more comprehensive error checking + $this->error = array(); + switch ($loginResult->login->result) { + case 'Failed': + $this->error['login'] = 'Incorrect username or password'; + break; + default: + $this->error['login'] = 'The API result was: ' . $loginResult->login->result; + break; } + return false; } //Logger::log("Logged in"); From cc9bc21650bd980f489e6324235a755456f4b3ba Mon Sep 17 00:00:00 2001 From: Xymph Date: Sat, 19 Jun 2021 16:22:07 +0200 Subject: [PATCH 3/6] Remove commented-out, unused logging --- Wikimate.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/Wikimate.php b/Wikimate.php index 7bf5461..45c0fa7 100644 --- a/Wikimate.php +++ b/Wikimate.php @@ -124,8 +124,6 @@ protected function token($type = self::TOKEN_DEFAULT) */ public function login($username, $password, $domain = null) { - //Logger::log("Logging in"); - // Obtain login token first if (($logintoken = $this->token(self::TOKEN_LOGIN)) === false) { return false; @@ -169,8 +167,6 @@ public function login($username, $password, $domain = null) } if (isset($loginResult->login->result) && $loginResult->login->result != 'Success') { - //Logger::log("Sending token {$loginResult->login->token}"); - // Some more comprehensive error checking $this->error = array(); switch ($loginResult->login->result) { @@ -184,7 +180,6 @@ public function login($username, $password, $domain = null) return false; } - //Logger::log("Logged in"); return true; } From 0a00740f2413e6966ade51ec9edc3bac762af4f6 Mon Sep 17 00:00:00 2001 From: Xymph Date: Sat, 19 Jun 2021 16:46:00 +0200 Subject: [PATCH 4/6] Apply token method during edit/delete/upload --- Wikimate.php | 78 +++++++++++++++++++++++++++++----------------------- 1 file changed, 44 insertions(+), 34 deletions(-) diff --git a/Wikimate.php b/Wikimate.php index 45c0fa7..1deb68e 100644 --- a/Wikimate.php +++ b/Wikimate.php @@ -293,12 +293,18 @@ public function parse($array) */ public function edit($array) { + // Obtain default token first + if (($edittoken = $this->token()) === false) { + return false; + } + $headers = array( 'Content-Type' => "application/x-www-form-urlencoded" ); $array['action'] = 'edit'; $array['format'] = 'php'; + $array['token'] = $edittoken; $apiResult = $this->session->post($this->api, $headers, $array); @@ -313,12 +319,18 @@ public function edit($array) */ public function delete($array) { + // Obtain default token first + if (($deletetoken = $this->token()) === false) { + return false; + } + $headers = array( 'Content-Type' => "application/x-www-form-urlencoded" ); $array['action'] = 'delete'; $array['format'] = 'php'; + $array['token'] = $deletetoken; $apiResult = $this->session->post($this->api, $headers, $array); @@ -352,8 +364,14 @@ public function download($url) */ public function upload($array) { + // Obtain default token first + if (($uploadtoken = $this->token()) === false) { + return false; + } + $array['action'] = 'upload'; $array['format'] = 'php'; + $array['token'] = $uploadtoken; // Construct multipart body: https://www.mediawiki.org/wiki/API:Upload#Sample_Raw_Upload $boundary = '---Wikimate-' . md5(microtime()); @@ -416,7 +434,6 @@ class WikiPage protected $exists = false; protected $invalid = false; protected $error = null; - protected $edittoken = null; protected $starttimestamp = null; protected $text = null; protected $sections = null; @@ -457,7 +474,6 @@ public function __destruct() $this->exists = false; $this->invalid = false; $this->error = null; - $this->edittoken = null; $this->starttimestamp = null; $this->text = null; $this->sections = null; @@ -575,7 +591,7 @@ public function getText($refresh = false) 'titles' => $this->title, 'prop' => 'info|revisions', 'rvprop' => 'content', // Need to get page text - 'intoken' => 'edit', + 'curtimestamp' => 1, ); $r = $this->wikimate->query($data); // Run the query @@ -590,7 +606,6 @@ public function getText($refresh = false) // Get the page (there should only be one) $page = array_pop($r['query']['pages']); - unset($r, $data); // Abort if invalid page title if (isset($page['invalid'])) { @@ -598,8 +613,8 @@ public function getText($refresh = false) return null; } - $this->edittoken = $page['edittoken']; - $this->starttimestamp = $page['starttimestamp']; + $this->starttimestamp = $r['curtimestamp']; + unset($r, $data); if (!isset($page['missing'])) { // Update the existence if the page is there @@ -812,7 +827,6 @@ public function setText($text, $section = null, $minor = false, $summary = null) 'text' => $text, 'md5' => md5($text), 'bot' => "true", - 'token' => $this->edittoken, 'starttimestamp' => $this->starttimestamp, ); @@ -852,16 +866,20 @@ public function setText($text, $section = null, $minor = false, $summary = null) $data = array( 'titles' => $this->title, 'prop' => 'info', - 'intoken' => 'edit', + 'curtimestamp' => 1, ); $r = $this->wikimate->query($data); - $page = array_pop($r['query']['pages']); // Get the page - - $this->starttimestamp = $page['starttimestamp']; // Update the starttimestamp + // Check for errors + if (isset($r['error'])) { + $this->error = $r['error']; // Set the error if there was one + return null; + } else { + $this->error = null; // Reset the error status + } - $this->error = null; // Reset the error status + $this->starttimestamp = $r['curtimestamp']; // Update the starttimestamp return true; } @@ -920,7 +938,6 @@ public function delete($reason = null) { $data = array( 'title' => $this->title, - 'token' => $this->edittoken, ); // Set options from arguments @@ -989,14 +1006,13 @@ private function findSection($section) */ class WikiFile { - protected $filename = null; - protected $wikimate = null; - protected $exists = false; - protected $invalid = false; - protected $error = null; - protected $edittoken = null; - protected $info = null; - protected $history = null; + protected $filename = null; + protected $wikimate = null; + protected $exists = false; + protected $invalid = false; + protected $error = null; + protected $info = null; + protected $history = null; /* * @@ -1029,14 +1045,13 @@ public function __construct($filename, $wikimate) */ public function __destruct() { - $this->filename = null; - $this->wikimate = null; - $this->exists = false; - $this->invalid = false; - $this->error = null; - $this->edittoken = null; - $this->info = null; - $this->history = null; + $this->filename = null; + $this->wikimate = null; + $this->exists = false; + $this->invalid = false; + $this->error = null; + $this->info = null; + $this->history = null; return null; } @@ -1108,7 +1123,6 @@ public function getInfo($refresh = false, $history = null) 'iiprop' => 'bitdepth|canonicaltitle|comment|parsedcomment|' . 'commonmetadata|metadata|extmetadata|mediatype|' . 'mime|thumbmime|sha1|size|timestamp|url|user|userid', - 'intoken' => 'edit', ); // Add optional history parameters if (is_array($history)) { @@ -1139,8 +1153,6 @@ public function getInfo($refresh = false, $history = null) return null; } - $this->edittoken = $page['edittoken']; - // Check that file is present and has info if (!isset($page['missing']) && isset($page['imageinfo'])) { // Update the existence if the file is there @@ -1779,7 +1791,6 @@ public function delete($reason = null, $archivename = null) { $data = array( 'title' => 'File:' . $this->filename, - 'token' => $this->edittoken, ); // Set options from arguments @@ -1879,7 +1890,6 @@ private function uploadCommon(array $params, $comment, $text = null, $overwrite $params['filename'] = $this->filename; $params['comment'] = $comment; $params['ignorewarnings'] = $overwrite; - $params['token'] = $this->edittoken; if (!is_null($text)) { $params['text'] = $text; } From 31289e6108f231461df367623cddca8e94f0fba1 Mon Sep 17 00:00:00 2001 From: Xymph Date: Sun, 20 Jun 2021 12:24:34 +0200 Subject: [PATCH 5/6] Update changelog for PR #100 --- CHANGELOG.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0462ce6..393aa3f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,16 +8,20 @@ Since v0.10.0 this project adheres to [Semantic Versioning](http://semver.org/) * New GOVERNANCE.md file to explicitly codify the project management principles and provide guidelines for maintenance tasks ([#83]) +#### Changed + +* Modernize token handling for login and data-modifying actions ([#100]) + #### Fixed -* Prevented PHP notice in `WikiFile::getInfo()` for moved or deleted file. ([#85]) +* Prevented PHP notice in `WikiFile::getInfo()` for moved or deleted file ([#85]) ### Version 0.12.0 #### Added * New class WikiFile to retrieve properties of a file, and download and upload its contents. All properties pertain to the current revision of the file, or a specific older revision. ([#69], [#71], [#78], [#80]) -* WikiFile also provides the file history and the ability to delete a file or an older revision of it. ([#76]) +* WikiFile also provides the file history and the ability to delete a file or an older revision of it ([#76]) ### Version 0.11.0 From db71f849938bb9814f9de62ac46de56f08145742 Mon Sep 17 00:00:00 2001 From: Xymph Date: Mon, 28 Jun 2021 20:05:24 +0200 Subject: [PATCH 6/6] Explain design choices for token method --- Wikimate.php | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Wikimate.php b/Wikimate.php index 1deb68e..c5bc72c 100644 --- a/Wikimate.php +++ b/Wikimate.php @@ -63,6 +63,9 @@ protected function initRequests() /** * Obtains a wiki token for logging in or data-modifying actions. + * For now this method, in Wikimate tradition, is kept simple and supports + * only the two token types needed elsewhere in the library. It also + * doesn't support the option to request multiple tokens at once. * * @param string $type The token type * @return string The requested token