Passed
Push — master ( 35aa34...c9fcf5 )
by Roeland
49:09 queued 29:27
created

ExceptionSerializer::editTrace()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
cc 2
nc 2
nop 2
1
<?php
2
/**
3
 * @copyright Copyright (c) 2018 Robin Appelman <[email protected]>
4
 *
5
 * @author Arthur Schiwon <[email protected]>
6
 * @author Christoph Wurst <[email protected]>
7
 * @author Joas Schilling <[email protected]>
8
 * @author Morris Jobke <[email protected]>
9
 * @author Robin Appelman <[email protected]>
10
 * @author Roeland Jago Douma <[email protected]>
11
 * @author Vincent Petry <[email protected]>
12
 *
13
 * @license GNU AGPL version 3 or any later version
14
 *
15
 * This program is free software: you can redistribute it and/or modify
16
 * it under the terms of the GNU Affero General Public License as
17
 * published by the Free Software Foundation, either version 3 of the
18
 * License, or (at your option) any later version.
19
 *
20
 * This program is distributed in the hope that it will be useful,
21
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
22
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
23
 * GNU Affero General Public License for more details.
24
 *
25
 * You should have received a copy of the GNU Affero General Public License
26
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
27
 *
28
 */
29
30
namespace OC\Log;
31
32
use OC\Core\Controller\SetupController;
33
use OC\HintException;
34
use OC\Security\IdentityProof\Key;
35
use OC\Setup;
36
use OC\SystemConfig;
37
38
class ExceptionSerializer {
39
	public const methodsWithSensitiveParameters = [
40
		// Session/User
41
		'completeLogin',
42
		'login',
43
		'checkPassword',
44
		'checkPasswordNoLogging',
45
		'loginWithPassword',
46
		'updatePrivateKeyPassword',
47
		'validateUserPass',
48
		'loginWithToken',
49
		'{closure}',
50
		'createSessionToken',
51
52
		// Provisioning
53
		'addUser',
54
55
		// TokenProvider
56
		'getToken',
57
		'isTokenPassword',
58
		'getPassword',
59
		'decryptPassword',
60
		'logClientIn',
61
		'generateToken',
62
		'validateToken',
63
64
		// TwoFactorAuth
65
		'solveChallenge',
66
		'verifyChallenge',
67
68
		// ICrypto
69
		'calculateHMAC',
70
		'encrypt',
71
		'decrypt',
72
73
		// LoginController
74
		'tryLogin',
75
		'confirmPassword',
76
77
		// LDAP
78
		'bind',
79
		'areCredentialsValid',
80
		'invokeLDAPMethod',
81
82
		// Encryption
83
		'storeKeyPair',
84
		'setupUser',
85
86
		// files_external: OCA\Files_External\MountConfig
87
		'getBackendStatus',
88
89
		// files_external: UserStoragesController
90
		'update',
91
92
		// Preview providers, don't log big data strings
93
		'imagecreatefromstring',
94
	];
95
96
	/** @var SystemConfig */
97
	private $systemConfig;
98
99
	public function __construct(SystemConfig $systemConfig) {
100
		$this->systemConfig = $systemConfig;
101
	}
102
103
	public const methodsWithSensitiveParametersByClass = [
104
		SetupController::class => [
105
			'run',
106
			'display',
107
			'loadAutoConfig',
108
		],
109
		Setup::class => [
110
			'install'
111
		],
112
		Key::class => [
113
			'__construct'
114
		],
115
	];
116
117
	private function editTrace(array &$sensitiveValues, array $traceLine): array {
118
		if (isset($traceLine['args'])) {
119
			$sensitiveValues = array_merge($sensitiveValues, $traceLine['args']);
120
		}
121
		$traceLine['args'] = ['*** sensitive parameters replaced ***'];
122
		return $traceLine;
123
	}
124
125
	private function filterTrace(array $trace) {
126
		$sensitiveValues = [];
127
		$trace = array_map(function (array $traceLine) use (&$sensitiveValues) {
128
			$className = $traceLine['class'] ?? '';
129
			if ($className && isset(self::methodsWithSensitiveParametersByClass[$className])
130
				&& in_array($traceLine['function'], self::methodsWithSensitiveParametersByClass[$className], true)) {
131
				return $this->editTrace($sensitiveValues, $traceLine);
132
			}
133
			foreach (self::methodsWithSensitiveParameters as $sensitiveMethod) {
134
				if (strpos($traceLine['function'], $sensitiveMethod) !== false) {
135
					return $this->editTrace($sensitiveValues, $traceLine);
136
				}
137
			}
138
			return $traceLine;
139
		}, $trace);
140
		return array_map(function (array $traceLine) use ($sensitiveValues) {
141
			if (isset($traceLine['args'])) {
142
				$traceLine['args'] = $this->removeValuesFromArgs($traceLine['args'], $sensitiveValues);
143
			}
144
			return $traceLine;
145
		}, $trace);
146
	}
147
148
	private function removeValuesFromArgs($args, $values) {
149
		foreach ($args as &$arg) {
150
			if (in_array($arg, $values, true)) {
151
				$arg = '*** sensitive parameter replaced ***';
152
			} elseif (is_array($arg)) {
153
				$arg = $this->removeValuesFromArgs($arg, $values);
154
			}
155
		}
156
		return $args;
157
	}
158
159
	private function encodeTrace($trace) {
160
		$filteredTrace = $this->filterTrace($trace);
161
		return array_map(function (array $line) {
162
			if (isset($line['args'])) {
163
				$line['args'] = array_map([$this, 'encodeArg'], $line['args']);
164
			}
165
			return $line;
166
		}, $filteredTrace);
167
	}
168
169
	private function encodeArg($arg) {
170
		if (is_object($arg)) {
171
			$data = get_object_vars($arg);
172
			$data['__class__'] = get_class($arg);
173
			return array_map([$this, 'encodeArg'], $data);
174
		}
175
176
		if (is_array($arg)) {
177
			// Only log the first 5 elements of an array unless we are on debug
178
			if ((int)$this->systemConfig->getValue('loglevel', 2) !== 0) {
179
				$elemCount = count($arg);
180
				if ($elemCount > 5) {
181
					$arg = array_slice($arg, 0, 5);
182
					$arg[] = 'And ' . ($elemCount - 5) . ' more entries, set log level to debug to see all entries';
183
				}
184
			}
185
			return array_map([$this, 'encodeArg'], $arg);
186
		}
187
188
		return $arg;
189
	}
190
191
	public function serializeException(\Throwable $exception) {
192
		$data = [
193
			'Exception' => get_class($exception),
194
			'Message' => $exception->getMessage(),
195
			'Code' => $exception->getCode(),
196
			'Trace' => $this->encodeTrace($exception->getTrace()),
197
			'File' => $exception->getFile(),
198
			'Line' => $exception->getLine(),
199
		];
200
201
		if ($exception instanceof HintException) {
202
			$data['Hint'] = $exception->getHint();
203
		}
204
205
		if ($exception->getPrevious()) {
206
			$data['Previous'] = $this->serializeException($exception->getPrevious());
207
		}
208
209
		return $data;
210
	}
211
}
212