From 22c8cda8419395bf1caa124bdb3a80e771bdc853 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Gerg=C5=91=20Tisza?= Date: Sat, 19 Jan 2019 18:16:51 -0800 Subject: [PATCH] Show password policy flags on Special:PasswordPolicies Follow-up to I28c31fc4ea. Also improves what policy values are considered disabled, documents how to extend core checks/flags and adds a structure test for it. Bug: T118774 Change-Id: I66bf396e8e8a8c310a47ba337abe9070e7e83ff6 --- includes/DefaultSettings.php | 7 +++ includes/specials/SpecialPasswordPolicies.php | 39 +++++++++++----- languages/i18n/en.json | 2 + languages/i18n/qqq.json | 4 +- resources/src/mediawiki.special/special.less | 4 ++ .../structure/PasswordPolicyStructureTest.php | 46 +++++++++++++++++++ 6 files changed, 90 insertions(+), 12 deletions(-) create mode 100644 tests/phpunit/structure/PasswordPolicyStructureTest.php diff --git a/includes/DefaultSettings.php b/includes/DefaultSettings.php index dc8f1e86e4..a49ed135c9 100644 --- a/includes/DefaultSettings.php +++ b/includes/DefaultSettings.php @@ -4483,6 +4483,13 @@ $wgCentralIdLookupProvider = 'local'; * 100,000 commonly used passwords. Due to the size of the list this * is a probabilistic test. * + * If you add custom checks, for Special:PasswordPolicies to display them correctly, + * every check should have a corresponding passwordpolicies-policy- message, + * and every settings field other than 'value' should have a corresponding + * passwordpolicies-policyflag- message ( and are in lowercase). + * The check message receives the policy value as a parameter, the flag message + * receives the flag value (or values if it's an array). + * * @since 1.26 * @see PasswordPolicyChecks * @see User::checkPasswordValidity() diff --git a/includes/specials/SpecialPasswordPolicies.php b/includes/specials/SpecialPasswordPolicies.php index 573dcb52aa..d09deabf27 100644 --- a/includes/specials/SpecialPasswordPolicies.php +++ b/includes/specials/SpecialPasswordPolicies.php @@ -136,20 +136,37 @@ class SpecialPasswordPolicies extends SpecialPage { ); $ret = []; - foreach ( $groupPolicies as $gp => $val ) { - if ( $val === false ) { - // Policy isn't enabled, so no need to dislpay it + foreach ( $groupPolicies as $gp => $settings ) { + if ( !is_array( $settings ) ) { + $settings = [ 'value' => $settings ]; + } + $val = $settings['value']; + $flags = array_diff_key( $settings, [ 'value' => true ] ); + if ( !$val ) { + // Policy isn't enabled, so no need to display it continue; - } elseif ( $val === true ) { - $msg = $this->msg( 'passwordpolicies-policy-' . strtolower( $gp ) ); + } + $msg = $this->msg( 'passwordpolicies-policy-' . strtolower( $gp ) )->numParams( $val ); + $flagMsgs = []; + foreach ( array_filter( $flags ) as $flag => $value ) { + $flagMsg = $this->msg( 'passwordpolicies-policyflag-' . strtolower( $flag ) ); + $flagMsg->params( $value ); + $flagMsgs[] = $flagMsg; + } + if ( $flagMsgs ) { + $ret[] = $this->msg( + 'passwordpolicies-policy-displaywithflags', + $msg, + '' . $gp . '', + $this->getLanguage()->commaList( $flagMsgs ) + )->parse(); } else { - $msg = $this->msg( 'passwordpolicies-policy-' . strtolower( $gp ) )->numParams( $val ); + $ret[] = $this->msg( + 'passwordpolicies-policy-display', + $msg, + '' . $gp . '' + )->parse(); } - $ret[] = $this->msg( - 'passwordpolicies-policy-display', - $msg, - '' . $gp . '' - )->parse(); } if ( $ret === [] ) { return ''; diff --git a/languages/i18n/en.json b/languages/i18n/en.json index 81c305f59f..de8d251d99 100644 --- a/languages/i18n/en.json +++ b/languages/i18n/en.json @@ -4173,6 +4173,7 @@ "passwordpolicies-group": "Group", "passwordpolicies-policies": "Policies", "passwordpolicies-policy-display": "$1 ($2)", + "passwordpolicies-policy-displaywithflags": "$1 ($2) ($3)", "passwordpolicies-policy-minimalpasswordlength": "Password must be at least $1 {{PLURAL:$1|character|characters}} long", "passwordpolicies-policy-minimumpasswordlengthtologin": "Password must be at least $1 {{PLURAL:$1|character|characters}} long to be able to login", "passwordpolicies-policy-passwordcannotmatchusername": "Password cannot be the same as username", @@ -4180,6 +4181,7 @@ "passwordpolicies-policy-maximalpasswordlength": "Password must be less than $1 {{PLURAL:$1|character|characters}} long", "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", "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 --git a/languages/i18n/qqq.json b/languages/i18n/qqq.json index fa9bf44853..1ad7505bbf 100644 --- a/languages/i18n/qqq.json +++ b/languages/i18n/qqq.json @@ -4377,7 +4377,8 @@ "passwordpolicies-summary": "The description used on [[Special:PasswordPolicies]].\n\nRefers to {{msg-mw|Passwordpolicies-helppage}}.", "passwordpolicies-group": "The title of the column in the table, about user groups (like you are in the ''translator'' group).\n\n{{Identical|Group}}\n{{Related|Passwordpolicies}}", "passwordpolicies-policies": "The title of the column in the table, about password policies.\n{{Related|Passwordpolicies}}", - "passwordpolicies-policy-display": "{{optional}}\nParameters:\n* $1 - the text from the \"passwordpolicies-policy-...\" messages, i.e. {{msg-mw|passwordpolicies-policy-minimalpasswordlength}}\n* $2 - the name of this password policy", + "passwordpolicies-policy-display": "{{optional}}\nParameters:\n* $1 - the text from the \"passwordpolicies-policy-...\" messages, e.g. {{msg-mw|passwordpolicies-policy-minimalpasswordlength}}\n* $2 - the name of this password policy", + "passwordpolicies-policy-displaywithflags": "{{optional}}\nParameters:\n* $1 - the text from the \"passwordpolicies-policy-...\" messages, i.e. {{msg-mw|passwordpolicies-policy-minimalpasswordlength}}\n* $2 - the name of this password policy\n* $3 - comma-separated list of the text from the \"passwordpolicies-policyflag-...\" messages, e.g. {{msg-mw|passwordpolicies-policyflag-forcechange}}", "passwordpolicies-policy-minimalpasswordlength": "Password policy that enforces a minimum number of characters a password must be. $1 - minimum number of characters that a password can be", "passwordpolicies-policy-minimumpasswordlengthtologin": "Password policy that enforces a minimum number of characters a password must be to be able to login to the wiki. $1 - minimum number of characters that a password can be to be able to login", "passwordpolicies-policy-passwordcannotmatchusername": "Password policy that enforces that the password of the account cannot be the same as the username", @@ -4385,6 +4386,7 @@ "passwordpolicies-policy-maximalpasswordlength": "Password policy that enforces a maximum number of characters a password must be. $1 - maximum number of characters that a password can be", "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.", "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" } diff --git a/resources/src/mediawiki.special/special.less b/resources/src/mediawiki.special/special.less index e053b6c48c..35071be04e 100644 --- a/resources/src/mediawiki.special/special.less +++ b/resources/src/mediawiki.special/special.less @@ -156,3 +156,7 @@ .mw-passwordpolicies-table tr { vertical-align: top; } + +.passwordpolicies-policy-flags { + font-size: 90%; +} diff --git a/tests/phpunit/structure/PasswordPolicyStructureTest.php b/tests/phpunit/structure/PasswordPolicyStructureTest.php new file mode 100644 index 0000000000..b263762513 --- /dev/null +++ b/tests/phpunit/structure/PasswordPolicyStructureTest.php @@ -0,0 +1,46 @@ + $callback ) { + yield [ $name ]; + } + } + + public function provideFlags() { + global $wgPasswordPolicy; + + // This won't actually find all flags, just the ones in use. Can't really be helped, + // other than adding the core flags here. + $flags = [ 'forceChange' ]; + foreach ( $wgPasswordPolicy['policies'] as $group => $checks ) { + foreach ( $checks as $check => $settings ) { + if ( is_array( $settings ) ) { + $flags = array_merge( $flags, array_diff( $settings, [ 'value' ] ) ); + } + } + } + foreach ( $flags as $flag ) { + yield [ $flag ]; + } + } + + /** @dataProvider provideChecks */ + public function testCheckMessage( $check ) { + $msg = wfMessage( 'passwordpolicies-policy-' . strtolower( $check ) ); + $this->assertTrue( $msg->exists() ); + } + + /** @dataProvider provideFlags */ + public function testFlagMessage( $flag ) { + $msg = wfMessage( 'passwordpolicies-policyflag-' . strtolower( $flag ) ); + $this->assertTrue( $msg->exists() ); + } + +} -- 2.20.1