You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

189 lines
4.9 KiB

3 years ago
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2018 Robin Appelman <robin@icewind.nl>
  4. *
  5. * @author Arthur Schiwon <blizzz@arthur-schiwon.de>
  6. * @author Christoph Wurst <christoph@winzerhof-wurst.at>
  7. * @author Joas Schilling <coding@schilljs.com>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <robin@icewind.nl>
  10. * @author Roeland Jago Douma <roeland@famdouma.nl>
  11. *
  12. * @license GNU AGPL version 3 or any later version
  13. *
  14. * This program is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License as
  16. * published by the Free Software Foundation, either version 3 of the
  17. * License, or (at your option) any later version.
  18. *
  19. * This program is distributed in the hope that it will be useful,
  20. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  21. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  22. * GNU Affero General Public License for more details.
  23. *
  24. * You should have received a copy of the GNU Affero General Public License
  25. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  26. *
  27. */
  28. namespace OC\Log;
  29. use OC\Core\Controller\SetupController;
  30. use OC\HintException;
  31. use OC\Security\IdentityProof\Key;
  32. use OC\Setup;
  33. class ExceptionSerializer {
  34. public const methodsWithSensitiveParameters = [
  35. // Session/User
  36. 'completeLogin',
  37. 'login',
  38. 'checkPassword',
  39. 'checkPasswordNoLogging',
  40. 'loginWithPassword',
  41. 'updatePrivateKeyPassword',
  42. 'validateUserPass',
  43. 'loginWithToken',
  44. '{closure}',
  45. 'createSessionToken',
  46. // Provisioning
  47. 'addUser',
  48. // TokenProvider
  49. 'getToken',
  50. 'isTokenPassword',
  51. 'getPassword',
  52. 'decryptPassword',
  53. 'logClientIn',
  54. 'generateToken',
  55. 'validateToken',
  56. // TwoFactorAuth
  57. 'solveChallenge',
  58. 'verifyChallenge',
  59. // ICrypto
  60. 'calculateHMAC',
  61. 'encrypt',
  62. 'decrypt',
  63. // LoginController
  64. 'tryLogin',
  65. 'confirmPassword',
  66. // LDAP
  67. 'bind',
  68. 'areCredentialsValid',
  69. 'invokeLDAPMethod',
  70. // Encryption
  71. 'storeKeyPair',
  72. 'setupUser',
  73. // files_external: OCA\Files_External\MountConfig
  74. 'getBackendStatus',
  75. // files_external: UserStoragesController
  76. 'update',
  77. ];
  78. public const methodsWithSensitiveParametersByClass = [
  79. SetupController::class => [
  80. 'run',
  81. 'display',
  82. 'loadAutoConfig',
  83. ],
  84. Setup::class => [
  85. 'install'
  86. ],
  87. Key::class => [
  88. '__construct'
  89. ],
  90. ];
  91. private function editTrace(array &$sensitiveValues, array $traceLine): array {
  92. if (isset($traceLine['args'])) {
  93. $sensitiveValues = array_merge($sensitiveValues, $traceLine['args']);
  94. }
  95. $traceLine['args'] = ['*** sensitive parameters replaced ***'];
  96. return $traceLine;
  97. }
  98. private function filterTrace(array $trace) {
  99. $sensitiveValues = [];
  100. $trace = array_map(function (array $traceLine) use (&$sensitiveValues) {
  101. $className = $traceLine['class'] ?? '';
  102. if ($className && isset(self::methodsWithSensitiveParametersByClass[$className])
  103. && in_array($traceLine['function'], self::methodsWithSensitiveParametersByClass[$className], true)) {
  104. return $this->editTrace($sensitiveValues, $traceLine);
  105. }
  106. foreach (self::methodsWithSensitiveParameters as $sensitiveMethod) {
  107. if (strpos($traceLine['function'], $sensitiveMethod) !== false) {
  108. return $this->editTrace($sensitiveValues, $traceLine);
  109. }
  110. }
  111. return $traceLine;
  112. }, $trace);
  113. return array_map(function (array $traceLine) use ($sensitiveValues) {
  114. if (isset($traceLine['args'])) {
  115. $traceLine['args'] = $this->removeValuesFromArgs($traceLine['args'], $sensitiveValues);
  116. }
  117. return $traceLine;
  118. }, $trace);
  119. }
  120. private function removeValuesFromArgs($args, $values) {
  121. foreach ($args as &$arg) {
  122. if (in_array($arg, $values, true)) {
  123. $arg = '*** sensitive parameter replaced ***';
  124. } elseif (is_array($arg)) {
  125. $arg = $this->removeValuesFromArgs($arg, $values);
  126. }
  127. }
  128. return $args;
  129. }
  130. private function encodeTrace($trace) {
  131. $filteredTrace = $this->filterTrace($trace);
  132. return array_map(function (array $line) {
  133. if (isset($line['args'])) {
  134. $line['args'] = array_map([$this, 'encodeArg'], $line['args']);
  135. }
  136. return $line;
  137. }, $filteredTrace);
  138. }
  139. private function encodeArg($arg) {
  140. if (is_object($arg)) {
  141. $data = get_object_vars($arg);
  142. $data['__class__'] = get_class($arg);
  143. return array_map([$this, 'encodeArg'], $data);
  144. } elseif (is_array($arg)) {
  145. return array_map([$this, 'encodeArg'], $arg);
  146. } else {
  147. return $arg;
  148. }
  149. }
  150. public function serializeException(\Throwable $exception) {
  151. $data = [
  152. 'Exception' => get_class($exception),
  153. 'Message' => $exception->getMessage(),
  154. 'Code' => $exception->getCode(),
  155. 'Trace' => $this->encodeTrace($exception->getTrace()),
  156. 'File' => $exception->getFile(),
  157. 'Line' => $exception->getLine(),
  158. ];
  159. if ($exception instanceof HintException) {
  160. $data['Hint'] = $exception->getHint();
  161. }
  162. if ($exception->getPrevious()) {
  163. $data['Previous'] = $this->serializeException($exception->getPrevious());
  164. }
  165. return $data;
  166. }
  167. }