Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
81.16% |
56 / 69 |
|
33.33% |
1 / 3 |
CRAP | |
0.00% |
0 / 1 |
BlockMetricsHooks | |
81.16% |
56 / 69 |
|
33.33% |
1 / 3 |
17.71 | |
0.00% |
0 / 1 |
__construct | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
onPermissionErrorAudit | |
81.82% |
54 / 66 |
|
0.00% |
0 / 1 |
15.18 | |||
submitEvent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace WikimediaEvents\BlockMetrics; |
4 | |
5 | use MediaWiki\Block\Block; |
6 | use MediaWiki\Extension\EventBus\EventFactory; |
7 | use MediaWiki\Extension\EventLogging\EventLogging; |
8 | use MediaWiki\Linker\LinkTarget; |
9 | use MediaWiki\Logger\LoggerFactory; |
10 | use MediaWiki\MediaWikiServices; |
11 | use MediaWiki\Permissions\Hook\PermissionErrorAuditHook; |
12 | use MediaWiki\Permissions\PermissionManager; |
13 | use MediaWiki\User\UserFactory; |
14 | use MediaWiki\User\UserIdentity; |
15 | use Message; |
16 | use RequestContext; |
17 | |
18 | /** |
19 | * Hooks related to T303995. |
20 | */ |
21 | class BlockMetricsHooks implements PermissionErrorAuditHook { |
22 | |
23 | public const SCHEMA = '/analytics/mediawiki/accountcreation/block/4.0.0'; |
24 | |
25 | /** @var UserFactory */ |
26 | private $userFactory; |
27 | |
28 | /** @var EventFactory */ |
29 | private $eventFactory; |
30 | |
31 | /** |
32 | * @param UserFactory $userFactory |
33 | * @param EventFactory $eventFactory |
34 | */ |
35 | public function __construct( |
36 | UserFactory $userFactory, |
37 | EventFactory $eventFactory |
38 | ) { |
39 | $this->userFactory = $userFactory; |
40 | $this->eventFactory = $eventFactory; |
41 | } |
42 | |
43 | /** @inheritDoc */ |
44 | public function onPermissionErrorAudit( |
45 | LinkTarget $title, |
46 | UserIdentity $user, |
47 | string $action, |
48 | string $rigor, |
49 | array $errors |
50 | ): void { |
51 | // Ignore RIGOR_QUICK checks for performance; those won't check blocks anyway. |
52 | if ( $action !== 'createaccount' || $rigor === PermissionManager::RIGOR_QUICK ) { |
53 | return; |
54 | } |
55 | // Possible block error keys from Block\BlockErrorFormatter::getBlockErrorMessageKey() |
56 | $blockedErrorKeys = [ |
57 | 'blockedtext', |
58 | 'autoblockedtext', |
59 | 'blockedtext-partial', |
60 | 'systemblockedtext', |
61 | 'blockedtext-composite' |
62 | ]; |
63 | // Possible block error keys from GlobalBlocking extension GlobalBlocking::getUserBlockDetails() |
64 | $globalBlockedErrorKeys = [ |
65 | 'globalblocking-ipblocked', |
66 | 'globalblocking-ipblocked-range', |
67 | 'globalblocking-ipblocked-xff', |
68 | // WikimediaMessages versions |
69 | 'wikimedia-globalblocking-ipblocked', |
70 | 'wikimedia-globalblocking-ipblocked-range', |
71 | 'wikimedia-globalblocking-ipblocked-xff', |
72 | ]; |
73 | $isApi = defined( 'MW_API' ) || defined( 'MW_REST_API' ); |
74 | |
75 | $blockedErrorMsgs = $globalBlockedErrorMsgs = []; |
76 | foreach ( $errors as $error ) { |
77 | $errorMsg = Message::newFromSpecifier( $error ); |
78 | $errorKey = $errorMsg->getKey(); |
79 | if ( in_array( $errorKey, $blockedErrorKeys, true ) ) { |
80 | $blockedErrorMsgs[] = $errorMsg; |
81 | } elseif ( in_array( $errorKey, $globalBlockedErrorKeys, true ) ) { |
82 | $globalBlockedErrorMsgs[] = $errorMsg; |
83 | } |
84 | } |
85 | $allErrorMsgs = array_merge( $blockedErrorMsgs, $globalBlockedErrorMsgs ); |
86 | |
87 | if ( !$allErrorMsgs ) { |
88 | return; |
89 | } |
90 | |
91 | $user = $this->userFactory->newFromUserIdentity( $user ); |
92 | |
93 | $block = null; |
94 | // Prefer the local block over the global one if both are set. This is somewhat arbitrary. |
95 | if ( $blockedErrorMsgs ) { |
96 | $block = MediaWikiServices::getInstance()->getBlockManager() |
97 | ->getCreateAccountBlock( $user, RequestContext::getMain()->getRequest(), true ); |
98 | } elseif ( $globalBlockedErrorMsgs ) { |
99 | $block = $user->getGlobalBlock(); |
100 | } |
101 | |
102 | if ( $block ) { |
103 | $context = RequestContext::getMain(); |
104 | foreach ( $allErrorMsgs as $msg ) { |
105 | $msg->setContext( $context )->useDatabase( false )->inLanguage( 'en' ); |
106 | } |
107 | $rawExpiry = $block->getExpiry(); |
108 | if ( wfIsInfinity( $rawExpiry ) ) { |
109 | $expiry = 'infinity'; |
110 | } else { |
111 | $expiry = wfTimestamp( TS_ISO_8601, $rawExpiry ); |
112 | } |
113 | $event = [ |
114 | '$schema' => self::SCHEMA, |
115 | 'block_id' => json_encode( $block->getIdentifier() ), |
116 | // @phan-suppress-next-line PhanTypeMismatchDimFetchNullable |
117 | 'block_type' => Block::BLOCK_TYPES[ $block->getType() ] ?? 'other', |
118 | 'block_expiry' => $expiry, |
119 | 'block_scope' => $blockedErrorMsgs ? 'local' : 'global', |
120 | 'error_message_keys' => array_map( static function ( Message $msg ) { |
121 | return $msg->getKey(); |
122 | }, $allErrorMsgs ), |
123 | 'error_messages' => array_map( static function ( Message $msg ) { |
124 | return $msg->plain(); |
125 | }, $allErrorMsgs ), |
126 | 'user_ip' => $user->getRequest()->getIP(), |
127 | 'is_api' => $isApi, |
128 | ]; |
129 | $event += $this->eventFactory->createMediaWikiCommonAttrs( $user ); |
130 | $this->submitEvent( 'mediawiki.accountcreation_block', $event ); |
131 | } else { |
132 | LoggerFactory::getInstance( 'WikimediaEvents' )->warning( 'Could not find block', [ |
133 | 'errorKeys' => implode( ',', array_map( static function ( Message $msg ) { |
134 | return $msg->getKey(); |
135 | }, $allErrorMsgs ) ), |
136 | ] ); |
137 | } |
138 | } |
139 | |
140 | /** |
141 | * PHPUnit test helper that allows mocking out the EventLogging dependency. |
142 | * @param string $streamName |
143 | * @param array $event |
144 | * @return void |
145 | */ |
146 | protected function submitEvent( string $streamName, array $event ): void { |
147 | EventLogging::submit( $streamName, $event ); |
148 | } |
149 | |
150 | } |