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
* 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-<check> message,
+ * and every settings field other than 'value' should have a corresponding
+ * passwordpolicies-policyflag-<flag> message (<check> and <flag> 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()
);
$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,
+ '<span class="mw-passwordpolicies-policy-name">' . $gp . '</span>',
+ $this->getLanguage()->commaList( $flagMsgs )
+ )->parse();
} else {
- $msg = $this->msg( 'passwordpolicies-policy-' . strtolower( $gp ) )->numParams( $val );
+ $ret[] = $this->msg(
+ 'passwordpolicies-policy-display',
+ $msg,
+ '<span class="mw-passwordpolicies-policy-name">' . $gp . '</span>'
+ )->parse();
}
- $ret[] = $this->msg(
- 'passwordpolicies-policy-display',
- $msg,
- '<span class="mw-passwordpolicies-policy-name">' . $gp . '</span>'
- )->parse();
}
if ( $ret === [] ) {
return '';
"passwordpolicies-group": "Group",
"passwordpolicies-policies": "Policies",
"passwordpolicies-policy-display": "<span class=\"passwordpolicies-policy\">$1 <code>($2)</code></span>",
+ "passwordpolicies-policy-displaywithflags": "<span class=\"passwordpolicies-policy\">$1 <code>($2)</code></span> <span class=\"passwordpolicies-policy-flags\">($3)</span>",
"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",
"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"
}
"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",
"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"
}
.mw-passwordpolicies-table tr {
vertical-align: top;
}
+
+.passwordpolicies-policy-flags {
+ font-size: 90%;
+}
--- /dev/null
+<?php
+
+/**
+ * @coversNothing
+ */
+class PasswordPolicyStructureTest extends MediaWikiTestCase {
+
+ public function provideChecks() {
+ global $wgPasswordPolicy;
+
+ foreach ( $wgPasswordPolicy['checks'] as $name => $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() );
+ }
+
+}