Send a notification when 2FA is disabled

Notify users when 2FA is disabled on their account in case something was
fishy about it. This notification is a "system" notification that will
be displayed in the web UI and sent over email. It can't be opted out of
as a preference.

The notification links to Special:Preferences, where users can see their
2FA status and re-enable it if they want. A secondary help link goes to
[[mw:Help:Two-factor authentication]], but can be overridden by
adjusting the "oathauth-notifications-disable-helplink" message. The
notification text is different based on whether the user disabled 2FA on
their own, or an admin used the special page or a maint script to do it.

On Wikimedia wikis, we'll use the WikimediaMessages extension to
customize the messages.

The Echo (Notifications) extension is not required, this will gracefully
do nothing if it's not enabled.

Bug: T210075
Bug: T210963
Change-Id: I99077ea082b8483cc4fd77573a0d00fa98201f15
This commit is contained in:
Kunal Mehta 2022-02-16 01:15:02 -08:00
parent 4cc5cbe4ad
commit 329c3133d6
10 changed files with 184 additions and 6 deletions

View file

@ -1,3 +1,17 @@
<?php
return require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php';
$cfg = require __DIR__ . '/../vendor/mediawiki/mediawiki-phan-config/src/config.php';
$cfg['directory_list'] = array_merge(
$cfg['directory_list'],
[
'../../extensions/Echo',
]
);
$cfg['exclude_analysis_directory_list'] = array_merge(
$cfg['exclude_analysis_directory_list'],
[
'../../extensions/Echo',
]
);
return $cfg;

View file

@ -46,7 +46,8 @@
"GetPreferences": "main",
"getUserPermissionsErrors": "main",
"UserEffectiveGroups": "main",
"UserGetRights": "main"
"UserGetRights": "main",
"BeforeCreateEchoEvent": "\\MediaWiki\\Extension\\OATHAuth\\Notifications\\Manager::onBeforeCreateEchoEvent"
},
"HookHandlers": {
"main": {

View file

@ -82,6 +82,11 @@
"oathauth-switch-method-warning": "By switching to $2 two-factor authentication method, current method ($1) will be disabled, and all the data associated with the current authentication method will be deleted",
"oathauth-totp-disable-warning": "You will no longer be able to use the authentication device registered with this account. All scratch-tokens associated with this account will be invalidated.",
"oathauth-invalidrequest": "Invalid request",
"notification-header-oathauth-disable": "Two-factor authentication has been disabled on {{GENDER:$2|your account}}.",
"notification-body-oathauth-disable": "If {{GENDER:$2|you}} did not do this, {{GENDER:$2|your account}} may have been compromised.",
"notification-body-oathauth-disable-other": "If {{GENDER:$2|you}} did not request this, {{GENDER:$2|you}} should contact an administrator.",
"oathauth-notifications-disable-help": "Help",
"oathauth-notifications-disable-helplink": "mw:Special:MyLanguage/Help:Two-factor authentication",
"oathauth-verify-enabled": "{{GENDER:$1|$1}} has two-factor authentication enabled.",
"oathauth-verify-disabled": "{{GENDER:$1|$1}} does not have two-factor authentication enabled.",
"oathauth-prefs-disabledgroups": "Disabled {{PLURAL:$1|group|groups}}:",

View file

@ -94,6 +94,11 @@
"oathauth-switch-method-warning": "Generic message warning the user of token/data loss when switching to an alternative method.\n$1 - Current method name, $2 - Name of the method that is being switched to",
"oathauth-totp-disable-warning": "TOTP specific warning message when disabling/switching to alternative 2FA method",
"oathauth-invalidrequest": "Generic error message that is displayed when request cannot be processed due to an unpredicted reason",
"notification-header-oathauth-disable": "Notification header for when two-factor authentication has been disabled.\n$2 - Name of user for GENDER",
"notification-body-oathauth-disable": "Notification body text for when two-factor authentication has been disabled.\n$2 - Name of user for GENDER",
"notification-body-oathauth-disable-other": "Notification body text for when two-factor authentication has been disabled by an administrator or sysadmin.\n$2 - Name of user for GENDER.",
"oathauth-notifications-disable-help": "Link text for the help link in the notification",
"oathauth-notifications-disable-helplink": "{{notranslate}}",
"oathauth-verify-enabled": "Notice that a user has 2FA enabled, shown on success at [[Special:VerifyOATHForUser]].\n$1 - Name of user",
"oathauth-verify-disabled": "Notice that a user does not have 2FA enabled, shown on success at [[Special:VerifyOATHForUser]].\n$1 - Name of user",
"oathauth-prefs-disabledgroups": "Label on Special:Preferences for groups in which the user's membership has been disabled for a lack of two-factor authentication.\n$1 - Number of groups",

View file

@ -34,7 +34,7 @@ class DisableOATHAuthForUser extends Maintenance {
$this->fatalError( "User $username doesn't have OATHAuth enabled!" );
}
$repo->remove( $oathUser, 'Maintenance script' );
$repo->remove( $oathUser, 'Maintenance script', false );
// Kill all existing sessions. If this disable was social-engineered by an attacker,
// the legitimate user will hopefully login again and notice that the second factor
// is missing or different, and alert the operators.

View file

@ -65,7 +65,7 @@ class TOTPDisableForm extends OATHAuthOOUIHTMLForm implements IManageForm {
}
$this->oathUser->setKeys();
$this->oathRepo->remove( $this->oathUser, $this->getRequest()->getIP() );
$this->oathRepo->remove( $this->oathUser, $this->getRequest()->getIP(), true );
return true;
}

View file

@ -0,0 +1,72 @@
<?php
/**
* Copyright (C) 2022 Kunal Mehta <legoktm@debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
namespace MediaWiki\Extension\OATHAuth\Notifications;
use EchoEventPresentationModel;
use SpecialPage;
use Title;
class DisablePresentationModel extends EchoEventPresentationModel {
/**
* @inheritDoc
*/
public function getIconType() {
return 'site';
}
/**
* @inheritDoc
*/
public function getPrimaryLink() {
return [
'url' => SpecialPage::getTitleFor( 'Preferences' )->getLocalURL(),
'label' => $this->msg( 'oathauth-notifications-disable-primary' )->text()
];
}
/**
* @inheritDoc
*/
public function getSecondaryLinks() {
$link = $this->msg( 'oathauth-notifications-disable-helplink' )->inContentLanguage();
$title = Title::newFromText( $link->plain() );
if ( !$title ) {
// Invalid title, skip
return [];
}
return [ [
'url' => $title->getLocalURL(),
'label' => $this->msg( 'oathauth-notifications-disable-help' )->text(),
'icon' => 'help',
] ];
}
/**
* @inheritDoc
*/
public function getBodyMessage() {
$self = $this->event->getExtraParam( 'self', true );
$message = $this->event->getExtraParam( 'self', true )
? 'notification-body-oathauth-disable'
: 'notification-body-oathauth-disable-other';
return $this->getMessageWithAgent( $message );
}
}

