LoggerTest.php 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322
  1. <?php
  2. /**
  3. * SPDX-FileCopyrightText: 2016-2024 Nextcloud GmbH and Nextcloud contributors
  4. * SPDX-FileCopyrightText: 2016 ownCloud, Inc.
  5. * SPDX-License-Identifier: AGPL-3.0-or-later
  6. */
  7. namespace Test;
  8. use OC\Log;
  9. use OC\SystemConfig;
  10. use OCP\ILogger;
  11. use OCP\IUser;
  12. use OCP\IUserSession;
  13. use OCP\Log\IWriter;
  14. use OCP\Support\CrashReport\IRegistry;
  15. use PHPUnit\Framework\MockObject\MockObject;
  16. class LoggerTest extends TestCase implements IWriter {
  17. /** @var SystemConfig|MockObject */
  18. private $config;
  19. /** @var IRegistry|MockObject */
  20. private $registry;
  21. /** @var ILogger */
  22. private $logger;
  23. /** @var array */
  24. private array $logs = [];
  25. protected function setUp(): void {
  26. parent::setUp();
  27. $this->logs = [];
  28. $this->config = $this->createMock(SystemConfig::class);
  29. $this->registry = $this->createMock(IRegistry::class);
  30. $this->logger = new Log($this, $this->config, null, $this->registry);
  31. }
  32. private function mockDefaultLogLevel(): void {
  33. $this->config->expects($this->any())
  34. ->method('getValue')
  35. ->will(($this->returnValueMap([
  36. ['loglevel', ILogger::WARN, ILogger::WARN],
  37. ])));
  38. }
  39. public function testInterpolation() {
  40. $this->mockDefaultLogLevel();
  41. $logger = $this->logger;
  42. $logger->warning('{Message {nothing} {user} {foo.bar} a}', ['user' => 'Bob', 'foo.bar' => 'Bar']);
  43. $expected = ['2 {Message {nothing} Bob Bar a}'];
  44. $this->assertEquals($expected, $this->getLogs());
  45. }
  46. public function testAppCondition() {
  47. $this->config->expects($this->any())
  48. ->method('getValue')
  49. ->will(($this->returnValueMap([
  50. ['loglevel', ILogger::WARN, ILogger::WARN],
  51. ['log.condition', [], ['apps' => ['files']]]
  52. ])));
  53. $logger = $this->logger;
  54. $logger->info('Don\'t display info messages');
  55. $logger->info('Show info messages of files app', ['app' => 'files']);
  56. $logger->warning('Show warning messages of other apps');
  57. $expected = [
  58. '1 Show info messages of files app',
  59. '2 Show warning messages of other apps',
  60. ];
  61. $this->assertEquals($expected, $this->getLogs());
  62. }
  63. public function dataMatchesCondition(): array {
  64. return [
  65. [
  66. 'user0',
  67. [
  68. 'apps' => ['app2'],
  69. ],
  70. [
  71. '1 Info of app2',
  72. ],
  73. ],
  74. [
  75. 'user2',
  76. [
  77. 'users' => ['user1', 'user2'],
  78. 'apps' => ['app1'],
  79. ],
  80. [
  81. '1 Info of app1',
  82. ],
  83. ],
  84. [
  85. 'user3',
  86. [
  87. 'users' => ['user3'],
  88. ],
  89. [
  90. '1 Info without app',
  91. '1 Info of app1',
  92. '1 Info of app2',
  93. '0 Debug of app3',
  94. ],
  95. ],
  96. [
  97. 'user4',
  98. [
  99. 'users' => ['user4'],
  100. 'apps' => ['app3'],
  101. 'loglevel' => 0,
  102. ],
  103. [
  104. '0 Debug of app3',
  105. ],
  106. ],
  107. [
  108. 'user4',
  109. [
  110. 'message' => ' of ',
  111. ],
  112. [
  113. '1 Info of app1',
  114. '1 Info of app2',
  115. '0 Debug of app3',
  116. ],
  117. ],
  118. ];
  119. }
  120. /**
  121. * @dataProvider dataMatchesCondition
  122. */
  123. public function testMatchesCondition(string $userId, array $conditions, array $expectedLogs): void {
  124. $this->config->expects($this->any())
  125. ->method('getValue')
  126. ->willReturnMap([
  127. ['loglevel', ILogger::WARN, ILogger::WARN],
  128. ['log.condition', [], ['matches' => [
  129. $conditions,
  130. ]]],
  131. ]);
  132. $logger = $this->logger;
  133. $user = $this->createMock(IUser::class);
  134. $user->method('getUID')
  135. ->willReturn($userId);
  136. $userSession = $this->createMock(IUserSession::class);
  137. $userSession->method('getUser')
  138. ->willReturn($user);
  139. $this->overwriteService(IUserSession::class, $userSession);
  140. $logger->info('Info without app');
  141. $logger->info('Info of app1', ['app' => 'app1']);
  142. $logger->info('Info of app2', ['app' => 'app2']);
  143. $logger->debug('Debug of app3', ['app' => 'app3']);
  144. $this->assertEquals($expectedLogs, $this->getLogs());
  145. }
  146. public function testLoggingWithDataArray(): void {
  147. $this->mockDefaultLogLevel();
  148. $writerMock = $this->createMock(IWriter::class);
  149. $logFile = new Log($writerMock, $this->config);
  150. $writerMock->expects($this->once())->method('write')->with('no app in context', ['something' => 'extra', 'message' => 'Testing logging with john']);
  151. $logFile->error('Testing logging with {user}', ['something' => 'extra', 'user' => 'john']);
  152. }
  153. private function getLogs(): array {
  154. return $this->logs;
  155. }
  156. public function write(string $app, $message, int $level) {
  157. $textMessage = $message;
  158. if (is_array($message)) {
  159. $textMessage = $message['message'];
  160. }
  161. $this->logs[] = $level . " " . $textMessage;
  162. }
  163. public function userAndPasswordData(): array {
  164. return [
  165. ['mySpecialUsername', 'MySuperSecretPassword'],
  166. ['my-user', '324324()#ä234'],
  167. ['my-user', ')qwer'],
  168. ['my-user', 'qwer)asdf'],
  169. ['my-user', 'qwer)'],
  170. ['my-user', '(qwer'],
  171. ['my-user', 'qwer(asdf'],
  172. ['my-user', 'qwer('],
  173. ];
  174. }
  175. /**
  176. * @dataProvider userAndPasswordData
  177. */
  178. public function testDetectlogin(string $user, string $password): void {
  179. $this->mockDefaultLogLevel();
  180. $e = new \Exception('test');
  181. $this->registry->expects($this->once())
  182. ->method('delegateReport')
  183. ->with($e, ['level' => 3]);
  184. $this->logger->logException($e);
  185. $logLines = $this->getLogs();
  186. foreach ($logLines as $logLine) {
  187. if (is_array($logLine)) {
  188. $logLine = json_encode($logLine);
  189. }
  190. $this->assertStringNotContainsString($user, $logLine);
  191. $this->assertStringNotContainsString($password, $logLine);
  192. $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine);
  193. }
  194. }
  195. /**
  196. * @dataProvider userAndPasswordData
  197. */
  198. public function testDetectcheckPassword(string $user, string $password): void {
  199. $this->mockDefaultLogLevel();
  200. $e = new \Exception('test');
  201. $this->registry->expects($this->once())
  202. ->method('delegateReport')
  203. ->with($e, ['level' => 3]);
  204. $this->logger->logException($e);
  205. $logLines = $this->getLogs();
  206. foreach ($logLines as $logLine) {
  207. if (is_array($logLine)) {
  208. $logLine = json_encode($logLine);
  209. }
  210. $this->assertStringNotContainsString($user, $logLine);
  211. $this->assertStringNotContainsString($password, $logLine);
  212. $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine);
  213. }
  214. }
  215. /**
  216. * @dataProvider userAndPasswordData
  217. */
  218. public function testDetectvalidateUserPass(string $user, string $password): void {
  219. $this->mockDefaultLogLevel();
  220. $e = new \Exception('test');
  221. $this->registry->expects($this->once())
  222. ->method('delegateReport')
  223. ->with($e, ['level' => 3]);
  224. $this->logger->logException($e);
  225. $logLines = $this->getLogs();
  226. foreach ($logLines as $logLine) {
  227. if (is_array($logLine)) {
  228. $logLine = json_encode($logLine);
  229. }
  230. $this->assertStringNotContainsString($user, $logLine);
  231. $this->assertStringNotContainsString($password, $logLine);
  232. $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine);
  233. }
  234. }
  235. /**
  236. * @dataProvider userAndPasswordData
  237. */
  238. public function testDetecttryLogin(string $user, string $password): void {
  239. $this->mockDefaultLogLevel();
  240. $e = new \Exception('test');
  241. $this->registry->expects($this->once())
  242. ->method('delegateReport')
  243. ->with($e, ['level' => 3]);
  244. $this->logger->logException($e);
  245. $logLines = $this->getLogs();
  246. foreach ($logLines as $logLine) {
  247. if (is_array($logLine)) {
  248. $logLine = json_encode($logLine);
  249. }
  250. $this->assertStringNotContainsString($user, $logLine);
  251. $this->assertStringNotContainsString($password, $logLine);
  252. $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine);
  253. }
  254. }
  255. /**
  256. * @dataProvider userAndPasswordData
  257. */
  258. public function testDetectclosure(string $user, string $password): void {
  259. $this->mockDefaultLogLevel();
  260. $a = function ($user, $password) {
  261. throw new \Exception('test');
  262. };
  263. $this->registry->expects($this->once())
  264. ->method('delegateReport');
  265. try {
  266. $a($user, $password);
  267. } catch (\Exception $e) {
  268. $this->logger->logException($e);
  269. }
  270. $logLines = $this->getLogs();
  271. foreach ($logLines as $logLine) {
  272. if (is_array($logLine)) {
  273. $logLine = json_encode($logLine);
  274. }
  275. $log = explode('\n', $logLine);
  276. unset($log[1]); // Remove `testDetectclosure(` because we are not testing this here, but the closure on stack trace 0
  277. $logLine = implode('\n', $log);
  278. $this->assertStringNotContainsString($user, $logLine);
  279. $this->assertStringNotContainsString($password, $logLine);
  280. $this->assertStringContainsString('*** sensitive parameters replaced ***', $logLine);
  281. }
  282. }
  283. }