Merge "Add password policy setting `suggestChangeOnLogin`"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Tue, 12 Mar 2019 16:35:00 +0000 (16:35 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Tue, 12 Mar 2019 16:35:00 +0000 (16:35 +0000)
1  2 
includes/DefaultSettings.php
includes/user/User.php
languages/i18n/en.json
languages/i18n/qqq.json

@@@ -4445,6 -4445,11 +4445,11 @@@ $wgCentralIdLookupProvider = 'local'
   * - value: (number, boolean or null) the value to pass to the callback
   * - forceChange: (bool, default false) if the password is invalid, do
   *   not let the user log in without changing the password
+  * - suggestChangeOnLogin: (bool, default false) if true and the password is
+  *   invalid, suggest a password change if logging in. If all the failing policies
+  *   that apply to the user have this set to false, the password change
+  *   screen will not be shown. 'forceChange' takes precedence over
+  *   'suggestChangeOnLogin' if they are both present.
   * As a shorthand for [ 'value' => <value> ], simply <value> can be written.
   * When multiple password policies are defined for a user, the settings
   * arrays are merged, and for fields which are set in both arrays, the
@@@ -4514,10 -4519,10 +4519,10 @@@ $wgPasswordPolicy = 
                        'PasswordNotInLargeBlacklist' => true,
                ],
                'default' => [
-                       'MinimalPasswordLength' => 1,
-                       'PasswordCannotMatchUsername' => true,
-                       'PasswordCannotMatchBlacklist' => true,
-                       'MaximalPasswordLength' => 4096,
+                       'MinimalPasswordLength' => [ 'value' => 1, 'suggestChangeOnLogin' => true ],
+                       'PasswordCannotMatchUsername' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
+                       'PasswordCannotMatchBlacklist' => [ 'value' => true, 'suggestChangeOnLogin' => true ],
+                       'MaximalPasswordLength' => [ 'value' => 4096, 'suggestChangeOnLogin' => true ],
                ],
        ],
        'checks' => [
@@@ -7471,6 -7476,13 +7476,6 @@@ $wgAutoloadAttemptLowercase = true
   */
  $wgExtensionCredits = [];
  
 -/**
 - * Authentication plugin.
 - * @var $wgAuth AuthPlugin
 - * @deprecated since 1.27 use $wgAuthManagerConfig instead
 - */
 -$wgAuth = null;
 -
  /**
   * Global list of hooks.
   *
diff --combined includes/user/User.php
@@@ -1191,6 -1191,8 +1191,8 @@@ class User implements IDBAccessObject, 
         * - forceChange (bool): if set to true, the user should not be
         *   allowed to log with this password unless they change it during
         *   the login process (see ResetPasswordSecondaryAuthenticationProvider).
+        * - suggestChangeOnLogin (bool): if set to true, the user should be prompted for
+        *   a password change on login.
         *
         * @param string $password Desired password
         * @return Status
                        return false;
                }
  
 -              // Reject various classes of invalid names
 -              $name = AuthManager::callLegacyAuthPlugin(
 -                      'getCanonicalName', [ $t->getText() ], $t->getText()
 -              );
 +              $name = $t->getText();
  
                switch ( $validate ) {
                        case false:
  
                // update groups in external authentication database
                Hooks::run( 'UserGroupsChanged', [ $this, $toPromote, [], false, false, $oldUGMs, $newUGMs ] );
 -              AuthManager::callLegacyAuthPlugin( 'updateExternalDBGroups', [ $this, $toPromote ] );
  
                $logEntry = new ManualLogEntry( 'rights', 'autopromote' );
                $logEntry->setPerformer( $this );
                if ( $this->mLocked !== null ) {
                        return $this->mLocked;
                }
 -              // Avoid PHP 7.1 warning of passing $this by reference
 -              $user = $this;
 -              $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
 -              $this->mLocked = $authUser && $authUser->isLocked();
 +              // Reset for hook
 +              $this->mLocked = false;
                Hooks::run( 'UserIsLocked', [ $this, &$this->mLocked ] );
                return $this->mLocked;
        }
                }
                $this->getBlockedStatus();
                if ( !$this->mHideName ) {
 -                      // Avoid PHP 7.1 warning of passing $this by reference
 -                      $user = $this;
 -                      $authUser = AuthManager::callLegacyAuthPlugin( 'getUserInstance', [ &$user ], null );
 -                      $this->mHideName = $authUser && $authUser->isHidden();
 +                      // Reset for hook
 +                      $this->mHideName = false;
                        Hooks::run( 'UserIsHidden', [ $this, &$this->mHideName ] );
                }
                return (bool)$this->mHideName;
         *  non-existent/anonymous user accounts.
         */
        public function getFirstEditTimestamp() {
 +              return $this->getEditTimestamp( true );
 +      }
 +
 +      /**
 +       * Get the timestamp of the latest edit
 +       *
 +       * @since 1.33
 +       * @return string|bool Timestamp of first edit, or false for
 +       *  non-existent/anonymous user accounts.
 +       */
 +      public function getLatestEditTimestamp() {
 +              return $this->getEditTimestamp( false );
 +      }
 +
 +      /**
 +       * Get the timestamp of the first or latest edit
 +       *
 +       * @param bool $first True for the first edit, false for the latest one
 +       * @return string|bool Timestamp of first or latest edit, or false for
 +       *  non-existent/anonymous user accounts.
 +       */
 +      private function getEditTimestamp( $first ) {
                if ( $this->getId() == 0 ) {
                        return false; // anons
                }
                $actorWhere = ActorMigration::newMigration()->getWhere( $dbr, 'rev_user', $this );
                $tsField = isset( $actorWhere['tables']['temp_rev_user'] )
                        ? 'revactor_timestamp' : 'rev_timestamp';
 +              $sortOrder = $first ? 'ASC' : 'DESC';
                $time = $dbr->selectField(
                        [ 'revision' ] + $actorWhere['tables'],
                        $tsField,
                        [ $actorWhere['conds'] ],
                        __METHOD__,
 -                      [ 'ORDER BY' => "$tsField ASC" ],
 +                      [ 'ORDER BY' => "$tsField $sortOrder" ],
                        $actorWhere['joins']
                );
                if ( !$time ) {
diff --combined languages/i18n/en.json
        "badretype": "The passwords you entered do not match.",
        "usernameinprogress": "An account creation for this user name is already in progress.\nPlease wait.",
        "userexists": "Username entered already in use.\nPlease choose a different name.",
 +      "createacct-normalization": "Your username will be adjusted to \"$2\" due to technical restrictions.",
        "loginerror": "Login error",
        "createacct-error": "Account creation error",
        "createaccounterror": "Could not create account: $1",
        "ipb-confirm": "Confirm block",
        "ipb-sitewide": "Sitewide",
        "ipb-partial": "Partial",
 +      "ipb-sitewide-help": "Every page on the wiki and all other contribution actions.",
 +      "ipb-partial-help": "Specific pages or namespaces.",
        "ipb-pages-label": "Pages",
        "ipb-namespaces-label": "Namespaces",
        "badipaddress": "Invalid IP address",
        "authmanager-create-no-primary": "The supplied credentials could not be used for account creation.",
        "authmanager-link-no-primary": "The supplied credentials could not be used for account linking.",
        "authmanager-link-not-in-progress": "Account linking is not in progress or session data has been lost. Please start again from the beginning.",
 -      "authmanager-authplugin-setpass-failed-title": "Password change failed",
 -      "authmanager-authplugin-setpass-failed-message": "The authentication plugin denied the password change.",
 -      "authmanager-authplugin-create-fail": "The authentication plugin denied the account creation.",
 -      "authmanager-authplugin-setpass-denied": "The authentication plugin does not allow changing passwords.",
 -      "authmanager-authplugin-setpass-bad-domain": "Invalid domain.",
        "authmanager-autocreate-noperm": "Automatic account creation is not allowed.",
        "authmanager-autocreate-exception": "Automatic account creation temporarily disabled due to prior errors.",
        "authmanager-userdoesnotexist": "User account \"$1\" is not registered.",
        "passwordpolicies-policy-passwordcannotbepopular": "Password cannot be {{PLURAL:$1|the popular password|in the list of $1 popular passwords}}",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "Password cannot be in the list of 100,000 most commonly used passwords.",
        "passwordpolicies-policyflag-forcechange": "must change on login",
+       "passwordpolicies-policyflag-suggestchangeonlogin": "suggest change on login",
        "easydeflate-invaliddeflate": "Content provided is not properly deflated",
        "unprotected-js": "For security reasons JavaScript cannot be loaded from unprotected pages. Please only create javascript in the MediaWiki: namespace or as a User subpage"
  }
diff --combined languages/i18n/qqq.json
        "site-rss-feed": "Used in the HTML header of a wiki's RSS feed.\nHTML markup cannot be used.\n\nParameters:\n* $1 - <nowiki>{{SITENAME}}</nowiki>\n{{Identical|S1 RSS/Atom feed}}",
        "site-atom-feed": "Used in the HTML header of a wiki's Atom feed.\nHTML markup cannot be used.\n\nParameters:\n* $1 - <nowiki>{{SITENAME}}</nowiki>\n{{Identical|S1 RSS/Atom feed}}",
        "page-rss-feed": "Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Page-atom-feed}}\n{{Identical|S1 RSS/Atom feed}}",
 -      "page-atom-feed": "Parameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Page-rss-feed}}\n{{Identical|S1 RSS/Atom feed}}",
 +      "page-atom-feed": "Used as the \"title\" attribute in the <link rel=\"alternate\" type=\"application/atom+xml\"> element of the HTML source of the page. Not rendered in the web page.\n\nParameters:\n* $1 - page title\nSee also:\n* {{msg-mw|Page-rss-feed}}\n{{Identical|S1 RSS/Atom feed}}",
        "feed-atom": "{{optional}}\nSee also:\n* {{msg-mw|Feed-atom}}\n* {{msg-mw|Accesskey-feed-atom}}\n* {{msg-mw|Tooltip-feed-atom}}",
        "feed-rss": "{{optional}}\nSee also:\n* {{msg-mw|Feed-rss}}\n* {{msg-mw|Accesskey-feed-rss}}\n* {{msg-mw|Tooltip-feed-rss}}",
        "sitenotice": "{{Notranslate}}\n\nMediaWiki:Sitenotice is displayed above the page title for all users if it is defined, unless it is superseded by another notice. 'Defined' means it exists and has content other than the single character '-'.\n\nManual: [[mw:Manual:Interface/Sitenotice]]",
        "badretype": "Used as error message when the new password and its retype do not match.",
        "usernameinprogress": "Used as error message in creating a user account.",
        "userexists": "Used as error message in creating a user account.",
 +      "createacct-normalization": "Used as warning message on account creation when user name is adjusted silently due to technical restrictions (e.g. first letter capitalized, underscores converted to spaces).\nParameters:\n* $1 - the old username\n* $2 - the new username",
        "loginerror": "Used as title of error message.\n{{Identical|Login error}}",
        "createacct-error": "Used as heading for the error message.",
        "createaccounterror": "Parameters:\n* $1 - an error message",
        "ipb-confirm": "Used as hidden field in the form on [[Special:Block]].",
        "ipb-sitewide": "A type of block the user can select from on [[Special:Block]].",
        "ipb-partial": "A type of block the user can select from on [[Special:Block]].",
 +      "ipb-sitewide-help": "Help text describing the effects of a sitewide block on [[Special:Block]]",
 +      "ipb-partial-help": "Help text describing the effects of a partial block on  [[Special:Block]]",
        "ipb-pages-label": "The label for an autocomplete text field to specify pages to block a user from editing on [[Special:Block]].",
        "ipb-namespaces-label": "The label for an autocomplete text field to specify namespaces to block a user from editing on [[Special:Block]].",
        "badipaddress": "An error message shown when one entered an invalid IP address in blocking page.",
        "ip_range_toolarge": "Used as error message in [[Special:Block]]. Parameters:\n* $1 - a number from 0 to 32 for IPv4 (from 0 to 128 for IPv6); a part of CIDR (Classless Inter-Domain Routing) notation.\nSee also:\n* {{msg-mw|Range block disabled}}\n* {{msg-mw|Ip range invalid}}\n* {{msg-mw|Ip range toolarge}}",
        "ip_range_exceeded": "Used as error message in HTMLUserTextField when an IP range exceeds its maximum amount. See {{msg-mw|ip_range_toolarge}} for parameter.\n/$1 is the width as a number of bits.",
        "ip_range_toolow": "Used as error message in HTMLUserTextField, if effectively no IP ranges are interpreted as valid (IPv4 CIDR range /32 or IPv6 /128).",
 -      "proxyblocker": "Used in [[Special:BlockMe]].\n\nSee also:\n* {{msg-mw|proxyblocker-disabled}}\n* {{msg-mw|proxyblockreason}}\n* {{msg-mw|proxyblocksuccess}}",
 -      "proxyblockreason": "Used as explanation of the reason in [[Special:BlockMe]].\n\nSee also:\n* {{msg-mw|proxyblocker-disabled}}\n* {{msg-mw|proxyblocker}}\n* {{msg-mw|proxyblocksuccess}}",
 +      "proxyblocker": "Username for blocking IP addresses listed in [[mw:Manual:$wgProxyList|$wgProxyList]].\n\nSee also:\n* {{msg-mw|proxyblockreason}}",
 +      "proxyblockreason": "Reason for blocking IP addresses listed in [[mw:Manual:$wgProxyList|$wgProxyList]].\n\nSee also:\n* {{msg-mw|proxyblocker}}",
        "sorbs": "{{optional}}",
        "sorbsreason": "See also:\n* {{msg-mw|Sorbsreason}}\n* {{msg-mw|Sorbs create account_reason}}",
        "sorbs_create_account_reason": "Used in [[Special:UserLogin]] when creating an account.\n\nSee also:\n* {{msg-mw|Sorbsreason}}\n* {{msg-mw|Sorbs create account_reason}}",
        "authmanager-create-no-primary": "Error message when no AuthenticationProvider handles the AuthenticationRequests for account creation. This might mean the user needs to fill out all the form fields.",
        "authmanager-link-no-primary": "Error message when no AuthenticationProvider handles the AuthenticationRequests for account linking. This might mean the user needs to fill out all the form fields.",
        "authmanager-link-not-in-progress": "Error message when AuthManager session data is lost during account linking, or the user hits the \"continue\" endpoint without an active account link attempt.",
 -      "authmanager-authplugin-setpass-failed-title": "Title of error page from AuthManager if AuthPlugin returns false from its setPassword() method.",
 -      "authmanager-authplugin-setpass-failed-message": "Text of error page from AuthManager if AuthPlugin returns false from its setPassword() method.",
 -      "authmanager-authplugin-create-fail": "Error message from AuthManager if the AuthPlugin returns false from its addUser() method.",
 -      "authmanager-authplugin-setpass-denied": "Error message from AuthManager if the AuthPlugin returns false from its allowPasswordChange() method.",
 -      "authmanager-authplugin-setpass-bad-domain": "Error message from AuthManager if the AuthPlugin rejects the passed domain.",
        "authmanager-autocreate-noperm": "Error message when auto-creation fails due to lack of permission.",
        "authmanager-autocreate-exception": "Error message when auto-creation fails because we tried recently and an exception was thrown, so we're not going to try again yet.",
        "authmanager-userdoesnotexist": "Error message when a user account does not exist. Parameters:\n* $1 - User name.",
        "passwordpolicies-policy-passwordcannotbepopular": "Password policy that enforces that a password is not in a list of $1 number of \"popular\" passwords. $1 - number of popular passwords the password will be checked against",
        "passwordpolicies-policy-passwordnotinlargeblacklist": "Password policy that enforces that a password is not in a list of 100,000 number of \"popular\" passwords.",
        "passwordpolicies-policyflag-forcechange": "Password policy flag that enforces changing invalid passwords on login.",
+       "passwordpolicies-policyflag-suggestchangeonlogin": "Password policy flag that suggests changing invalid passwords on login.",
        "easydeflate-invaliddeflate": "Error message if the content passed to easydeflate was not deflated (compressed) properly",
        "unprotected-js": "Error message shown when trying to load javascript via action=raw that is not protected"
  }