3 * Session provider for bot passwords
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
24 namespace MediaWiki\Session
;
26 use InvalidArgumentException
;
27 use MediaWiki\MainConfigNames
;
28 use MediaWiki\Permissions\GrantsInfo
;
29 use MediaWiki\Request\WebRequest
;
30 use MediaWiki\User\BotPassword
;
31 use MediaWiki\User\User
;
35 * Session provider for bot passwords
38 class BotPasswordSessionProvider
extends ImmutableSessionProviderWithCookie
{
39 private GrantsInfo
$grantsInfo;
41 /** @var bool Whether the current request is an API request. */
42 private $isApiRequest;
45 * @param GrantsInfo $grantsInfo
46 * @param array $params Keys include:
47 * - priority: (required) Set the priority
48 * - sessionCookieName: Session cookie name. Default is '_BPsession'.
49 * - sessionCookieOptions: Options to pass to WebResponse::setCookie().
50 * - isApiRequest: Whether the current request is an API request. Should be only set in tests.
52 public function __construct( GrantsInfo
$grantsInfo, array $params = [] ) {
53 if ( !isset( $params['sessionCookieName'] ) ) {
54 $params['sessionCookieName'] = '_BPsession';
56 parent
::__construct( $params );
58 if ( !isset( $params['priority'] ) ) {
59 throw new InvalidArgumentException( __METHOD__
. ': priority must be specified' );
61 if ( $params['priority'] < SessionInfo
::MIN_PRIORITY ||
62 $params['priority'] > SessionInfo
::MAX_PRIORITY
64 throw new InvalidArgumentException( __METHOD__
. ': Invalid priority' );
67 $this->priority
= $params['priority'];
69 $this->grantsInfo
= $grantsInfo;
71 $this->isApiRequest
= $params['isApiRequest']
72 ??
( defined( 'MW_API' ) ||
defined( 'MW_REST_API' ) );
75 public function provideSessionInfo( WebRequest
$request ) {
76 // Only relevant for the (Action or REST) API
77 if ( !$this->isApiRequest
) {
82 if ( !$this->getConfig()->get( MainConfigNames
::EnableBotPasswords
) ) {
87 $id = $this->getSessionIdFromCookie( $request );
92 return new SessionInfo( $this->priority
, [
99 public function newSessionInfo( $id = null ) {
100 // We don't activate by default
105 * Create a new session for a request
107 * @param BotPassword $bp
108 * @param WebRequest $request
111 public function newSessionForRequest( User
$user, BotPassword
$bp, WebRequest
$request ) {
112 $id = $this->getSessionIdFromCookie( $request );
113 $info = new SessionInfo( SessionInfo
::MAX_PRIORITY
, [
116 'userInfo' => UserInfo
::newFromUser( $user, true ),
117 'persisted' => $id !== null,
119 'centralId' => $bp->getUserCentralId(),
120 'appId' => $bp->getAppId(),
121 'token' => $bp->getToken(),
122 'rights' => $this->grantsInfo
->getGrantRights( $bp->getGrants() ),
123 'restrictions' => $bp->getRestrictions()->toJson(),
126 $session = $this->getManager()->getSessionFromInfo( $info, $request );
133 * @phan-param array &$metadata
135 public function refreshSessionInfo( SessionInfo
$info, WebRequest
$request, &$metadata ) {
136 $missingKeys = array_diff(
137 [ 'centralId', 'appId', 'token' ],
138 array_keys( $metadata )
140 if ( $missingKeys ) {
141 $this->logger
->info( 'Session "{session}": Missing metadata: {missing}', [
142 'session' => $info->__toString(),
143 'missing' => implode( ', ', $missingKeys ),
148 $bp = BotPassword
::newFromCentralId( $metadata['centralId'], $metadata['appId'] );
151 'Session "{session}": No BotPassword for {centralId} {appId}',
153 'session' => $info->__toString(),
154 'centralId' => $metadata['centralId'],
155 'appId' => $metadata['appId'],
160 if ( !hash_equals( $metadata['token'], $bp->getToken() ) ) {
161 $this->logger
->info( 'Session "{session}": BotPassword token check failed', [
162 'session' => $info->__toString(),
163 'centralId' => $metadata['centralId'],
164 'appId' => $metadata['appId'],
169 $status = $bp->getRestrictions()->check( $request );
170 if ( !$status->isOK() ) {
172 'Session "{session}": Restrictions check failed',
174 'session' => $info->__toString(),
175 'restrictions' => $status->getValue(),
176 'centralId' => $metadata['centralId'],
177 'appId' => $metadata['appId'],
182 // Update saved rights
183 $metadata['rights'] = $this->grantsInfo
->getGrantRights( $bp->getGrants() );
189 * @codeCoverageIgnore
192 public function preventSessionsForUser( $username ) {
193 BotPassword
::removeAllPasswordsForUser( $username );
196 public function getAllowedUserRights( SessionBackend
$backend ) {
197 if ( $backend->getProvider() !== $this ) {
198 throw new InvalidArgumentException( 'Backend\'s provider isn\'t $this' );
200 $data = $backend->getProviderMetadata();
201 if ( $data && isset( $data['rights'] ) && is_array( $data['rights'] ) ) {
202 return $data['rights'];
205 // Should never happen
206 $this->logger
->debug( __METHOD__
. ': No provider metadata, returning no rights allowed' );
210 public function getRestrictions( ?
array $data ): ?MWRestrictions
{
211 if ( $data && isset( $data['restrictions'] ) && is_string( $data['restrictions'] ) ) {
213 return MWRestrictions
::newFromJson( $data['restrictions'] );
214 } catch ( InvalidArgumentException
$e ) {
215 $this->logger
->warning( __METHOD__
. ': Failed to parse restrictions: {restrictions}', [
216 'restrictions' => $data['restrictions']