Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 46
0.00% covered (danger)
0.00%
0 / 2
CRAP
0.00% covered (danger)
0.00%
0 / 1
Send2FAWarningNotifications
0.00% covered (danger)
0.00%
0 / 45
0.00% covered (danger)
0.00%
0 / 2
156
0.00% covered (danger)
0.00%
0 / 1
 __construct
0.00% covered (danger)
0.00%
0 / 6
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
132
1<?php
2
3namespace MediaWiki\Extension\WikimediaMessages\Maintenance;
4
5use MediaWiki\Maintenance\Maintenance;
6use MediaWiki\Notification\Notification;
7use MediaWiki\Notification\RecipientSet;
8use MediaWiki\WikiMap\WikiMap;
9
10class Send2FAWarningNotifications extends Maintenance {
11
12    public function __construct() {
13        parent::__construct();
14        $this->requireExtension( 'WikimediaMessages' );
15        $this->addDescription( 'Send notifications about mandatory 2FA to users (read from stdin)' );
16        $this->addOption( 'deadline', 'Deadline for 2FA compliance in YYYYMMDD format', true, true );
17        $this->addOption( 'input-file', 'Name of file with users to notify. If absent, use stdin', false, true );
18        $this->setBatchSize( 100 );
19    }
20
21    public function execute() {
22        $services = $this->getServiceContainer();
23        $userIdentityLookup = $services->getUserIdentityLookup();
24        $notifications = $services->getNotificationService();
25
26        $deadline = $this->getOption( 'deadline' );
27        if ( !preg_match( '/^\d{8}$/', $deadline ) ) {
28            $this->error( "Invalid deadline format, expected YYYYMMDD: $deadline" );
29            return;
30        }
31        // Notifications expect a timestamp, so append time to the date and convert to timestamp
32        $deadline .= '000000';
33
34        if ( $this->hasOption( 'input-file' ) ) {
35            $fileName = $this->getOption( 'input-file' );
36            if ( !file_exists( $fileName ) || !is_readable( $fileName ) ) {
37                $this->error( "Input file does not exist or is not readable: $fileName" );
38                return;
39            }
40            $text = file_get_contents( $fileName );
41        } else {
42            $text = $this->getStdin( Maintenance::STDIN_ALL );
43        }
44        $success = 0;
45        foreach ( explode( "\n", $text ) as $i => $userName ) {
46            $userName = trim( $userName );
47            if ( $userName === '' ) {
48                continue;
49            }
50            if ( str_contains( $userName, '@' ) ) {
51                [ $userName, $wikiId ] = explode( '@', $userName, 2 );
52                $userName = trim( $userName );
53                $wikiId = trim( $wikiId );
54                if ( !WikiMap::isCurrentWikiId( $wikiId ) ) {
55                    continue;
56                }
57            }
58            $userIdentity = $userIdentityLookup->getUserIdentityByName( $userName );
59            if ( !$userIdentity ) {
60                $this->error( ( $i + 1 ) . ": User is invalid or not registered: $userName" );
61                continue;
62            }
63            $notifications->notify(
64                new Notification( '2fa-warning', [ 'deadline' => $deadline ] ),
65                new RecipientSet( $userIdentity )
66            );
67            $this->output( ( $i + 1 ) . ": $userName\n" );
68            $success++;
69            if ( $success % $this->getBatchSize() === 0 ) {
70                $this->waitForReplication();
71            }
72        }
73
74        $this->output( "Notified $success users.\n" );
75    }
76}
77
78$maintClass = Send2FAWarningNotifications::class;