Example #1
0
	/**
	 * 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();
	}
Example #2
0
 /**
  * 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;
 }
Example #3
0
 /**
  * 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;
 }
Example #5
0
 /**
  * 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 '';
 }