View file

@ -0,0 +1,79 @@
<?php
/**
* Copyright (C) 2022 Kunal Mehta <legoktm@debian.org>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*/
namespace MediaWiki\Extension\OATHAuth\Notifications;
use EchoEvent;
use ExtensionRegistry;
use MediaWiki\Extension\OATHAuth\OATHUser;
use SpecialPage;
/**
* Manages logic for configuring and sending out notifications with Echo
*/
class Manager {
/**
* Whether Echo is installed and can be used
*
* @return bool
*/
private static function isEnabled(): bool {
return ExtensionRegistry::getInstance()->isLoaded( 'Echo' );
}
/**
* Send a notification that 2FA has been disabled
*
* @param OATHUser $oUser
* @param bool $self Whether they disabled it themselves
*/
public static function notifyDisabled( OATHUser $oUser, bool $self ) {
if ( !self::isEnabled() ) {
return;
}
EchoEvent::create( [
'type' => 'oathauth-disable',
'title' => SpecialPage::getTitleFor( 'Preferences' ),
'agent' => $oUser->getUser(),
'extra' => [
'self' => $self,
]
] );
}
/**
* Hook: BeforeCreateEchoEvent
*
* Configure our notification types. We don't register a category since
* these are all "system" messages that cannot be disabled.
*
* @param array &$notifications
*/
public static function onBeforeCreateEchoEvent( &$notifications ) {
$notifications['oathauth-disable'] = [
'category' => 'system',
'group' => 'negative',
'section' => 'alert',
'presentation-model' => DisablePresentationModel::class,
'canNotifyAgent' => true,
'user-locators' => [ 'EchoUserLocator::locateEventAgent' ],
];
}
}

View file

@ -168,8 +168,9 @@ class OATHUserRepository {
/**
* @param OATHUser $user
* @param string $clientInfo
* @param bool $self Whether they disabled it themselves
*/
public function remove( OATHUser $user, $clientInfo ) {
public function remove( OATHUser $user, $clientInfo, bool $self ) {
$this->getDB( DB_PRIMARY )->delete(
'oathauth_users',
[ 'id' => MediaWikiServices::getInstance()
@ -186,6 +187,7 @@ class OATHUserRepository {
'user' => $userName,
'clientip' => $clientInfo,
] );
Notifications\Manager::notifyDisabled( $user, $self );
}
/**

View file

@ -136,7 +136,7 @@ class DisableOATHForUser extends FormSpecialPage {
}
$oathUser->disable();
$this->userRepo->remove( $oathUser, $this->getRequest()->getIP() );
$this->userRepo->remove( $oathUser, $this->getRequest()->getIP(), false );
$logEntry = new ManualLogEntry( 'oath', 'disable-other' );
$logEntry->setPerformer( $this->getUser() );