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