Localisation updates from https://translatewiki.net.
[mediawiki.git] / includes / session / BotPasswordSessionProvider.php
blob90c1ea1927e96836b90ff0ea19a3a96cac69a647
1 <?php
2 /**
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
20 * @file
21 * @ingroup Session
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;
32 use MWRestrictions;
34 /**
35 * Session provider for bot passwords
36 * @since 1.27
38 class BotPasswordSessionProvider extends ImmutableSessionProviderWithCookie {
39 private GrantsInfo $grantsInfo;
41 /** @var bool Whether the current request is an API request. */
42 private $isApiRequest;
44 /**
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
63 ) {
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 ) {
78 return null;
81 // Enabled?
82 if ( !$this->getConfig()->get( MainConfigNames::EnableBotPasswords ) ) {
83 return null;
86 // Have a session ID?
87 $id = $this->getSessionIdFromCookie( $request );
88 if ( $id === null ) {
89 return null;
92 return new SessionInfo( $this->priority, [
93 'provider' => $this,
94 'id' => $id,
95 'persisted' => true
96 ] );
99 public function newSessionInfo( $id = null ) {
100 // We don't activate by default
101 return null;
105 * Create a new session for a request
106 * @param User $user
107 * @param BotPassword $bp
108 * @param WebRequest $request
109 * @return Session
111 public function newSessionForRequest( User $user, BotPassword $bp, WebRequest $request ) {
112 $id = $this->getSessionIdFromCookie( $request );
113 $info = new SessionInfo( SessionInfo::MAX_PRIORITY, [
114 'provider' => $this,
115 'id' => $id,
116 'userInfo' => UserInfo::newFromUser( $user, true ),
117 'persisted' => $id !== null,
118 'metadata' => [
119 'centralId' => $bp->getUserCentralId(),
120 'appId' => $bp->getAppId(),
121 'token' => $bp->getToken(),
122 'rights' => $this->grantsInfo->getGrantRights( $bp->getGrants() ),
123 'restrictions' => $bp->getRestrictions()->toJson(),
125 ] );
126 $session = $this->getManager()->getSessionFromInfo( $info, $request );
127 $session->persist();
128 return $session;
132 * @inheritDoc
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 ),
144 ] );
145 return false;
148 $bp = BotPassword::newFromCentralId( $metadata['centralId'], $metadata['appId'] );
149 if ( !$bp ) {
150 $this->logger->info(
151 'Session "{session}": No BotPassword for {centralId} {appId}',
153 'session' => $info->__toString(),
154 'centralId' => $metadata['centralId'],
155 'appId' => $metadata['appId'],
156 ] );
157 return false;
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'],
165 ] );
166 return false;
169 $status = $bp->getRestrictions()->check( $request );
170 if ( !$status->isOK() ) {
171 $this->logger->info(
172 'Session "{session}": Restrictions check failed',
174 'session' => $info->__toString(),
175 'restrictions' => $status->getValue(),
176 'centralId' => $metadata['centralId'],
177 'appId' => $metadata['appId'],
178 ] );
179 return false;
182 // Update saved rights
183 $metadata['rights'] = $this->grantsInfo->getGrantRights( $bp->getGrants() );
185 return true;
189 * @codeCoverageIgnore
190 * @inheritDoc
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' );
207 return [];
210 public function getRestrictions( ?array $data ): ?MWRestrictions {
211 if ( $data && isset( $data['restrictions'] ) && is_string( $data['restrictions'] ) ) {
212 try {
213 return MWRestrictions::newFromJson( $data['restrictions'] );
214 } catch ( InvalidArgumentException $e ) {
215 $this->logger->warning( __METHOD__ . ': Failed to parse restrictions: {restrictions}', [
216 'restrictions' => $data['restrictions']
217 ] );
218 return null;
221 return null;