Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
13 / 13
CRAP
100.00% covered (success)
100.00%
1 / 1
SessionInfo
100.00% covered (success)
100.00%
66 / 66
100.00% covered (success)
100.00%
13 / 13
33
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
1 / 1
19
 getProvider
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getId
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isIdSafe
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 forceUse
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPriority
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getUserInfo
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wasPersisted
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getProviderMetadata
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 wasRemembered
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 forceHTTPS
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __toString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
3
 compare
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
1<?php
2/**
3 * MediaWiki session info
4 *
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.
9 *
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.
14 *
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
19 *
20 * @file
21 * @ingroup Session
22 */
23
24namespace MediaWiki\Session;
25
26use InvalidArgumentException;
27use Stringable;
28
29/**
30 * Value object returned by SessionProvider
31 *
32 * This holds the data necessary to construct a Session.
33 * May require services to be injected into the constructor.
34 *
35 * @newable
36 *
37 * @ingroup Session
38 * @since 1.27
39 */
40class SessionInfo implements Stringable {
41    /** Minimum allowed priority */
42    public const MIN_PRIORITY = 1;
43
44    /** Maximum allowed priority */
45    public const MAX_PRIORITY = 100;
46
47    /** @var SessionProvider|null */
48    private $provider;
49
50    /** @var string */
51    private $id;
52
53    /** @var int */
54    private $priority;
55
56    /** @var UserInfo|null */
57    private $userInfo = null;
58
59    /** @var bool */
60    private $persisted = false;
61
62    /** @var bool */
63    private $remembered = false;
64
65    /** @var bool */
66    private $forceHTTPS = false;
67
68    /** @var bool */
69    private $idIsSafe = false;
70
71    /** @var bool */
72    private $forceUse = false;
73
74    /** @var array|null */
75    private $providerMetadata = null;
76
77    /**
78     * @stable to call
79     *
80     * @param int $priority Session priority
81     * @param array $data
82     *  - provider: (SessionProvider|null) If not given, the provider will be
83     *    determined from the saved session data.
84     *  - id: (string|null) Session ID
85     *  - userInfo: (UserInfo|null) User known from the request. If
86     *    $provider->canChangeUser() is false, a verified user
87     *    must be provided.
88     *  - persisted: (bool) Whether this session was persisted
89     *  - remembered: (bool) Whether the verified user was remembered.
90     *    Defaults to true.
91     *  - forceHTTPS: (bool) Whether to force HTTPS for this session. This is
92     *    ignored if $wgForceHTTPS is true.
93     *  - metadata: (array) Provider metadata, to be returned by
94     *    Session::getProviderMetadata(). See SessionProvider::mergeMetadata()
95     *    and SessionProvider::refreshSessionInfo().
96     *  - idIsSafe: (bool) Set true if the 'id' did not come from the user.
97     *    Generally you'll use this from SessionProvider::newEmptySession(),
98     *    and not from any other method.
99     *  - forceUse: (bool) Set true if the 'id' is from
100     *    SessionProvider::hashToSessionId() to delete conflicting session
101     *    store data instead of discarding this SessionInfo. Ignored unless
102     *    both 'provider' and 'id' are given.
103     *  - copyFrom: (SessionInfo) SessionInfo to copy other data items from.
104     */
105    public function __construct( $priority, array $data ) {
106        if ( $priority < self::MIN_PRIORITY || $priority > self::MAX_PRIORITY ) {
107            throw new InvalidArgumentException( 'Invalid priority' );
108        }
109
110        if ( isset( $data['copyFrom'] ) ) {
111            $from = $data['copyFrom'];
112            if ( !$from instanceof SessionInfo ) {
113                throw new InvalidArgumentException( 'Invalid copyFrom' );
114            }
115            $data += [
116                'provider' => $from->provider,
117                'id' => $from->id,
118                'userInfo' => $from->userInfo,
119                'persisted' => $from->persisted,
120                'remembered' => $from->remembered,
121                'forceHTTPS' => $from->forceHTTPS,
122                'metadata' => $from->providerMetadata,
123                'idIsSafe' => $from->idIsSafe,
124                'forceUse' => $from->forceUse,
125                // @codeCoverageIgnoreStart
126            ];
127            // @codeCoverageIgnoreEnd
128        } else {
129            $data += [
130                'provider' => null,
131                'id' => null,
132                'userInfo' => null,
133                'persisted' => false,
134                'remembered' => true,
135                'forceHTTPS' => false,
136                'metadata' => null,
137                'idIsSafe' => false,
138                'forceUse' => false,
139                // @codeCoverageIgnoreStart
140            ];
141            // @codeCoverageIgnoreEnd
142        }
143
144        if ( $data['id'] !== null && !SessionManager::validateSessionId( $data['id'] ) ) {
145            throw new InvalidArgumentException( 'Invalid session ID' );
146        }
147
148        if ( $data['userInfo'] !== null && !$data['userInfo'] instanceof UserInfo ) {
149            throw new InvalidArgumentException( 'Invalid userInfo' );
150        }
151
152        if ( !$data['provider'] && $data['id'] === null ) {
153            throw new InvalidArgumentException(
154                'Must supply an ID when no provider is given'
155            );
156        }
157
158        if ( $data['metadata'] !== null && !is_array( $data['metadata'] ) ) {
159            throw new InvalidArgumentException( 'Invalid metadata' );
160        }
161
162        $this->provider = $data['provider'];
163        if ( $data['id'] !== null ) {
164            $this->id = $data['id'];
165            $this->idIsSafe = $data['idIsSafe'];
166            $this->forceUse = $data['forceUse'] && $this->provider;
167        } else {
168            $this->id = $this->provider->getManager()->generateSessionId();
169            $this->idIsSafe = true;
170            $this->forceUse = false;
171        }
172        $this->priority = (int)$priority;
173        $this->userInfo = $data['userInfo'];
174        $this->persisted = (bool)$data['persisted'];
175        if ( $data['provider'] !== null ) {
176            if ( $this->userInfo !== null && !$this->userInfo->isAnon() && $this->userInfo->isVerified() ) {
177                $this->remembered = (bool)$data['remembered'];
178            }
179            $this->providerMetadata = $data['metadata'];
180        }
181        $this->forceHTTPS = (bool)$data['forceHTTPS'];
182    }
183
184    /**
185     * Return the provider
186     * @return SessionProvider|null
187     */
188    final public function getProvider() {
189        return $this->provider;
190    }
191
192    /**
193     * Return the session ID
194     * @return string
195     */
196    final public function getId() {
197        return $this->id;
198    }
199
200    /**
201     * Indicate whether the ID is "safe"
202     *
203     * The ID is safe in the following cases:
204     * - The ID was randomly generated by the constructor.
205     * - The ID was found in the backend data store.
206     * - $this->getProvider()->persistsSessionId() is false.
207     * - The constructor was explicitly told it's safe using the 'idIsSafe'
208     *   parameter.
209     *
210     * @return bool
211     */
212    final public function isIdSafe() {
213        return $this->idIsSafe;
214    }
215
216    /**
217     * Force use of this SessionInfo if validation fails
218     *
219     * The normal behavior is to discard the SessionInfo if validation against
220     * the data stored in the session store fails. If this returns true,
221     * SessionManager will instead delete the session store data so this
222     * SessionInfo may still be used. This is important for providers which use
223     * deterministic IDs and so cannot just generate a random new one.
224     *
225     * @return bool
226     */
227    final public function forceUse() {
228        return $this->forceUse;
229    }
230
231    /**
232     * Return the priority
233     * @return int
234     */
235    final public function getPriority() {
236        return $this->priority;
237    }
238
239    /**
240     * Return the user
241     * @return UserInfo|null
242     */
243    final public function getUserInfo() {
244        return $this->userInfo;
245    }
246
247    /**
248     * Return whether the session is persisted
249     * @return bool
250     */
251    final public function wasPersisted() {
252        return $this->persisted;
253    }
254
255    /**
256     * Return provider metadata
257     * @return array|null
258     */
259    final public function getProviderMetadata() {
260        return $this->providerMetadata;
261    }
262
263    /**
264     * Return whether the user was remembered
265     *
266     * For providers that can persist the user separately from the session,
267     * the human using it may not actually *want* that to be done. For example,
268     * a cookie-based provider can set cookies that are longer-lived than the
269     * backend session data, but on a public terminal the human likely doesn't
270     * want those cookies set.
271     *
272     * This is false unless a non-anonymous verified user was passed to
273     * the SessionInfo constructor by the provider, and the provider didn't
274     * pass false for the 'remembered' data item.
275     *
276     * @return bool
277     */
278    final public function wasRemembered() {
279        return $this->remembered;
280    }
281
282    /**
283     * Whether this session should only be used over HTTPS. This should be
284     * ignored if $wgForceHTTPS is true.
285     *
286     * @return bool
287     */
288    final public function forceHTTPS() {
289        return $this->forceHTTPS;
290    }
291
292    public function __toString() {
293        return '[' . $this->getPriority() . ']' .
294            ( $this->getProvider() ?: 'null' ) .
295            ( $this->userInfo ?: '<null>' ) . $this->getId();
296    }
297
298    /**
299     * Compare two SessionInfo objects by priority
300     * @param SessionInfo $a
301     * @param SessionInfo $b
302     * @return int Negative if $a < $b, positive if $a > $b, zero if equal
303     */
304    public static function compare( $a, $b ) {
305        return $a->getPriority() <=> $b->getPriority();
306    }
307
308}