Passed
Push — master ( d80277...8673d0 )
by Christoph
16:23 queued 12s
created

Internal::validateSession()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 2
nc 2
nop 0
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * @copyright Copyright (c) 2016, ownCloud, Inc.
7
 *
8
 * @author Arthur Schiwon <[email protected]>
9
 * @author cetra3 <[email protected]>
10
 * @author Christoph Wurst <[email protected]>
11
 * @author Lukas Reschke <[email protected]>
12
 * @author MartB <[email protected]>
13
 * @author Morris Jobke <[email protected]>
14
 * @author Robin Appelman <[email protected]>
15
 * @author Roeland Jago Douma <[email protected]>
16
 * @author Thomas Müller <[email protected]>
17
 * @author Victor Dubiniuk <[email protected]>
18
 *
19
 * @license AGPL-3.0
20
 *
21
 * This code is free software: you can redistribute it and/or modify
22
 * it under the terms of the GNU Affero General Public License, version 3,
23
 * as published by the Free Software Foundation.
24
 *
25
 * This program is distributed in the hope that it will be useful,
26
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
27
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
28
 * GNU Affero General Public License for more details.
29
 *
30
 * You should have received a copy of the GNU Affero General Public License, version 3,
31
 * along with this program. If not, see <http://www.gnu.org/licenses/>
32
 *
33
 */
34
namespace OC\Session;
35
36
use OC\Authentication\Exceptions\InvalidTokenException;
37
use OC\Authentication\Token\IProvider;
38
use OCP\Session\Exceptions\SessionNotAvailableException;
39
40
/**
41
 * Class Internal
42
 *
43
 * wrap php's internal session handling into the Session interface
44
 *
45
 * @package OC\Session
46
 */
47
class Internal extends Session {
48
	/**
49
	 * @param string $name
50
	 * @throws \Exception
51
	 */
52
	public function __construct(string $name) {
53
		set_error_handler([$this, 'trapError']);
54
		$this->invoke('session_name', [$name]);
55
		try {
56
			$this->startSession();
57
		} catch (\Exception $e) {
58
			setcookie($this->invoke('session_name'), '', -1, \OC::$WEBROOT ?: '/');
59
		}
60
		restore_error_handler();
61
		if (!isset($_SESSION)) {
62
			throw new \Exception('Failed to start session');
63
		}
64
	}
65
66
	/**
67
	 * @param string $key
68
	 * @param integer $value
69
	 */
70
	public function set(string $key, $value) {
71
		$reopened = $this->reopen();
72
		$_SESSION[$key] = $value;
73
		if ($reopened) {
74
			$this->close();
75
		}
76
	}
77
78
	/**
79
	 * @param string $key
80
	 * @return mixed
81
	 */
82
	public function get(string $key) {
83
		if (!$this->exists($key)) {
84
			return null;
85
		}
86
		return $_SESSION[$key];
87
	}
88
89
	/**
90
	 * @param string $key
91
	 * @return bool
92
	 */
93
	public function exists(string $key): bool {
94
		return isset($_SESSION[$key]);
95
	}
96
97
	/**
98
	 * @param string $key
99
	 */
100
	public function remove(string $key) {
101
		if (isset($_SESSION[$key])) {
102
			unset($_SESSION[$key]);
103
		}
104
	}
105
106
	public function clear() {
107
		$this->reopen();
108
		$this->invoke('session_unset');
109
		$this->regenerateId();
110
		$this->invoke('session_write_close');
111
		$this->startSession(true);
112
		$_SESSION = [];
113
	}
114
115
	public function close() {
116
		$this->invoke('session_write_close');
117
		parent::close();
118
	}
119
120
	/**
121
	 * Wrapper around session_regenerate_id
122
	 *
123
	 * @param bool $deleteOldSession Whether to delete the old associated session file or not.
124
	 * @param bool $updateToken Wheater to update the associated auth token
125
	 * @return void
126
	 */
127
	public function regenerateId(bool $deleteOldSession = true, bool $updateToken = false) {
128
		$this->reopen();
129
		$oldId = null;
130
131
		if ($updateToken) {
132
			// Get the old id to update the token
133
			try {
134
				$oldId = $this->getId();
135
			} catch (SessionNotAvailableException $e) {
136
				// We can't update a token if there is no previous id
137
				$updateToken = false;
138
			}
139
		}
140
141
		try {
142
			@session_regenerate_id($deleteOldSession);
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for session_regenerate_id(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-unhandled  annotation

142
			/** @scrutinizer ignore-unhandled */ @session_regenerate_id($deleteOldSession);

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
143
		} catch (\Error $e) {
144
			$this->trapError($e->getCode(), $e->getMessage());
145
		}
146
147
		if ($updateToken) {
148
			// Get the new id to update the token
149
			$newId = $this->getId();
150
151
			/** @var IProvider $tokenProvider */
152
			$tokenProvider = \OC::$server->query(IProvider::class);
153
154
			try {
155
				$tokenProvider->renewSessionToken($oldId, $newId);
156
			} catch (InvalidTokenException $e) {
157
				// Just ignore
158
			}
159
		}
160
	}
161
162
	/**
163
	 * Wrapper around session_id
164
	 *
165
	 * @return string
166
	 * @throws SessionNotAvailableException
167
	 * @since 9.1.0
168
	 */
169
	public function getId(): string {
170
		$id = $this->invoke('session_id', [], true);
171
		if ($id === '') {
172
			throw new SessionNotAvailableException();
173
		}
174
		return $id;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $id could return the type null which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
175
	}
176
177
	/**
178
	 * @throws \Exception
179
	 */
180
	public function reopen(): bool {
181
		if ($this->sessionClosed) {
182
			$this->startSession(false, false);
183
			$this->sessionClosed = false;
184
			return true;
185
		}
186
187
		return false;
188
	}
189
190
	/**
191
	 * @param int $errorNumber
192
	 * @param string $errorString
193
	 * @throws \ErrorException
194
	 */
195
	public function trapError(int $errorNumber, string $errorString) {
196
		if ($errorNumber & E_ERROR) {
197
			throw new \ErrorException($errorString);
198
		}
199
	}
200
201
	/**
202
	 * @param string $functionName the full session_* function name
203
	 * @param array $parameters
204
	 * @param bool $silence whether to suppress warnings
205
	 * @throws \ErrorException via trapError
206
	 * @return mixed
207
	 */
208
	private function invoke(string $functionName, array $parameters = [], bool $silence = false) {
209
		try {
210
			if ($silence) {
211
				return @call_user_func_array($functionName, $parameters);
212
			} else {
213
				return call_user_func_array($functionName, $parameters);
214
			}
215
		} catch (\Error $e) {
216
			$this->trapError($e->getCode(), $e->getMessage());
217
		}
218
	}
219
220
	private function startSession(bool $silence = false, bool $readAndClose = true) {
221
		$sessionParams = ['cookie_samesite' => 'Lax'];
222
		if (\OC::hasSessionRelaxedExpiry()) {
223
			$sessionParams['read_and_close'] = $readAndClose;
224
		}
225
		$this->invoke('session_start', [$sessionParams], $silence);
226
	}
227
}
228