/** * Backend implementation of doRollback(), please refer there for parameter * and return value documentation * * NOTE: This function does NOT check ANY permissions, it just commits the * rollback to the DB. Therefore, you should only call this function direct- * ly if you want to use custom permissions checks. If you don't, use * doRollback() instead. * @param string $fromP Name of the user whose edits to rollback. * @param string $summary Custom summary. Set to default summary if empty. * @param $bot Boolean: If true, mark all reverted edits as bot. * * @param array $resultDetails contains result-specific array of additional values * @param $guser User The user performing the rollback * @return array */ public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) { global $wgUseRCPatrol, $wgContLang; $dbw = wfGetDB( DB_MASTER ); if ( wfReadOnly() ) { return array( array( 'readonlytext' ) ); } // Get the last editor $current = $this->getRevision(); if ( is_null( $current ) ) { // Something wrong... no page? return array( array( 'notanarticle' ) ); } $from = str_replace( '_', ' ', $fromP ); // User name given should match up with the top revision. // If the user was deleted then $from should be empty. if ( $from != $current->getUserText() ) { $resultDetails = array( 'current' => $current ); return array( array( 'alreadyrolled', htmlspecialchars( $this->mTitle->getPrefixedText() ), htmlspecialchars( $fromP ), htmlspecialchars( $current->getUserText() ) ) ); } // Get the last edit not by this guy... // Note: these may not be public values $user = intval( $current->getRawUser() ); $user_text = $dbw->addQuotes( $current->getRawUserText() ); $s = $dbw->selectRow( 'revision', array( 'rev_id', 'rev_timestamp', 'rev_deleted' ), array( 'rev_page' => $current->getPage(), "rev_user != {$user} OR rev_user_text != {$user_text}" ), __METHOD__, array( 'USE INDEX' => 'page_timestamp', 'ORDER BY' => 'rev_timestamp DESC' ) ); if ( $s === false ) { // No one else ever edited this page return array( array( 'cantrollback' ) ); } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) { // Only admins can see this text return array( array( 'notvisiblerev' ) ); } $set = array(); if ( $bot && $guser->isAllowed( 'markbotedits' ) ) { // Mark all reverted edits as bot $set['rc_bot'] = 1; } if ( $wgUseRCPatrol ) { // Mark all reverted edits as patrolled $set['rc_patrolled'] = 1; } if ( count( $set ) ) { $dbw->update( 'recentchanges', $set, array( /* WHERE */ 'rc_cur_id' => $current->getPage(), 'rc_user_text' => $current->getUserText(), 'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ), ), __METHOD__ ); } // Generate the edit summary if necessary $target = Revision::newFromId( $s->rev_id ); if ( empty( $summary ) ) { if ( $from == '' ) { // no public user name $summary = wfMessage( 'revertpage-nouser' ); } else { $summary = wfMessage( 'revertpage' ); } } // Allow the custom summary to use the same args as the default message $args = array( $target->getUserText(), $from, $s->rev_id, $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ), $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() ) ); if ( $summary instanceof Message ) { $summary = $summary->params( $args )->inContentLanguage()->text(); } else { $summary = wfMsgReplaceArgs( $summary, $args ); } // Trim spaces on user supplied text $summary = trim( $summary ); // Truncate for whole multibyte characters. $summary = $wgContLang->truncate( $summary, 255 ); // Save $flags = EDIT_UPDATE; if ( $guser->isAllowed( 'minoredit' ) ) { $flags |= EDIT_MINOR; } if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) { $flags |= EDIT_FORCE_BOT; } // Actually store the edit $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser ); if ( !$status->isOK() ) { return $status->getErrorsArray(); } if ( !empty( $status->value['revision'] ) ) { $revId = $status->value['revision']->getId(); } else { $revId = false; } wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) ); $resultDetails = array( 'summary' => $summary, 'current' => $current, 'target' => $target, 'newid' => $revId ); return array(); }
/** * Check CSS/JS sub-page permissions * * @param string $action The action to check * @param User $user User to check * @param array $errors List of current errors * @param string $rigor Same format as Title::getUserPermissionsErrors() * @param bool $short Short circuit on first error * * @return array List of errors */ private function checkCSSandJSPermissions($action, $user, $errors, $rigor, $short) { # Protect css/js subpages of user pages # XXX: this might be better using restrictions # XXX: right 'editusercssjs' is deprecated, for backward compatibility only if ($action != 'patrol' && !$user->isAllowed('editusercssjs')) { if (preg_match('/^' . preg_quote($user->getName(), '/') . '\\//', $this->mTextform)) { if ($this->isCssSubpage() && !$user->isAllowedAny('editmyusercss', 'editusercss')) { $errors[] = array('mycustomcssprotected', $action); } elseif ($this->isJsSubpage() && !$user->isAllowedAny('editmyuserjs', 'edituserjs')) { $errors[] = array('mycustomjsprotected', $action); } } else { if ($this->isCssSubpage() && !$user->isAllowed('editusercss')) { $errors[] = array('customcssprotected', $action); } elseif ($this->isJsSubpage() && !$user->isAllowed('edituserjs')) { $errors[] = array('customjsprotected', $action); } } } return $errors; }
/** * Backend implementation of doRollback(), please refer there for parameter * and return value documentation * * NOTE: This function does NOT check ANY permissions, it just commits the * rollback to the DB. Therefore, you should only call this function direct- * ly if you want to use custom permissions checks. If you don't, use * doRollback() instead. * @param string $fromP Name of the user whose edits to rollback. * @param string $summary Custom summary. Set to default summary if empty. * @param bool $bot If true, mark all reverted edits as bot. * * @param array $resultDetails Contains result-specific array of additional values * @param User $guser The user performing the rollback * @param array|null $tags Change tags to apply to the rollback * Callers are responsible for permission checks * (with ChangeTags::canAddTagsAccompanyingChange) * * @return array */ public function commitRollback($fromP, $summary, $bot, &$resultDetails, User $guser, $tags = null) { global $wgUseRCPatrol, $wgContLang; $dbw = wfGetDB(DB_MASTER); if (wfReadOnly()) { return [['readonlytext']]; } // Get the last editor $current = $this->getRevision(); if (is_null($current)) { // Something wrong... no page? return [['notanarticle']]; } $from = str_replace('_', ' ', $fromP); // User name given should match up with the top revision. // If the user was deleted then $from should be empty. if ($from != $current->getUserText()) { $resultDetails = ['current' => $current]; return [['alreadyrolled', htmlspecialchars($this->mTitle->getPrefixedText()), htmlspecialchars($fromP), htmlspecialchars($current->getUserText())]]; } // Get the last edit not by this person... // Note: these may not be public values $user = intval($current->getUser(Revision::RAW)); $user_text = $dbw->addQuotes($current->getUserText(Revision::RAW)); $s = $dbw->selectRow('revision', ['rev_id', 'rev_timestamp', 'rev_deleted'], ['rev_page' => $current->getPage(), "rev_user != {$user} OR rev_user_text != {$user_text}"], __METHOD__, ['USE INDEX' => 'page_timestamp', 'ORDER BY' => 'rev_timestamp DESC']); if ($s === false) { // No one else ever edited this page return [['cantrollback']]; } elseif ($s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER) { // Only admins can see this text return [['notvisiblerev']]; } // Generate the edit summary if necessary $target = Revision::newFromId($s->rev_id, Revision::READ_LATEST); if (empty($summary)) { if ($from == '') { // no public user name $summary = wfMessage('revertpage-nouser'); } else { $summary = wfMessage('revertpage'); } } // Allow the custom summary to use the same args as the default message $args = [$target->getUserText(), $from, $s->rev_id, $wgContLang->timeanddate(wfTimestamp(TS_MW, $s->rev_timestamp)), $current->getId(), $wgContLang->timeanddate($current->getTimestamp())]; if ($summary instanceof Message) { $summary = $summary->params($args)->inContentLanguage()->text(); } else { $summary = wfMsgReplaceArgs($summary, $args); } // Trim spaces on user supplied text $summary = trim($summary); // Truncate for whole multibyte characters. $summary = $wgContLang->truncate($summary, 255); // Save $flags = EDIT_UPDATE | EDIT_INTERNAL; if ($guser->isAllowed('minoredit')) { $flags |= EDIT_MINOR; } if ($bot && $guser->isAllowedAny('markbotedits', 'bot')) { $flags |= EDIT_FORCE_BOT; } $targetContent = $target->getContent(); $changingContentModel = $targetContent->getModel() !== $current->getContentModel(); // Actually store the edit $status = $this->doEditContent($targetContent, $summary, $flags, $target->getId(), $guser, null, $tags); // Set patrolling and bot flag on the edits, which gets rollbacked. // This is done even on edit failure to have patrolling in that case (bug 62157). $set = []; if ($bot && $guser->isAllowed('markbotedits')) { // Mark all reverted edits as bot $set['rc_bot'] = 1; } if ($wgUseRCPatrol) { // Mark all reverted edits as patrolled $set['rc_patrolled'] = 1; } if (count($set)) { $dbw->update('recentchanges', $set, ['rc_cur_id' => $current->getPage(), 'rc_user_text' => $current->getUserText(), 'rc_timestamp > ' . $dbw->addQuotes($s->rev_timestamp)], __METHOD__); } if (!$status->isOK()) { return $status->getErrorsArray(); } // raise error, when the edit is an edit without a new version $statusRev = isset($status->value['revision']) ? $status->value['revision'] : null; if (!$statusRev instanceof Revision) { $resultDetails = ['current' => $current]; return [['alreadyrolled', htmlspecialchars($this->mTitle->getPrefixedText()), htmlspecialchars($fromP), htmlspecialchars($current->getUserText())]]; } if ($changingContentModel) { // If the content model changed during the rollback, // make sure it gets logged to Special:Log/contentmodel $log = new ManualLogEntry('contentmodel', 'change'); $log->setPerformer($guser); $log->setTarget($this->mTitle); $log->setComment($summary); $log->setParameters(['4::oldmodel' => $current->getContentModel(), '5::newmodel' => $targetContent->getModel()]); $logId = $log->insert($dbw); $log->publish($logId); } $revId = $statusRev->getId(); Hooks::run('ArticleRollbackComplete', [$this, $guser, $target, $current]); $resultDetails = ['summary' => $summary, 'current' => $current, 'target' => $target, 'newid' => $revId]; return []; }
/** * Attempt to add a user to the database * Does the required authentication checks and updates for auto-creation * @param $user User * @throws Exception * @return bool Success */ static function attemptAddUser($user) { global $wgAuth, $wgCentralAuthCreateOnView; $userName = $user->getName(); // Denied by configuration? if (!$wgAuth->autoCreate()) { wfDebug(__METHOD__ . ": denied by configuration\n"); return false; } if (!$wgCentralAuthCreateOnView) { // Only create local accounts when we perform an active login... // Don't freak people out on every page view wfDebug(__METHOD__ . ": denied by \$wgCentralAuthCreateOnView\n"); return false; } // Is the user blacklisted by the session? // This is just a cache to avoid expensive DB queries in $user->isAllowedToCreateAccount(). // The user can log in via Special:UserLogin to bypass the blacklist and get a proper // error message. $session = CentralAuthUser::getSession(); if (isset($session['auto-create-blacklist']) && in_array(wfWikiID(), (array) $session['auto-create-blacklist'])) { wfDebug(__METHOD__ . ": blacklisted by session\n"); return false; } // Is the user blocked? $anon = new User(); if (!$anon->isAllowedAny('createaccount', 'centralauth-autoaccount') || $anon->isBlockedFromCreateAccount()) { // Blacklist the user to avoid repeated DB queries subsequently // First load the session again in case it changed while the above DB query was in progress wfDebug(__METHOD__ . ": user is blocked from this wiki, blacklisting\n"); $session['auto-create-blacklist'][] = wfWikiID(); CentralAuthUser::setSession($session); return false; } // Check for validity of username if (!User::isCreatableName($userName)) { wfDebug(__METHOD__ . ": Invalid username\n"); $session['auto-create-blacklist'][] = wfWikiID(); CentralAuthUser::setSession($session); return false; } // Give other extensions a chance to stop auto creation. $user->loadDefaults($userName); $abortMessage = ''; if (!Hooks::run('AbortAutoAccount', array($user, &$abortMessage))) { // In this case we have no way to return the message to the user, // but we can log it. wfDebug(__METHOD__ . ": denied by other extension: {$abortMessage}\n"); $session['auto-create-blacklist'][] = wfWikiID(); CentralAuthUser::setSession($session); return false; } // Make sure the name has not been changed if ($user->getName() !== $userName) { throw new Exception("AbortAutoAccount hook tried to change the user name"); } // Checks passed, create the user $from = isset($_SERVER['REQUEST_URI']) ? $_SERVER['REQUEST_URI'] : 'CLI'; wfDebugLog('CentralAuth-Bug39996', __METHOD__ . ": creating new user ({$userName}) - from: {$from}\n"); try { $status = $user->addToDatabase(); } catch (Exception $e) { wfDebugLog('CentralAuth-Bug39996', __METHOD__ . " User::addToDatabase for \"{$userName}\" threw an exception:" . " {$e->getMessage()}"); throw $e; } if ($status === null) { // MW before 1.21 -- ok, continue } elseif (!$status->isOK()) { wfDebugLog('CentralAuth-Bug39996', __METHOD__ . ": failed with message " . $status->getWikiText() . "\n"); return false; } $wgAuth->initUser($user, true); # Notify hooks (e.g. Newuserlog) Hooks::run('AuthPluginAutoCreate', array($user)); # Update user count DeferredUpdates::addUpdate(new SiteStatsUpdate(0, 0, 0, 0, 1)); return true; }
/** * Attempt to add a user to the database * Does the required authentication checks and updates for auto-creation * @param $user User * @param $userName string * @return bool Success */ static function attemptAddUser($user, $userName) { global $wgAuth, $wgCentralAuthCreateOnView; // Denied by configuration? if (!$wgAuth->autoCreate()) { wfDebug(__METHOD__ . ": denied by configuration\n"); return false; } if (!$wgCentralAuthCreateOnView) { // Only create local accounts when we perform an active login... // Don't freak people out on every page view wfDebug(__METHOD__ . ": denied by \$wgCentralAuthCreateOnView\n"); return false; } // Is the user blacklisted by the session? // This is just a cache to avoid expensive DB queries in $user->isAllowedToCreateAccount(). // The user can log in via Special:UserLogin to bypass the blacklist and get a proper // error message. $session = CentralAuthUser::getSession(); if (isset($session['auto-create-blacklist']) && in_array(wfWikiID(), (array) $session['auto-create-blacklist'])) { wfDebug(__METHOD__ . ": blacklisted by session\n"); return false; } // Is the user blocked? $anon = new User(); if (!$anon->isAllowedAny('createaccount', 'centralauth-autoaccount') || $anon->isBlockedFromCreateAccount()) { // Blacklist the user to avoid repeated DB queries subsequently // First load the session again in case it changed while the above DB query was in progress wfDebug(__METHOD__ . ": user is blocked from this wiki, blacklisting\n"); $session = CentralAuthUser::getSession(); $session['auto-create-blacklist'][] = wfWikiID(); CentralAuthUser::setSession($session); return false; } // Check for validity of username if (!User::isValidUserName($userName)) { wfDebug(__METHOD__ . ": Invalid username\n"); $session = CentralAuthUser::getSession(); $session['auto-create-blacklist'][] = wfWikiID(); CentralAuthUser::setSession($session); return false; } // Give other extensions a chance to stop auto creation, but they cannot // change $userName, because CentralAuth expects user names on all wikis // are the same. // // * $user (and usually $wgUser) is the half-created User object and // should not be accessed in any way since calling any User methods // in its half-initialised state will give incorrect results. // // * $userName is the new user name // // * $anon is an anonymous user object which can be safely used for // permissions checks. if (!wfRunHooks('CentralAuthAutoCreate', array($user, $userName, $anon))) { wfDebug(__METHOD__ . ": denied by other extensions\n"); return false; } $abortMessage = ''; if (!wfRunHooks('AbortAutoAccount', array($user, &$abortMessage))) { // In this case we have no way to return the message to the user, // but we can log it. wfDebug(__METHOD__ . ": denied by other extension: {$abortMessage}\n"); return false; } // Checks passed, create the user wfDebug(__METHOD__ . ": creating new user\n"); $user->loadDefaults($userName); $user->addToDatabase(); $user->addNewUserLogEntryAutoCreate(); $wgAuth->initUser($user, true); $wgAuth->updateUser($user); # Notify hooks (e.g. Newuserlog) wfRunHooks('AuthPluginAutoCreate', array($user)); # Update user count $ssUpdate = new SiteStatsUpdate(0, 0, 0, 0, 1); $ssUpdate->doUpdate(); return true; }
private function getExtraDeletedPageLogEntryRelatedCond(IDatabase $db, User $user) { // LogPage::DELETED_ACTION hides the affected page, too. So hide those // entirely from the watchlist, or someone could guess the title. $bitmask = 0; if (!$user->isAllowed('deletedhistory')) { $bitmask = LogPage::DELETED_ACTION; } elseif (!$user->isAllowedAny('suppressrevision', 'viewsuppressed')) { $bitmask = LogPage::DELETED_ACTION | LogPage::DELETED_RESTRICTED; } if ($bitmask) { return $db->makeList(['rc_type != ' . RC_LOG, $db->bitAnd('rc_deleted', $bitmask) . " != {$bitmask}"], LIST_OR); } return ''; }