Page Menu
Home
WickedGov Phorge
Search
Configure Global Search
Log In
Files
F6008412
ActionVariablesIntegrationTest.php
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
16 KB
Referenced Files
None
Subscribers
None
ActionVariablesIntegrationTest.php
View Options
<?php
namespace
MediaWiki\Extension\AbuseFilter\Tests\Integration
;
use
AbuseFilterCreateAccountTestTrait
;
use
Generator
;
use
MediaWiki\Api\ApiUsageException
;
use
MediaWiki\Content\Content
;
use
MediaWiki\Content\JsonContent
;
use
MediaWiki\Content\WikitextContent
;
use
MediaWiki\Extension\AbuseFilter\AbuseFilterServices
;
use
MediaWiki\Extension\AbuseFilter\AbuseLogger
;
use
MediaWiki\Extension\AbuseFilter\AbuseLoggerFactory
;
use
MediaWiki\Extension\AbuseFilter\Consequences\ConsequencesLookup
;
use
MediaWiki\Extension\AbuseFilter\EditRevUpdater
;
use
MediaWiki\Extension\AbuseFilter\EmergencyCache
;
use
MediaWiki\Extension\AbuseFilter\Filter\ExistingFilter
;
use
MediaWiki\Extension\AbuseFilter\Filter\Flags
;
use
MediaWiki\Extension\AbuseFilter\Filter\LastEditInfo
;
use
MediaWiki\Extension\AbuseFilter\Filter\Specs
;
use
MediaWiki\Extension\AbuseFilter\FilterLookup
;
use
MediaWiki\Extension\AbuseFilter\FilterProfiler
;
use
MediaWiki\Extension\AbuseFilter\Hooks\Handlers\FilteredActionsHandler
;
use
MediaWiki\Extension\AbuseFilter\Variables\VariableHolder
;
use
MediaWiki\Extension\AbuseFilter\Watcher\EmergencyWatcher
;
use
MediaWiki\Extension\AbuseFilter\Watcher\UpdateHitCountWatcher
;
use
MediaWiki\Json\FormatJson
;
use
MediaWiki\MainConfigNames
;
use
MediaWiki\MediaWikiServices
;
use
MediaWiki\RecentChanges\RecentChange
;
use
MediaWiki\Tests\Api\ApiTestCase
;
use
MediaWiki\Tests\User\TempUser\TempUserTestTrait
;
use
MediaWiki\User\UserIdentity
;
use
MediaWiki\User\UserIdentityValue
;
use
MediaWiki\Utils\MWTimestamp
;
use
Wikimedia\Stats\NullStatsdDataFactory
;
/**
* @group Database
* @group medium
* @group AbuseFilter
* @group AbuseFilterGeneric
*/
class
ActionVariablesIntegrationTest
extends
ApiTestCase
{
use
AbuseFilterCreateAccountTestTrait
;
use
TempUserTestTrait
;
private
function
prepareServices
(
string
$rules
=
'1 === 1'
):
void
{
$this
->
setService
(
FilterProfiler
::
SERVICE_NAME
,
$this
->
createMock
(
FilterProfiler
::
class
)
);
$this
->
setService
(
EmergencyCache
::
SERVICE_NAME
,
$this
->
createMock
(
EmergencyCache
::
class
)
);
$this
->
setService
(
EmergencyWatcher
::
SERVICE_NAME
,
$this
->
createMock
(
EmergencyWatcher
::
class
)
);
$this
->
setService
(
UpdateHitCountWatcher
::
SERVICE_NAME
,
$this
->
createMock
(
UpdateHitCountWatcher
::
class
)
);
$this
->
setService
(
EditRevUpdater
::
SERVICE_NAME
,
$this
->
createMock
(
EditRevUpdater
::
class
)
);
$this
->
overrideConfigValues
(
[
MainConfigNames
::
PageCreationLog
=>
false
,
'AbuseFilterCentralDB'
=>
false
,
]
);
$filter
=
new
ExistingFilter
(
new
Specs
(
$rules
,
''
,
'Test Filter'
,
[
'disallow'
],
'default'
),
new
Flags
(
true
,
false
,
Flags
::
FILTER_USES_PROTECTED_VARS
,
false
),
[
'disallow'
=>
[
'abusefilter-disallow'
]
],
new
LastEditInfo
(
UserIdentityValue
::
newRegistered
(
1
,
'Filter User'
),
'20220713000000'
),
1
,
0
,
false
);
$filterLookup
=
$this
->
createMock
(
FilterLookup
::
class
);
$filterLookup
->
expects
(
$this
->
once
()
)
->
method
(
'getAllActiveFiltersInGroup'
)
->
with
(
'default'
,
false
)
->
willReturn
(
[
$filter
]
);
$filterLookup
->
method
(
'getFilter'
)
->
with
(
1
,
false
)
->
willReturn
(
$filter
);
$this
->
setService
(
FilterLookup
::
SERVICE_NAME
,
$filterLookup
);
$consequencesLookup
=
$this
->
createMock
(
ConsequencesLookup
::
class
);
$consequencesLookup
->
method
(
'getConsequencesForFilters'
)
->
with
(
$this
->
logicalOr
(
[
1
],
[
'1'
]
)
)
->
willReturn
(
[
1
=>
$filter
->
getActions
()
]
);
$this
->
setService
(
ConsequencesLookup
::
SERVICE_NAME
,
$consequencesLookup
);
}
private
function
setAbuseLoggerFactoryWithEavesdrop
(
?
VariableHolder
&
$varHolder
=
null
):
void
{
$factory
=
$this
->
createMock
(
AbuseLoggerFactory
::
class
);
$factory
->
method
(
'newLogger'
)
->
willReturnCallback
(
function
(
$title
,
$user
,
$vars
)
use
(
&
$varHolder
)
{
$varHolder
=
$vars
;
$logger
=
$this
->
createMock
(
AbuseLogger
::
class
);
$logger
->
method
(
'addLogEntries'
)
->
willReturn
(
[
'local'
=>
[
1
],
'global'
=>
[]
]
);
return
$logger
;
}
);
$this
->
setService
(
AbuseLoggerFactory
::
SERVICE_NAME
,
$factory
);
}
private
function
assertVariables
(
array
$expected
,
array
$export
)
{
foreach
(
$expected
as
$var
=>
$value
)
{
$this
->
assertArrayHasKey
(
$var
,
$export
,
"Variable '$var' not set"
);
$actual
=
$export
[
$var
];
if
(
$var
===
'new_html'
&&
is_array
(
$value
)
)
{
// Special case for new_html: avoid flaky tests, and only check containment
$this
->
assertStringContainsString
(
'mw-parser-output'
,
$actual
);
$this
->
assertDoesNotMatchRegularExpression
(
"/<!--
\s
*NewPP limit/"
,
$actual
);
$this
->
assertDoesNotMatchRegularExpression
(
"/<!--
\s
*Transclusion/"
,
$actual
);
foreach
(
$value
as
$needle
)
{
$this
->
assertStringContainsString
(
$needle
,
$actual
,
'Checking new_html'
);
}
}
else
{
$this
->
assertSame
(
$value
,
$actual
,
$var
);
}
}
}
public
static
function
provideEditVariables
():
Generator
{
$summary
=
__METHOD__
;
$new
=
'[https://a.com Test] foo'
;
yield
'create page'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
''
,
'old_content_model'
=>
''
,
'new_wikitext'
=>
$new
,
'new_content_model'
=>
'wikitext'
,
'summary'
=>
$summary
,
'new_pst'
=>
$new
,
'new_text'
=>
"Test foo"
,
'edit_diff'
=>
"@@ -1,0 +1,1 @@
\n
+$new
\n
"
,
'edit_diff_pst'
=>
"@@ -1,0 +1,1 @@
\n
+$new
\n
"
,
'new_size'
=>
strlen
(
$new
),
'old_size'
=>
0
,
'edit_delta'
=>
strlen
(
$new
),
'added_lines'
=>
[
$new
],
'removed_lines'
=>
[],
'added_lines_pst'
=>
[
$new
],
'new_links'
=>
[
'https://a.com/'
],
'old_links'
=>
[],
'added_links'
=>
[
'https://a.com/'
],
'removed_links'
=>
[],
'page_last_edit_age'
=>
null
,
],
'params'
=>
[
'text'
=>
$new
,
'summary'
=>
$summary
,
'createonly'
=>
true
],
];
// phpcs:disable Generic.Files.LineLength
$old
=
'[https://a.com Test] foo'
;
$new
=
"'''Random'''.
\n
Some ''special'' chars: àèìòù 名探偵コナン.
\n
[[Help:PST|]] test, [//www.b.com link]"
;
yield
'PST and special chars'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
$old
,
'old_content_model'
=>
'wikitext'
,
'new_wikitext'
=>
$new
,
'new_content_model'
=>
'wikitext'
,
'summary'
=>
$summary
,
'new_pst'
=>
"'''Random'''.
\n
Some ''special'' chars: àèìòù 名探偵コナン.
\n
[[Help:PST|PST]] test, [//www.b.com link]"
,
'new_text'
=>
"Random.
\n
Some special chars: àèìòù 名探偵コナン.
\n
PST test, link"
,
'edit_diff'
=>
"@@ -1,1 +1,3 @@
\n
-[https://a.com Test] foo
\n
+'''Random'''.
\n
+Some ''special'' chars: àèìòù 名探偵コナン.
\n
+[[Help:PST|]] test, [//www.b.com link]
\n
"
,
'edit_diff_pst'
=>
"@@ -1,1 +1,3 @@
\n
-[https://a.com Test] foo
\n
+'''Random'''.
\n
+Some ''special'' chars: àèìòù 名探偵コナン.
\n
+[[Help:PST|PST]] test, [//www.b.com link]
\n
"
,
'new_size'
=>
strlen
(
$new
),
'old_size'
=>
strlen
(
$old
),
'edit_delta'
=>
strlen
(
$new
)
-
strlen
(
$old
),
'added_lines'
=>
explode
(
"
\n
"
,
$new
),
'removed_lines'
=>
[
$old
],
'added_lines_pst'
=>
[
"'''Random'''."
,
"Some ''special'' chars: àèìòù 名探偵コナン."
,
'[[Help:PST|PST]] test, [//www.b.com link]'
],
'old_links'
=>
[
'https://a.com/'
],
'new_links'
=>
[
'https://www.b.com/'
],
'removed_links'
=>
[
'https://a.com/'
],
'added_links'
=>
[
'https://www.b.com/'
],
'page_last_edit_age'
=>
10
,
],
'params'
=>
[
'text'
=>
$new
,
'summary'
=>
$summary
],
'oldContent'
=>
new
WikitextContent
(
$old
),
];
$old
=
"'''Random'''.
\n
Some ''special'' chars: àèìòù 名探偵コナン.
\n
[[Help:PST|PST]] test, [//www.b.com link]"
;
$new
=
'[https://a.com Test] foo'
;
yield
'PST and special chars, reverse'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
$old
,
'old_content_model'
=>
'wikitext'
,
'new_wikitext'
=>
$new
,
'new_content_model'
=>
'wikitext'
,
'summary'
=>
$summary
,
'new_html'
=>
[
'Test</a>'
],
'new_pst'
=>
'[https://a.com Test] foo'
,
'new_text'
=>
'Test foo'
,
'edit_diff'
=>
"@@ -1,3 +1,1 @@
\n
-'''Random'''.
\n
-Some ''special'' chars: àèìòù 名探偵コナン.
\n
-[[Help:PST|PST]] test, [//www.b.com link]
\n
+[https://a.com Test] foo
\n
"
,
'edit_diff_pst'
=>
"@@ -1,3 +1,1 @@
\n
-'''Random'''.
\n
-Some ''special'' chars: àèìòù 名探偵コナン.
\n
-[[Help:PST|PST]] test, [//www.b.com link]
\n
+[https://a.com Test] foo
\n
"
,
'new_size'
=>
strlen
(
$new
),
'old_size'
=>
strlen
(
$old
),
'edit_delta'
=>
strlen
(
$new
)
-
strlen
(
$old
),
'added_lines'
=>
[
$new
],
'removed_lines'
=>
explode
(
"
\n
"
,
$old
),
'added_lines_pst'
=>
[
$new
],
'old_links'
=>
[
'https://www.b.com/'
],
'new_links'
=>
[
'https://a.com/'
],
'removed_links'
=>
[
'https://www.b.com/'
],
'added_links'
=>
[
'https://a.com/'
],
'page_last_edit_age'
=>
10
,
],
'params'
=>
[
'text'
=>
$new
,
'summary'
=>
$summary
],
'oldContent'
=>
new
WikitextContent
(
$old
),
];
// phpcs:enable Generic.Files.LineLength
$old
=
'This edit will be pretty smal'
;
$new
=
$old
.
'l'
;
yield
'Small edit'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
$old
,
'old_content_model'
=>
'wikitext'
,
'new_wikitext'
=>
$new
,
'new_content_model'
=>
'wikitext'
,
'summary'
=>
$summary
,
'new_html'
=>
[
"<p>This edit will be pretty small
\n
</p>"
],
'new_pst'
=>
$new
,
'new_text'
=>
$new
,
'edit_diff'
=>
"@@ -1,1 +1,1 @@
\n
-$old
\n
+$new
\n
"
,
'edit_diff_pst'
=>
"@@ -1,1 +1,1 @@
\n
-$old
\n
+$new
\n
"
,
'new_size'
=>
strlen
(
$new
),
'old_size'
=>
strlen
(
$old
),
'edit_delta'
=>
1
,
'removed_lines'
=>
[
$old
],
'added_lines'
=>
[
$new
],
'added_lines_pst'
=>
[
$new
],
'old_links'
=>
[],
'new_links'
=>
[],
'removed_links'
=>
[],
'added_links'
=>
[],
'page_last_edit_age'
=>
10
,
],
'params'
=>
[
'text'
=>
$new
,
'summary'
=>
$summary
],
'oldContent'
=>
new
WikitextContent
(
$old
),
];
yield
'content model change to wikitext'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
"{
\n\t\"
key
\"
:
\"
value
\"\n
}"
,
'old_content_model'
=>
'json'
,
'new_wikitext'
=>
'new test https://en.wikipedia.org'
,
'new_content_model'
=>
'wikitext'
,
'old_links'
=>
[],
'new_links'
=>
[
'https://en.wikipedia.org/'
],
'page_last_edit_age'
=>
10
,
],
'params'
=>
[
'text'
=>
'new test https://en.wikipedia.org'
,
'contentmodel'
=>
'wikitext'
,
],
'oldContent'
=>
new
JsonContent
(
FormatJson
::
encode
(
[
'key'
=>
'value'
]
)
),
];
yield
'content model change from wikitext'
=>
[
'expected'
=>
[
'action'
=>
'edit'
,
'old_wikitext'
=>
'test https://en.wikipedia.org'
,
'old_content_model'
=>
'wikitext'
,
'new_wikitext'
=>
'{"key": "value"}'
,
'new_content_model'
=>
'json'
,
'old_links'
=>
[
'https://en.wikipedia.org/'
],
'new_links'
=>
[],
'page_last_edit_age'
=>
10
,
],
'params'
=>
[
'text'
=>
'{"key": "value"}'
,
'contentmodel'
=>
'json'
,
],
'oldContent'
=>
new
WikitextContent
(
'test https://en.wikipedia.org'
),
];
}
/**
* @dataProvider provideEditVariables
* @covers \MediaWiki\Extension\AbuseFilter\Hooks\Handlers\FilteredActionsHandler
* @covers \MediaWiki\Extension\AbuseFilter\VariableGenerator\RunVariableGenerator
* @covers \MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGenerator
* @covers \MediaWiki\Extension\AbuseFilter\Variables\LazyVariableComputer
*/
public
function
testEditVariables
(
array
$expected
,
array
$params
,
?
Content
$oldContent
=
null
)
{
$time
=
time
();
MWTimestamp
::
setFakeTime
(
$time
);
$varHolder
=
null
;
$this
->
prepareServices
();
$this
->
setAbuseLoggerFactoryWithEavesdrop
(
$varHolder
);
$title
=
'My test page'
;
$page
=
$this
->
getNonexistingTestPage
(
$title
);
if
(
$oldContent
)
{
$status
=
$this
->
editPage
(
$page
,
$oldContent
,
'Creating test page'
);
$this
->
assertStatusGood
(
$status
);
}
$handler
=
new
FilteredActionsHandler
(
new
NullStatsdDataFactory
(),
AbuseFilterServices
::
getFilterRunnerFactory
(),
AbuseFilterServices
::
getVariableGeneratorFactory
(),
AbuseFilterServices
::
getEditRevUpdater
(),
AbuseFilterServices
::
getBlockedDomainFilter
(),
MediaWikiServices
::
getInstance
()->
getPermissionManager
(),
MediaWikiServices
::
getInstance
()->
getTitleFactory
(),
MediaWikiServices
::
getInstance
()->
getUserFactory
(),
MediaWikiServices
::
getInstance
()->
getTempUserConfig
()
);
$this
->
setTemporaryHook
(
'EditFilterMergedContent'
,
[
$handler
,
'onEditFilterMergedContent'
],
true
);
MWTimestamp
::
setFakeTime
(
$time
+
10
);
$ex
=
null
;
try
{
$this
->
doApiRequestWithToken
(
[
'action'
=>
'edit'
,
'title'
=>
$title
]
+
$params
);
}
catch
(
ApiUsageException
$ex
)
{
}
$this
->
assertNotNull
(
$ex
,
'Exception should be thrown'
);
$this
->
assertNotNull
(
$varHolder
,
'Variables should be set'
);
$export
=
AbuseFilterServices
::
getVariablesManager
()->
dumpAllVars
(
$varHolder
,
array_keys
(
$expected
)
);
$this
->
assertVariables
(
$expected
,
$export
);
}
public
static
function
provideAccountCreationVars
():
Generator
{
yield
'create account anonymously'
=>
[
'expected'
=>
[
'action'
=>
'createaccount'
,
'accountname'
=>
'New account'
,
],
'accountName'
=>
'New account'
,
];
yield
'create account by an existing user'
=>
[
'expected'
=>
[
'action'
=>
'createaccount'
,
'accountname'
=>
'New account'
,
'user_name'
=>
'Account creator'
,
'user_editcount'
=>
0
,
],
'accountName'
=>
'New account'
,
'autocreate'
=>
false
,
'creatorName'
=>
'Account creator'
];
yield
'autocreate an account'
=>
[
'expected'
=>
[
'action'
=>
'autocreateaccount'
,
'accountname'
=>
'New account'
,
],
'accountName'
=>
'New account'
,
'autocreate'
=>
true
,
];
yield
'autocreate a temporary account'
=>
[
'expected'
=>
[
'action'
=>
'autocreateaccount'
,
'user_unnamed_ip'
=>
'127.0.0.1'
,
],
'accountName'
=>
null
,
'autocreate'
=>
true
,
];
}
/**
* @dataProvider provideAccountCreationVars
* @covers \MediaWiki\Extension\AbuseFilter\AbuseFilterPreAuthenticationProvider
* @covers \MediaWiki\Extension\AbuseFilter\Hooks\Handlers\FilteredActionsHandler
* @covers \MediaWiki\Extension\AbuseFilter\VariableGenerator\RunVariableGenerator
* @covers \MediaWiki\Extension\AbuseFilter\VariableGenerator\VariableGenerator
*/
public
function
testAccountCreationVars
(
array
$expected
,
?
string
$accountName
,
bool
$autocreate
=
false
,
?
string
$creatorName
=
null
)
{
if
(
$accountName
===
null
)
{
// $accountName being null indicates a temporary user
$this
->
enableAutoCreateTempUser
();
}
$varHolder
=
null
;
$this
->
prepareServices
(
'user_unnamed_ip !== "foo"'
);
$this
->
setAbuseLoggerFactoryWithEavesdrop
(
$varHolder
);
$creator
=
null
;
$services
=
$this
->
getServiceContainer
();
if
(
$creatorName
!==
null
)
{
$creator
=
$services
->
getUserFactory
()->
newFromName
(
$creatorName
);
$creator
->
addToDatabase
();
}
// Verify that the AbuseFilterGenerateAccountCreationVars hook is called and provides the expected
// data to the handlers.
$hookCalled
=
false
;
$this
->
setTemporaryHook
(
'AbuseFilterGenerateAccountCreationVars'
,
function
(
$actualVars
,
UserIdentity
$actualCreator
,
UserIdentity
$actualCreatedUser
,
bool
$autocreated
,
?
RecentChange
$rc
)
use
(
$services
,
&
$hookCalled
,
$creator
,
$accountName
,
$autocreate
)
{
$this
->
assertNull
(
$rc
);
if
(
$accountName
===
null
)
{
// Lazy-assign the autocreated temporary username
if
(
$services
->
getTempUserConfig
()->
isTempName
(
$actualCreatedUser
->
getName
()
)
)
{
$accountName
=
$actualCreatedUser
->
getName
();
}
else
{
$this
->
fail
(
'Expected temporary user creation when $accountName is null'
);
}
}
if
(
$creator
!==
null
)
{
$this
->
assertTrue
(
$creator
->
equals
(
$actualCreator
)
);
}
elseif
(
!
$autocreated
)
{
// When not performed by another user, if the account is not autocreated then the
// performer is $accountName otherwise it's the IP.
$this
->
assertSame
(
$accountName
,
$actualCreator
->
getName
()
);
}
else
{
$this
->
assertSame
(
'127.0.0.1'
,
$actualCreator
->
getName
()
);
}
$this
->
assertSame
(
$accountName
,
$actualCreatedUser
->
getName
()
);
$this
->
assertSame
(
$autocreate
,
$autocreated
);
$hookCalled
=
true
;
}
);
$status
=
$this
->
createAccount
(
$accountName
,
$autocreate
,
$creator
);
$this
->
assertStatusNotOK
(
$status
);
$this
->
assertNotNull
(
$varHolder
,
'Variables should be set'
);
$export
=
AbuseFilterServices
::
getVariablesManager
()->
dumpAllVars
(
$varHolder
,
array_keys
(
$expected
)
);
$this
->
assertVariables
(
$expected
,
$export
);
if
(
$accountName
!==
null
&&
!
empty
(
$export
[
'user_unnamed_ip'
]
)
)
{
$this
->
fail
(
'user_unnamed_ip incorrectly exposed'
);
}
$this
->
assertTrue
(
$hookCalled
);
}
}
File Metadata
Details
Attached
Mime Type
text/x-php
Expires
Oct 4 2026, 09:06 (5 w, 2 d ago)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
52/d7/98c868b3ae01b7f9bf7000025d5b
Default Alt Text
ActionVariablesIntegrationTest.php (16 KB)
Attached To
Mode
rMWPROD MediaWiki Production
Attached
Detach File
Event Timeline
Log In to Comment