diff --git a/apps/admin_audit/composer/composer/autoload_classmap.php b/apps/admin_audit/composer/composer/autoload_classmap.php index 9f496d408eccb..72bf58847b828 100644 --- a/apps/admin_audit/composer/composer/autoload_classmap.php +++ b/apps/admin_audit/composer/composer/autoload_classmap.php @@ -27,4 +27,5 @@ 'OCA\\AdminAudit\\Listener\\SharingEventListener' => $baseDir . '/../lib/Listener/SharingEventListener.php', 'OCA\\AdminAudit\\Listener\\TagEventListener' => $baseDir . '/../lib/Listener/TagEventListener.php', 'OCA\\AdminAudit\\Listener\\UserManagementEventListener' => $baseDir . '/../lib/Listener/UserManagementEventListener.php', + 'OCA\\AdminAudit\\Operation' => $baseDir . '/../lib/Operation.php', ); diff --git a/apps/admin_audit/composer/composer/autoload_static.php b/apps/admin_audit/composer/composer/autoload_static.php index 4be8380ff543a..25b67cae98869 100644 --- a/apps/admin_audit/composer/composer/autoload_static.php +++ b/apps/admin_audit/composer/composer/autoload_static.php @@ -42,6 +42,7 @@ class ComposerStaticInitAdminAudit 'OCA\\AdminAudit\\Listener\\SharingEventListener' => __DIR__ . '/..' . '/../lib/Listener/SharingEventListener.php', 'OCA\\AdminAudit\\Listener\\TagEventListener' => __DIR__ . '/..' . '/../lib/Listener/TagEventListener.php', 'OCA\\AdminAudit\\Listener\\UserManagementEventListener' => __DIR__ . '/..' . '/../lib/Listener/UserManagementEventListener.php', + 'OCA\\AdminAudit\\Operation' => __DIR__ . '/..' . '/../lib/Operation.php', ); public static function getInitializer(ClassLoader $loader) diff --git a/apps/admin_audit/lib/Actions/Action.php b/apps/admin_audit/lib/Actions/Action.php index 7d41eff4df6e4..cedf4a78fcb90 100644 --- a/apps/admin_audit/lib/Actions/Action.php +++ b/apps/admin_audit/lib/Actions/Action.php @@ -9,6 +9,7 @@ namespace OCA\AdminAudit\Actions; use OCA\AdminAudit\IAuditLogger; +use OCA\AdminAudit\Operation; class Action { @@ -20,21 +21,28 @@ public function __construct( /** * Log a single action with a log level of info * + * @param Operation|string|null $operation Stable identifier of the action. A string in the form `app.entity.action` is only expected from other apps via CriticalActionPerformedEvent * @param string $text * @param array $params * @param list $elements * @param bool $obfuscateParameters */ public function log( + Operation|string|null $operation, string $text, array $params, array $elements, bool $obfuscateParameters = false, ): void { + $baseContext = ['app' => 'admin_audit']; + if ($operation !== null) { + $baseContext['operation'] = $operation instanceof Operation ? $operation->value : $operation; + } + foreach ($elements as $element) { if (!array_key_exists($element, $params)) { $message = '$params["' . $element . '"] was missing.'; - $context = ['app' => 'admin_audit']; + $context = $baseContext; if (!$obfuscateParameters) { $message .= ' Transferred value: {params}'; @@ -47,7 +55,7 @@ public function log( } $replaceArray = []; - $context = ['app' => 'admin_audit']; + $context = $baseContext; foreach ($elements as $element) { $value = $params[$element]; if ($value instanceof \DateTimeInterface) { diff --git a/apps/admin_audit/lib/Actions/Files.php b/apps/admin_audit/lib/Actions/Files.php index 71048345f8f76..c6a8c122ecdbf 100644 --- a/apps/admin_audit/lib/Actions/Files.php +++ b/apps/admin_audit/lib/Actions/Files.php @@ -9,6 +9,7 @@ namespace OCA\AdminAudit\Actions; use OC\Files\Node\NonExistingFile; +use OCA\AdminAudit\Operation; use OCP\Files\Events\Node\BeforeNodeDeletedEvent; use OCP\Files\Events\Node\BeforeNodeReadEvent; use OCP\Files\Events\Node\NodeCopiedEvent; @@ -43,6 +44,7 @@ public function read(BeforeNodeReadEvent $event): void { return; } $this->log( + Operation::FileRead, 'File with id "%s" accessed: "%s"', $params, array_keys($params) @@ -69,6 +71,7 @@ public function afterRename(NodeRenamedEvent $event): void { } $this->log( + Operation::FileRenamed, 'File renamed with id "%s" from "%s" to "%s"', $params, array_keys($params) @@ -95,6 +98,7 @@ public function create(NodeCreatedEvent $event): void { return; } $this->log( + Operation::FileCreated, 'File with id "%s" created: "%s"', $params, array_keys($params) @@ -121,6 +125,7 @@ public function copy(NodeCopiedEvent $event): void { return; } $this->log( + Operation::FileCopied, 'File id copied from: "%s" to "%s", path from "%s" to "%s"', $params, array_keys($params) @@ -148,6 +153,7 @@ public function write(NodeWrittenEvent $event): void { } $this->log( + Operation::FileWritten, 'File with id "%s" written to: "%s"', $params, array_keys($params) @@ -171,6 +177,7 @@ public function delete(BeforeNodeDeletedEvent $event): void { return; } $this->log( + Operation::FileDeleted, 'File with id "%s" deleted: "%s"', $params, array_keys($params) diff --git a/apps/admin_audit/lib/Actions/Sharing.php b/apps/admin_audit/lib/Actions/Sharing.php index 424fe63757c58..226364ad83df9 100644 --- a/apps/admin_audit/lib/Actions/Sharing.php +++ b/apps/admin_audit/lib/Actions/Sharing.php @@ -8,6 +8,8 @@ namespace OCA\AdminAudit\Actions; +use OCA\AdminAudit\Operation; + /** * Class Sharing logs the sharing actions * @@ -22,6 +24,7 @@ class Sharing extends Action { */ public function updatePermissions(array $params): void { $this->log( + Operation::SharePermissionsUpdated, 'The permissions of the shared %s "%s" with ID "%s" have been changed to "%s"', $params, [ @@ -40,6 +43,7 @@ public function updatePermissions(array $params): void { */ public function updatePassword(array $params): void { $this->log( + Operation::SharePasswordUpdated, 'The password of the publicly shared %s "%s" with ID "%s" has been changed', $params, [ @@ -58,6 +62,7 @@ public function updatePassword(array $params): void { public function updateExpirationDate(array $params): void { if ($params['date'] === null) { $this->log( + Operation::ShareExpirationRemoved, 'The expiration date of the publicly shared %s with ID "%s" has been removed', $params, [ @@ -67,6 +72,7 @@ public function updateExpirationDate(array $params): void { ); } else { $this->log( + Operation::ShareExpirationUpdated, 'The expiration date of the publicly shared %s with ID "%s" has been changed to "%s"', $params, [ @@ -85,6 +91,7 @@ public function updateExpirationDate(array $params): void { */ public function shareAccessed(array $params): void { $this->log( + Operation::ShareLinkAccessed, 'The shared %s with the token "%s" by "%s" has been accessed.', $params, [ diff --git a/apps/admin_audit/lib/Actions/Trashbin.php b/apps/admin_audit/lib/Actions/Trashbin.php index c7503ed1becc4..83463a1624f70 100644 --- a/apps/admin_audit/lib/Actions/Trashbin.php +++ b/apps/admin_audit/lib/Actions/Trashbin.php @@ -8,15 +8,17 @@ namespace OCA\AdminAudit\Actions; +use OCA\AdminAudit\Operation; + class Trashbin extends Action { public function delete(array $params): void { - $this->log('File "%s" deleted from trash bin.', + $this->log(Operation::TrashbinFileDeleted, 'File "%s" deleted from trash bin.', ['path' => $params['path']], ['path'] ); } public function restore(array $params): void { - $this->log('File "%s" restored from trash bin.', + $this->log(Operation::TrashbinFileRestored, 'File "%s" restored from trash bin.', ['path' => $params['filePath']], ['path'] ); } diff --git a/apps/admin_audit/lib/Actions/Versions.php b/apps/admin_audit/lib/Actions/Versions.php index a7b18119ea28e..88cdb50a6ddc4 100644 --- a/apps/admin_audit/lib/Actions/Versions.php +++ b/apps/admin_audit/lib/Actions/Versions.php @@ -8,9 +8,11 @@ namespace OCA\AdminAudit\Actions; +use OCA\AdminAudit\Operation; + class Versions extends Action { public function delete(array $params): void { - $this->log('Version "%s" was deleted.', + $this->log(Operation::VersionDeleted, 'Version "%s" was deleted.', ['path' => $params['path']], ['path'] ); diff --git a/apps/admin_audit/lib/Listener/AppManagementEventListener.php b/apps/admin_audit/lib/Listener/AppManagementEventListener.php index 87b3f538fd3ab..bdd348fa5297b 100644 --- a/apps/admin_audit/lib/Listener/AppManagementEventListener.php +++ b/apps/admin_audit/lib/Listener/AppManagementEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\App\Events\AppDisableEvent; use OCP\App\Events\AppEnableEvent; use OCP\App\Events\AppUpdateEvent; @@ -33,12 +34,12 @@ public function handle(Event $event): void { private function appEnable(AppEnableEvent $event): void { if (empty($event->getGroupIds())) { - $this->log('App "%s" enabled', + $this->log(Operation::AppEnabled, 'App "%s" enabled', ['app' => $event->getAppId()], ['app'] ); } else { - $this->log('App "%1$s" enabled for groups: %2$s', + $this->log(Operation::AppEnabled, 'App "%1$s" enabled for groups: %2$s', ['app' => $event->getAppId(), 'groups' => implode(', ', $event->getGroupIds())], ['app', 'groups'] ); @@ -46,14 +47,14 @@ private function appEnable(AppEnableEvent $event): void { } private function appDisable(AppDisableEvent $event): void { - $this->log('App "%s" disabled', + $this->log(Operation::AppDisabled, 'App "%s" disabled', ['app' => $event->getAppId()], ['app'] ); } private function appUpdate(AppUpdateEvent $event): void { - $this->log('App "%s" updated', + $this->log(Operation::AppUpdated, 'App "%s" updated', ['app' => $event->getAppId()], ['app'] ); diff --git a/apps/admin_audit/lib/Listener/AuthEventListener.php b/apps/admin_audit/lib/Listener/AuthEventListener.php index dc4d857f59cfe..10f19f71ac43c 100644 --- a/apps/admin_audit/lib/Listener/AuthEventListener.php +++ b/apps/admin_audit/lib/Listener/AuthEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\Authentication\Events\AnyLoginFailedEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -37,6 +38,7 @@ public function handle(Event $event): void { private function beforeUserLoggedIn(BeforeUserLoggedInEvent $event): void { $this->log( + Operation::LoginAttempted, 'Login attempt: "%s"', [ 'uid' => $event->getUsername() @@ -50,6 +52,7 @@ private function beforeUserLoggedIn(BeforeUserLoggedInEvent $event): void { private function userLoggedIn(UserLoggedInWithCookieEvent|UserLoggedInEvent $event): void { $this->log( + Operation::LoginSucceeded, 'Login successful: "%s"', [ 'uid' => $event->getUser()->getUID() @@ -63,6 +66,7 @@ private function userLoggedIn(UserLoggedInWithCookieEvent|UserLoggedInEvent $eve private function beforeUserLogout(BeforeUserLoggedOutEvent $event): void { $this->log( + Operation::LogoutPerformed, 'Logout occurred', [], [] @@ -71,6 +75,7 @@ private function beforeUserLogout(BeforeUserLoggedOutEvent $event): void { private function anyLoginFailed(AnyLoginFailedEvent $event): void { $this->log( + Operation::LoginFailed, 'Login failed: "%s"', [ 'loginName' => $event->getLoginName() diff --git a/apps/admin_audit/lib/Listener/CacheEventListener.php b/apps/admin_audit/lib/Listener/CacheEventListener.php index 76b0f94e4d7e2..e4eed67762ef6 100644 --- a/apps/admin_audit/lib/Listener/CacheEventListener.php +++ b/apps/admin_audit/lib/Listener/CacheEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Files\Cache\CacheEntryInsertedEvent; @@ -29,7 +30,7 @@ public function handle(Event $event): void { } private function entryInserted(CacheEntryInsertedEvent $event): void { - $this->log('Cache entry inserted for fileid "%1$d", path "%2$s" on storageid "%3$d"', + $this->log(Operation::CacheEntryInserted, 'Cache entry inserted for fileid "%1$d", path "%2$s" on storageid "%3$d"', [ 'fileid' => $event->getFileId(), 'path' => $event->getPath(), @@ -40,7 +41,7 @@ private function entryInserted(CacheEntryInsertedEvent $event): void { } private function entryRemoved(CacheEntryRemovedEvent $event): void { - $this->log('Cache entry removed for fileid "%1$d", path "%2$s" on storageid "%3$d"', + $this->log(Operation::CacheEntryRemoved, 'Cache entry removed for fileid "%1$d", path "%2$s" on storageid "%3$d"', [ 'fileid' => $event->getFileId(), 'path' => $event->getPath(), diff --git a/apps/admin_audit/lib/Listener/ConsoleEventListener.php b/apps/admin_audit/lib/Listener/ConsoleEventListener.php index 8527c6331fd77..2ed8aa028561c 100644 --- a/apps/admin_audit/lib/Listener/ConsoleEventListener.php +++ b/apps/admin_audit/lib/Listener/ConsoleEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\Console\ConsoleEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -35,7 +36,7 @@ private function runCommand(ConsoleEvent $event): void { // Remove `./occ` array_shift($arguments); - $this->log('Console command executed: %s', + $this->log(Operation::ConsoleCommandExecuted, 'Console command executed: %s', ['arguments' => implode(' ', $arguments)], ['arguments'] ); diff --git a/apps/admin_audit/lib/Listener/CriticalActionPerformedEventListener.php b/apps/admin_audit/lib/Listener/CriticalActionPerformedEventListener.php index 38c6922b015d3..743cabbc0c936 100644 --- a/apps/admin_audit/lib/Listener/CriticalActionPerformedEventListener.php +++ b/apps/admin_audit/lib/Listener/CriticalActionPerformedEventListener.php @@ -23,6 +23,7 @@ public function handle(Event $event): void { } $this->log( + $event->getOperation(), $event->getLogMessage(), $event->getParameters(), array_keys($event->getParameters()), diff --git a/apps/admin_audit/lib/Listener/FileEventListener.php b/apps/admin_audit/lib/Listener/FileEventListener.php index 20c0a2831f922..1d8902673e26c 100644 --- a/apps/admin_audit/lib/Listener/FileEventListener.php +++ b/apps/admin_audit/lib/Listener/FileEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCA\Files_Versions\Events\VersionRestoredEvent; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; @@ -47,6 +48,7 @@ private function beforePreviewFetched(BeforePreviewFetchedEvent $event): void { 'path' => $file->getPath(), ]; $this->log( + Operation::PreviewAccessed, 'Preview accessed: (id: "%s", width: "%s", height: "%s" crop: "%s", mode: "%s", path: "%s")', $params, array_keys($params) @@ -64,7 +66,7 @@ private function beforePreviewFetched(BeforePreviewFetchedEvent $event): void { */ private function versionRestored(VersionRestoredEvent $event): void { $version = $event->getVersion(); - $this->log('Version "%s" of "%s" was restored.', + $this->log(Operation::VersionRestored, 'Version "%s" of "%s" was restored.', [ 'version' => $version->getRevisionId(), 'path' => $version->getVersionPath() diff --git a/apps/admin_audit/lib/Listener/GroupManagementEventListener.php b/apps/admin_audit/lib/Listener/GroupManagementEventListener.php index b17ff74829602..25d123ca39c6e 100644 --- a/apps/admin_audit/lib/Listener/GroupManagementEventListener.php +++ b/apps/admin_audit/lib/Listener/GroupManagementEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Group\Events\GroupCreatedEvent; @@ -35,7 +36,7 @@ public function handle(Event $event): void { } private function userAdded(UserAddedEvent $event): void { - $this->log('User "%s" added to group "%s"', + $this->log(Operation::GroupMemberAdded, 'User "%s" added to group "%s"', [ 'group' => $event->getGroup()->getGID(), 'user' => $event->getUser()->getUID() @@ -47,7 +48,7 @@ private function userAdded(UserAddedEvent $event): void { } private function userRemoved(UserRemovedEvent $event): void { - $this->log('User "%s" removed from group "%s"', + $this->log(Operation::GroupMemberRemoved, 'User "%s" removed from group "%s"', [ 'group' => $event->getGroup()->getGID(), 'user' => $event->getUser()->getUID() @@ -59,7 +60,7 @@ private function userRemoved(UserRemovedEvent $event): void { } private function groupCreated(GroupCreatedEvent $event): void { - $this->log('Group created: "%s"', + $this->log(Operation::GroupCreated, 'Group created: "%s"', [ 'group' => $event->getGroup()->getGID() ], @@ -70,7 +71,7 @@ private function groupCreated(GroupCreatedEvent $event): void { } private function groupDeleted(GroupDeletedEvent $event): void { - $this->log('Group deleted: "%s"', + $this->log(Operation::GroupDeleted, 'Group deleted: "%s"', [ 'group' => $event->getGroup()->getGID() ], diff --git a/apps/admin_audit/lib/Listener/SecurityEventListener.php b/apps/admin_audit/lib/Listener/SecurityEventListener.php index 192433c54b9bc..994867c0fcaf8 100644 --- a/apps/admin_audit/lib/Listener/SecurityEventListener.php +++ b/apps/admin_audit/lib/Listener/SecurityEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\Authentication\TwoFactorAuth\TwoFactorProviderChallengeFailed; use OCP\Authentication\TwoFactorAuth\TwoFactorProviderChallengePassed; use OCP\EventDispatcher\Event; @@ -30,6 +31,7 @@ public function handle(Event $event): void { private function twoFactorProviderChallengePassed(TwoFactorProviderChallengePassed $event): void { $this->log( + Operation::TwoFactorPassed, 'Successful two factor attempt by user %s (%s) with provider %s', [ 'uid' => $event->getUser()->getUID(), @@ -46,6 +48,7 @@ private function twoFactorProviderChallengePassed(TwoFactorProviderChallengePass private function twoFactorProviderChallengeFailed(TwoFactorProviderChallengeFailed $event): void { $this->log( + Operation::TwoFactorFailed, 'Failed two factor attempt by user %s (%s) with provider %s', [ 'uid' => $event->getUser()->getUID(), diff --git a/apps/admin_audit/lib/Listener/SharingEventListener.php b/apps/admin_audit/lib/Listener/SharingEventListener.php index 3131a09aaab99..96599c1dc42e5 100644 --- a/apps/admin_audit/lib/Listener/SharingEventListener.php +++ b/apps/admin_audit/lib/Listener/SharingEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\Share\Events\ShareCreatedEvent; @@ -43,6 +44,7 @@ private function shareCreated(ShareCreatedEvent $event): void { match ($share->getShareType()) { IShare::TYPE_LINK => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared via link with permissions "%s" (Share ID: %s)', $params, [ @@ -54,6 +56,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_USER => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the user "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -66,6 +69,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_GROUP => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the group "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -78,6 +82,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_ROOM => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the room "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -90,6 +95,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_EMAIL => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the email recipient "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -102,6 +108,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_CIRCLE => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the circle "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -114,6 +121,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_REMOTE => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the remote user "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -126,6 +134,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_REMOTE_GROUP => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the remote group "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -138,6 +147,7 @@ private function shareCreated(ShareCreatedEvent $event): void { ] ), IShare::TYPE_DECK => $this->log( + Operation::ShareCreated, 'The %s "%s" with ID "%s" has been shared to the deck card "%s" with permissions "%s" (Share ID: %s)', $params, [ @@ -166,6 +176,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { match ($share->getShareType()) { IShare::TYPE_LINK => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared (Share ID: %s)', $params, [ @@ -176,6 +187,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_USER => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the user "%s" (Share ID: %s)', $params, [ @@ -187,6 +199,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_GROUP => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the group "%s" (Share ID: %s)', $params, [ @@ -198,6 +211,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_ROOM => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the room "%s" (Share ID: %s)', $params, [ @@ -209,6 +223,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_EMAIL => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the email recipient "%s" (Share ID: %s)', $params, [ @@ -220,6 +235,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_CIRCLE => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the circle "%s" (Share ID: %s)', $params, [ @@ -231,6 +247,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_REMOTE => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the remote user "%s" (Share ID: %s)', $params, [ @@ -242,6 +259,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_REMOTE_GROUP => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the remote group "%s" (Share ID: %s)', $params, [ @@ -253,6 +271,7 @@ private function shareDeleted(ShareDeletedEvent $event): void { ] ), IShare::TYPE_DECK => $this->log( + Operation::ShareDeleted, 'The %s "%s" with ID "%s" has been unshared from the deck card "%s" (Share ID: %s)', $params, [ diff --git a/apps/admin_audit/lib/Listener/TagEventListener.php b/apps/admin_audit/lib/Listener/TagEventListener.php index ca90ce10f621e..4330393dd98ab 100644 --- a/apps/admin_audit/lib/Listener/TagEventListener.php +++ b/apps/admin_audit/lib/Listener/TagEventListener.php @@ -11,6 +11,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\SystemTag\Events\TagCreatedEvent; @@ -28,7 +29,7 @@ public function handle(Event $event): void { $tag = $event->getTag(); - $this->log('System tag "%s" (%s, %s) created', + $this->log(Operation::SystemTagCreated, 'System tag "%s" (%s, %s) created', [ 'name' => $tag->getName(), 'visibility' => $tag->isUserVisible() ? 'visible' : 'invisible', diff --git a/apps/admin_audit/lib/Listener/UserManagementEventListener.php b/apps/admin_audit/lib/Listener/UserManagementEventListener.php index 37767d11f4b5c..661fef2351705 100644 --- a/apps/admin_audit/lib/Listener/UserManagementEventListener.php +++ b/apps/admin_audit/lib/Listener/UserManagementEventListener.php @@ -10,6 +10,7 @@ namespace OCA\AdminAudit\Listener; use OCA\AdminAudit\Actions\Action; +use OCA\AdminAudit\Operation; use OCP\EventDispatcher\Event; use OCP\EventDispatcher\IEventListener; use OCP\User\Events\PasswordUpdatedEvent; @@ -42,6 +43,7 @@ public function handle(Event $event): void { private function userCreated(UserCreatedEvent $event): void { $this->log( + Operation::UserCreated, 'User created: "%s"', [ 'uid' => $event->getUid() @@ -54,6 +56,7 @@ private function userCreated(UserCreatedEvent $event): void { private function userDeleted(UserDeletedEvent $event): void { $this->log( + Operation::UserDeleted, 'User deleted: "%s"', [ 'uid' => $event->getUser()->getUID() @@ -67,10 +70,10 @@ private function userDeleted(UserDeletedEvent $event): void { private function userChanged(UserChangedEvent $event): void { switch ($event->getFeature()) { case 'enabled': + $enabled = $event->getValue() === true; $this->log( - $event->getValue() === true - ? 'User enabled: "%s"' - : 'User disabled: "%s"', + $enabled ? Operation::UserEnabled : Operation::UserDisabled, + $enabled ? 'User enabled: "%s"' : 'User disabled: "%s"', ['user' => $event->getUser()->getUID()], [ 'user', @@ -79,6 +82,7 @@ private function userChanged(UserChangedEvent $event): void { break; case 'eMailAddress': $this->log( + Operation::UserEmailChanged, 'Email address changed for user %s', ['user' => $event->getUser()->getUID()], [ @@ -92,6 +96,7 @@ private function userChanged(UserChangedEvent $event): void { private function passwordUpdated(PasswordUpdatedEvent $event): void { if ($event->getUser()->getBackendClassName() === 'Database') { $this->log( + Operation::UserPasswordChanged, 'Password of user "%s" has been changed', [ 'user' => $event->getUser()->getUID(), @@ -108,6 +113,7 @@ private function passwordUpdated(PasswordUpdatedEvent $event): void { */ private function userIdAssigned(UserIdAssignedEvent $event): void { $this->log( + Operation::UserIdAssigned, 'UserID assigned: "%s"', [ 'uid' => $event->getUserId() ], [ 'uid' ] @@ -119,6 +125,7 @@ private function userIdAssigned(UserIdAssignedEvent $event): void { */ private function userIdUnassigned(UserIdUnassignedEvent $event): void { $this->log( + Operation::UserIdUnassigned, 'UserID unassigned: "%s"', [ 'uid' => $event->getUserId() ], [ 'uid' ] diff --git a/apps/admin_audit/lib/Operation.php b/apps/admin_audit/lib/Operation.php new file mode 100644 index 0000000000000..c88bffdff2178 --- /dev/null +++ b/apps/admin_audit/lib/Operation.php @@ -0,0 +1,75 @@ +logger = $this->createMock(IAuditLogger::class); + $this->action = new Action($this->logger); + } + + public function testLogAddsOperationToContext(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('File "/a.txt" deleted', ['app' => 'admin_audit', 'operation' => 'files.file.deleted']); + + $this->action->log(Operation::FileDeleted, 'File "%s" deleted', ['path' => '/a.txt'], ['path']); + } + + public function testLogAcceptsOperationString(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('Share "42" accepted', ['app' => 'admin_audit', 'operation' => 'federatedfilesharing.share.accepted']); + + $this->action->log('federatedfilesharing.share.accepted', 'Share "%s" accepted', ['id' => '42'], ['id']); + } + + public function testLogWithoutOperation(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('File "/a.txt" deleted', ['app' => 'admin_audit']); + + $this->action->log(null, 'File "%s" deleted', ['path' => '/a.txt'], ['path']); + } + + public function testLogWithOperationAndNamedPlaceholders(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with( + 'File "{path}" deleted', + ['app' => 'admin_audit', 'operation' => 'files.file.deleted', 'path' => '/a.txt'], + ); + + $this->action->log(Operation::FileDeleted, 'File "{path}" deleted', ['path' => '/a.txt'], ['path']); + } + + public function testMissingParameterKeepsOperation(): void { + $this->logger->expects($this->never()) + ->method('info'); + $this->logger->expects($this->once()) + ->method('critical') + ->with( + '$params["path"] was missing. Transferred value: {params}', + ['app' => 'admin_audit', 'operation' => 'files.file.deleted', 'params' => ['id' => 42]], + ); + + $this->action->log(Operation::FileDeleted, 'File "%s" deleted', ['id' => 42], ['path']); + } + + public function testMissingParameterObfuscatedKeepsOperation(): void { + $this->logger->expects($this->once()) + ->method('critical') + ->with( + '$params["uid"] was missing.', + ['app' => 'admin_audit', 'operation' => 'auth.login.failed'], + ); + + $this->action->log(Operation::LoginFailed, 'Login failed: "%s"', [], ['uid'], true); + } +} diff --git a/apps/admin_audit/tests/Actions/FilesTest.php b/apps/admin_audit/tests/Actions/FilesTest.php new file mode 100644 index 0000000000000..39da7533e600e --- /dev/null +++ b/apps/admin_audit/tests/Actions/FilesTest.php @@ -0,0 +1,79 @@ +logger = $this->createMock(IAuditLogger::class); + $this->files = new Files($this->logger); + + $this->source = $this->createMock(File::class); + $this->source->method('getId')->willReturn(41); + $this->source->method('getPath')->willReturn('/alice/files/a.txt'); + $this->target = $this->createMock(File::class); + $this->target->method('getId')->willReturn(42); + $this->target->method('getPath')->willReturn('/alice/files/b.txt'); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testRead(): void { + $this->expectInfo('File with id "42" accessed: "/alice/files/b.txt"', 'files.file.read'); + $this->files->read(new BeforeNodeReadEvent($this->target)); + } + + public function testRename(): void { + $this->expectInfo('File renamed with id "42" from "/alice/files/a.txt" to "/alice/files/b.txt"', 'files.file.renamed'); + $this->files->afterRename(new NodeRenamedEvent($this->source, $this->target)); + } + + public function testCreate(): void { + $this->expectInfo('File with id "42" created: "/alice/files/b.txt"', 'files.file.created'); + $this->files->create(new NodeCreatedEvent($this->target)); + } + + public function testCopy(): void { + $this->expectInfo('File id copied from: "41" to "42", path from "/alice/files/a.txt" to "/alice/files/b.txt"', 'files.file.copied'); + $this->files->copy(new NodeCopiedEvent($this->source, $this->target)); + } + + public function testWrite(): void { + $this->expectInfo('File with id "42" written to: "/alice/files/b.txt"', 'files.file.written'); + $this->files->write(new NodeWrittenEvent($this->target)); + } + + public function testDelete(): void { + $this->expectInfo('File with id "42" deleted: "/alice/files/b.txt"', 'files.file.deleted'); + $this->files->delete(new BeforeNodeDeletedEvent($this->target)); + } +} diff --git a/apps/admin_audit/tests/Actions/SharingTest.php b/apps/admin_audit/tests/Actions/SharingTest.php new file mode 100644 index 0000000000000..4aafa0c0b687c --- /dev/null +++ b/apps/admin_audit/tests/Actions/SharingTest.php @@ -0,0 +1,79 @@ +logger = $this->createMock(IAuditLogger::class); + $this->sharing = new Sharing($this->logger); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testUpdatePermissions(): void { + $this->expectInfo('The permissions of the shared file "/a.txt" with ID "42" have been changed to "1"', 'sharing.share.permissions_updated'); + $this->sharing->updatePermissions([ + 'itemType' => 'file', + 'path' => '/a.txt', + 'itemSource' => 42, + 'permissions' => 1, + ]); + } + + public function testUpdatePassword(): void { + $this->expectInfo('The password of the publicly shared file "abc" with ID "42" has been changed', 'sharing.share.password_updated'); + $this->sharing->updatePassword([ + 'itemType' => 'file', + 'token' => 'abc', + 'itemSource' => 42, + ]); + } + + public function testExpirationDateRemoved(): void { + $this->expectInfo('The expiration date of the publicly shared file with ID "42" has been removed', 'sharing.share.expiration_removed'); + $this->sharing->updateExpirationDate([ + 'itemType' => 'file', + 'itemSource' => 42, + 'date' => null, + ]); + } + + public function testExpirationDateUpdated(): void { + $this->expectInfo('The expiration date of the publicly shared file with ID "42" has been changed to "2026-10-31 00:00:00"', 'sharing.share.expiration_updated'); + $this->sharing->updateExpirationDate([ + 'itemType' => 'file', + 'itemSource' => 42, + 'date' => new \DateTimeImmutable('2026-10-31 00:00:00'), + ]); + } + + public function testShareAccessed(): void { + $this->expectInfo('The shared file with the token "abc" by "alice" has been accessed.', 'sharing.share.link_accessed'); + $this->sharing->shareAccessed([ + 'itemType' => 'file', + 'token' => 'abc', + 'uidOwner' => 'alice', + ]); + } +} diff --git a/apps/admin_audit/tests/Actions/TrashbinTest.php b/apps/admin_audit/tests/Actions/TrashbinTest.php new file mode 100644 index 0000000000000..c78b4be6d86b7 --- /dev/null +++ b/apps/admin_audit/tests/Actions/TrashbinTest.php @@ -0,0 +1,43 @@ +logger = $this->createMock(IAuditLogger::class); + $this->trashbin = new Trashbin($this->logger); + } + + public function testDelete(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('File "/a.txt.d1700000000" deleted from trash bin.', ['app' => 'admin_audit', 'operation' => 'trashbin.file.deleted']); + + $this->trashbin->delete(['path' => '/a.txt.d1700000000']); + } + + public function testRestore(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('File "/a.txt" restored from trash bin.', ['app' => 'admin_audit', 'operation' => 'trashbin.file.restored']); + + $this->trashbin->restore(['filePath' => '/a.txt', 'trashPath' => '/a.txt.d1700000000']); + } +} diff --git a/apps/admin_audit/tests/Actions/VersionsTest.php b/apps/admin_audit/tests/Actions/VersionsTest.php new file mode 100644 index 0000000000000..3d909fc8412ac --- /dev/null +++ b/apps/admin_audit/tests/Actions/VersionsTest.php @@ -0,0 +1,35 @@ +logger = $this->createMock(IAuditLogger::class); + $this->versions = new Versions($this->logger); + } + + public function testDelete(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('Version "/a.txt.v1700000000" was deleted.', ['app' => 'admin_audit', 'operation' => 'versions.version.deleted']); + + $this->versions->delete(['path' => '/a.txt.v1700000000']); + } +} diff --git a/apps/admin_audit/tests/Listener/AppManagementEventListenerTest.php b/apps/admin_audit/tests/Listener/AppManagementEventListenerTest.php new file mode 100644 index 0000000000000..aa8c895a2160a --- /dev/null +++ b/apps/admin_audit/tests/Listener/AppManagementEventListenerTest.php @@ -0,0 +1,56 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new AppManagementEventListener($this->logger); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testAppEnabled(): void { + $this->expectInfo('App "files" enabled', 'apps.app.enabled'); + $this->listener->handle(new AppEnableEvent('files')); + } + + public function testAppEnabledForGroups(): void { + $this->expectInfo('App "files" enabled for groups: admin, staff', 'apps.app.enabled'); + $this->listener->handle(new AppEnableEvent('files', ['admin', 'staff'])); + } + + public function testAppDisabled(): void { + $this->expectInfo('App "files" disabled', 'apps.app.disabled'); + $this->listener->handle(new AppDisableEvent('files')); + } + + public function testAppUpdated(): void { + $this->expectInfo('App "files" updated', 'apps.app.updated'); + $this->listener->handle(new AppUpdateEvent('files')); + } +} diff --git a/apps/admin_audit/tests/Listener/AuthEventListenerTest.php b/apps/admin_audit/tests/Listener/AuthEventListenerTest.php new file mode 100644 index 0000000000000..c5296e139ca77 --- /dev/null +++ b/apps/admin_audit/tests/Listener/AuthEventListenerTest.php @@ -0,0 +1,68 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new AuthEventListener($this->logger); + + $this->user = $this->createMock(IUser::class); + $this->user->method('getUID')->willReturn('alice'); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testLoginAttempt(): void { + $this->expectInfo('Login attempt: "alice"', 'auth.login.attempted'); + $this->listener->handle(new BeforeUserLoggedInEvent('alice', 'password')); + } + + public function testLoginSucceeded(): void { + $this->expectInfo('Login successful: "alice"', 'auth.login.succeeded'); + $this->listener->handle(new UserLoggedInEvent($this->user, 'alice', 'password', false)); + } + + public function testLoginWithCookieSucceeded(): void { + $this->expectInfo('Login successful: "alice"', 'auth.login.succeeded'); + $this->listener->handle(new UserLoggedInWithCookieEvent($this->user, null)); + } + + public function testLogout(): void { + $this->expectInfo('Logout occurred', 'auth.logout.performed'); + $this->listener->handle(new BeforeUserLoggedOutEvent($this->user)); + } + + public function testLoginFailed(): void { + $this->expectInfo('Login failed: "alice"', 'auth.login.failed'); + $this->listener->handle(new AnyLoginFailedEvent('alice', 'password')); + } +} diff --git a/apps/admin_audit/tests/Listener/CacheEventListenerTest.php b/apps/admin_audit/tests/Listener/CacheEventListenerTest.php new file mode 100644 index 0000000000000..408deb04c47a7 --- /dev/null +++ b/apps/admin_audit/tests/Listener/CacheEventListenerTest.php @@ -0,0 +1,48 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new CacheEventListener($this->logger); + $this->storage = $this->createMock(IStorage::class); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testEntryInserted(): void { + $this->expectInfo('Cache entry inserted for fileid "42", path "files/a.txt" on storageid "3"', 'files.cache_entry.inserted'); + $this->listener->handle(new CacheEntryInsertedEvent($this->storage, 'files/a.txt', 42, 3)); + } + + public function testEntryRemoved(): void { + $this->expectInfo('Cache entry removed for fileid "42", path "files/a.txt" on storageid "3"', 'files.cache_entry.removed'); + $this->listener->handle(new CacheEntryRemovedEvent($this->storage, 'files/a.txt', 42, 3)); + } +} diff --git a/apps/admin_audit/tests/Listener/ConsoleEventListenerTest.php b/apps/admin_audit/tests/Listener/ConsoleEventListenerTest.php new file mode 100644 index 0000000000000..98d768c904c54 --- /dev/null +++ b/apps/admin_audit/tests/Listener/ConsoleEventListenerTest.php @@ -0,0 +1,36 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new ConsoleEventListener($this->logger); + } + + public function testCommandExecuted(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('Console command executed: user:disable carol', ['app' => 'admin_audit', 'operation' => 'console.command.executed']); + + $this->listener->handle(new ConsoleEvent(ConsoleEvent::EVENT_RUN, ['occ', 'user:disable', 'carol'])); + } +} diff --git a/apps/admin_audit/tests/Listener/CriticalActionPerformedEventListenerTest.php b/apps/admin_audit/tests/Listener/CriticalActionPerformedEventListenerTest.php index 7279a0aec25d5..0777dee5991a4 100644 --- a/apps/admin_audit/tests/Listener/CriticalActionPerformedEventListenerTest.php +++ b/apps/admin_audit/tests/Listener/CriticalActionPerformedEventListenerTest.php @@ -47,4 +47,20 @@ public function testNamedPlaceholders(): void { ['ip' => '10.0.0.1', 'action' => 'login'], )); } + + public function testOperationIsPassedThrough(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with( + 'Federated share with id "42" was accepted', + ['app' => 'admin_audit', 'operation' => 'federatedfilesharing.share.accepted'], + ); + + $this->listener->handle(new CriticalActionPerformedEvent( + 'Federated share with id "%s" was accepted', + ['42'], + false, + 'federatedfilesharing.share.accepted', + )); + } } diff --git a/apps/admin_audit/tests/Listener/FileEventListenerTest.php b/apps/admin_audit/tests/Listener/FileEventListenerTest.php new file mode 100644 index 0000000000000..6673ba2d86855 --- /dev/null +++ b/apps/admin_audit/tests/Listener/FileEventListenerTest.php @@ -0,0 +1,58 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new FileEventListener($this->logger); + } + + public function testPreviewAccessed(): void { + $file = $this->createMock(File::class); + $file->method('getId')->willReturn(42); + $file->method('getPath')->willReturn('/alice/files/a.jpg'); + + $this->logger->expects($this->once()) + ->method('info') + ->with( + 'Preview accessed: (id: "42", width: "64", height: "64" crop: "1", mode: "fill", path: "/alice/files/a.jpg")', + ['app' => 'admin_audit', 'operation' => 'files.preview.accessed'], + ); + + $this->listener->handle(new BeforePreviewFetchedEvent($file, 64, 64, true, 'fill')); + } + + public function testVersionRestored(): void { + $version = $this->createMock(IVersion::class); + $version->method('getRevisionId')->willReturn(1700000000); + $version->method('getVersionPath')->willReturn('/a.txt'); + + $this->logger->expects($this->once()) + ->method('info') + ->with('Version "1700000000" of "/a.txt" was restored.', ['app' => 'admin_audit', 'operation' => 'versions.version.restored']); + + $this->listener->handle(new VersionRestoredEvent($version)); + } +} diff --git a/apps/admin_audit/tests/Listener/GroupManagementEventListenerTest.php b/apps/admin_audit/tests/Listener/GroupManagementEventListenerTest.php new file mode 100644 index 0000000000000..875aff806b442 --- /dev/null +++ b/apps/admin_audit/tests/Listener/GroupManagementEventListenerTest.php @@ -0,0 +1,66 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new GroupManagementEventListener($this->logger); + + $this->group = $this->createMock(IGroup::class); + $this->group->method('getGID')->willReturn('admins'); + $this->user = $this->createMock(IUser::class); + $this->user->method('getUID')->willReturn('alice'); + } + + private function expectInfo(string $message, string $operation): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($message, ['app' => 'admin_audit', 'operation' => $operation]); + } + + public function testUserAdded(): void { + $this->expectInfo('User "alice" added to group "admins"', 'groups.member.added'); + $this->listener->handle(new UserAddedEvent($this->group, $this->user)); + } + + public function testUserRemoved(): void { + $this->expectInfo('User "alice" removed from group "admins"', 'groups.member.removed'); + $this->listener->handle(new UserRemovedEvent($this->group, $this->user)); + } + + public function testGroupCreated(): void { + $this->expectInfo('Group created: "admins"', 'groups.group.created'); + $this->listener->handle(new GroupCreatedEvent($this->group)); + } + + public function testGroupDeleted(): void { + $this->expectInfo('Group deleted: "admins"', 'groups.group.deleted'); + $this->listener->handle(new GroupDeletedEvent($this->group)); + } +} diff --git a/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php b/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php index 482301085308d..25d5ca4fd6dcf 100644 --- a/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php +++ b/apps/admin_audit/tests/Listener/SecurityEventListenerTest.php @@ -46,7 +46,7 @@ public function testTwofactorFailed(): void { ->method('info') ->with( $this->equalTo('Failed two factor attempt by user mydisplayname (myuid) with provider myprovider'), - ['app' => 'admin_audit'] + ['app' => 'admin_audit', 'operation' => 'auth.twofactor.failed'] ); $this->security->handle(new twoFactorProviderChallengeFailed($this->user, $this->provider)); @@ -57,7 +57,7 @@ public function testTwofactorSuccess(): void { ->method('info') ->with( $this->equalTo('Successful two factor attempt by user mydisplayname (myuid) with provider myprovider'), - ['app' => 'admin_audit'] + ['app' => 'admin_audit', 'operation' => 'auth.twofactor.passed'] ); $this->security->handle(new TwoFactorProviderChallengePassed($this->user, $this->provider)); diff --git a/apps/admin_audit/tests/Listener/SharingEventListenerTest.php b/apps/admin_audit/tests/Listener/SharingEventListenerTest.php new file mode 100644 index 0000000000000..1282d98db9cfb --- /dev/null +++ b/apps/admin_audit/tests/Listener/SharingEventListenerTest.php @@ -0,0 +1,102 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new SharingEventListener($this->logger); + } + + private function createShare(int $shareType): IShare&MockObject { + $node = $this->createMock(File::class); + $node->method('getPath')->willReturn('/alice/files/a.txt'); + + $share = $this->createMock(IShare::class); + $share->method('getShareType')->willReturn($shareType); + $share->method('getNodeType')->willReturn('file'); + $share->method('getNode')->willReturn($node); + $share->method('getNodeId')->willReturn(42); + $share->method('getSharedWith')->willReturn('bob'); + $share->method('getPermissions')->willReturn(19); + $share->method('getId')->willReturn('7'); + $share->method('getTarget')->willReturn('/a.txt'); + return $share; + } + + public static function shareTypeProvider(): array { + return [ + 'link' => [IShare::TYPE_LINK], + 'user' => [IShare::TYPE_USER], + 'group' => [IShare::TYPE_GROUP], + 'room' => [IShare::TYPE_ROOM], + 'email' => [IShare::TYPE_EMAIL], + 'circle' => [IShare::TYPE_CIRCLE], + 'remote' => [IShare::TYPE_REMOTE], + 'remote group' => [IShare::TYPE_REMOTE_GROUP], + 'deck' => [IShare::TYPE_DECK], + ]; + } + + #[DataProvider('shareTypeProvider')] + public function testShareCreatedOperation(int $shareType): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($this->isString(), ['app' => 'admin_audit', 'operation' => 'sharing.share.created']); + + $this->listener->handle(new ShareCreatedEvent($this->createShare($shareType))); + } + + #[DataProvider('shareTypeProvider')] + public function testShareDeletedOperation(int $shareType): void { + $this->logger->expects($this->once()) + ->method('info') + ->with($this->isString(), ['app' => 'admin_audit', 'operation' => 'sharing.share.deleted']); + + $this->listener->handle(new ShareDeletedEvent($this->createShare($shareType))); + } + + public function testUserShareCreatedMessage(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with( + 'The file "/alice/files/a.txt" with ID "42" has been shared to the user "bob" with permissions "19" (Share ID: 7)', + ['app' => 'admin_audit', 'operation' => 'sharing.share.created'], + ); + + $this->listener->handle(new ShareCreatedEvent($this->createShare(IShare::TYPE_USER))); + } + + public function testLinkShareDeletedMessage(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with( + 'The file "/a.txt" with ID "42" has been unshared (Share ID: 7)', + ['app' => 'admin_audit', 'operation' => 'sharing.share.deleted'], + ); + + $this->listener->handle(new ShareDeletedEvent($this->createShare(IShare::TYPE_LINK))); + } +} diff --git a/apps/admin_audit/tests/Listener/TagEventListenerTest.php b/apps/admin_audit/tests/Listener/TagEventListenerTest.php new file mode 100644 index 0000000000000..75953f9a25067 --- /dev/null +++ b/apps/admin_audit/tests/Listener/TagEventListenerTest.php @@ -0,0 +1,42 @@ +logger = $this->createMock(IAuditLogger::class); + $this->listener = new TagEventListener($this->logger); + } + + public function testTagCreated(): void { + $tag = $this->createMock(ISystemTag::class); + $tag->method('getName')->willReturn('confidential'); + $tag->method('isUserVisible')->willReturn(false); + $tag->method('isUserAssignable')->willReturn(false); + + $this->logger->expects($this->once()) + ->method('info') + ->with('System tag "confidential" (invisible, system only) created', ['app' => 'admin_audit', 'operation' => 'systemtags.tag.created']); + + $this->listener->handle(new TagCreatedEvent($tag)); + } +} diff --git a/apps/admin_audit/tests/Listener/UserManagementEventListenerTest.php b/apps/admin_audit/tests/Listener/UserManagementEventListenerTest.php index feb67ad0166e4..00fada24f8474 100644 --- a/apps/admin_audit/tests/Listener/UserManagementEventListenerTest.php +++ b/apps/admin_audit/tests/Listener/UserManagementEventListenerTest.php @@ -11,7 +11,12 @@ use OCA\AdminAudit\IAuditLogger; use OCA\AdminAudit\Listener\UserManagementEventListener; use OCP\IUser; +use OCP\User\Events\PasswordUpdatedEvent; use OCP\User\Events\UserChangedEvent; +use OCP\User\Events\UserCreatedEvent; +use OCP\User\Events\UserDeletedEvent; +use OCP\User\Events\UserIdAssignedEvent; +use OCP\User\Events\UserIdUnassignedEvent; use PHPUnit\Framework\MockObject\MockObject; use Test\TestCase; @@ -49,7 +54,7 @@ public function testSkipUnsupported(): void { public function testUserEnabled(): void { $this->logger->expects($this->once()) ->method('info') - ->with('User enabled: "alice"', ['app' => 'admin_audit']); + ->with('User enabled: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.enabled']); $event = new UserChangedEvent( $this->user, @@ -64,7 +69,7 @@ public function testUserEnabled(): void { public function testUserDisabled(): void { $this->logger->expects($this->once()) ->method('info') - ->with('User disabled: "alice"', ['app' => 'admin_audit']); + ->with('User disabled: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.disabled']); $event = new UserChangedEvent( $this->user, @@ -79,7 +84,7 @@ public function testUserDisabled(): void { public function testEmailChanged(): void { $this->logger->expects($this->once()) ->method('info') - ->with('Email address changed for user alice', ['app' => 'admin_audit']); + ->with('Email address changed for user alice', ['app' => 'admin_audit', 'operation' => 'users.user.email_changed']); $event = new UserChangedEvent( $this->user, @@ -90,4 +95,45 @@ public function testEmailChanged(): void { $this->listener->handle($event); } + + public function testUserCreated(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('User created: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.created']); + + $this->listener->handle(new UserCreatedEvent($this->user, 'password')); + } + + public function testUserDeleted(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('User deleted: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.deleted']); + + $this->listener->handle(new UserDeletedEvent($this->user)); + } + + public function testPasswordUpdated(): void { + $this->user->method('getBackendClassName')->willReturn('Database'); + $this->logger->expects($this->once()) + ->method('info') + ->with('Password of user "alice" has been changed', ['app' => 'admin_audit', 'operation' => 'users.user.password_changed']); + + $this->listener->handle(new PasswordUpdatedEvent($this->user, 'password')); + } + + public function testUserIdAssigned(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('UserID assigned: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.id_assigned']); + + $this->listener->handle(new UserIdAssignedEvent('alice')); + } + + public function testUserIdUnassigned(): void { + $this->logger->expects($this->once()) + ->method('info') + ->with('UserID unassigned: "alice"', ['app' => 'admin_audit', 'operation' => 'users.user.id_unassigned']); + + $this->listener->handle(new UserIdUnassignedEvent('alice')); + } } diff --git a/lib/public/Log/Audit/CriticalActionPerformedEvent.php b/lib/public/Log/Audit/CriticalActionPerformedEvent.php index dbee799f5c5fe..6ace9f00f8362 100644 --- a/lib/public/Log/Audit/CriticalActionPerformedEvent.php +++ b/lib/public/Log/Audit/CriticalActionPerformedEvent.php @@ -26,19 +26,25 @@ class CriticalActionPerformedEvent extends Event { /** @var bool */ private $obfuscateParameters; + private ?string $operation; + /** * @param string $logMessage * @param array $parameters * @param bool $obfuscateParameters + * @param ?string $operation Stable identifier of the action in the form `app.entity.action`, e.g. `federatedfilesharing.share.accepted` * @since 22.0.0 + * @since 36.0.0 added the $operation parameter */ public function __construct(string $logMessage, array $parameters = [], - bool $obfuscateParameters = false) { + bool $obfuscateParameters = false, + ?string $operation = null) { parent::__construct(); $this->logMessage = $logMessage; $this->parameters = $parameters; $this->obfuscateParameters = $obfuscateParameters; + $this->operation = $operation; } /** @@ -64,4 +70,11 @@ public function getParameters(): array { public function getObfuscateParameters(): bool { return $this->obfuscateParameters; } + + /** + * @since 36.0.0 + */ + public function getOperation(): ?string { + return $this->operation; + } }