LoggerTest.php 8.3 KB

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