Skip to content

Feat/admin audit logging - #1195

Open
GhassenKefi wants to merge 5 commits into
masterfrom
feat/admin-audit-logging
Open

GhassenKefi wants to merge 5 commits into
masterfrom
feat/admin-audit-logging

Conversation

@GhassenKefi

@GhassenKefi GhassenKefi commented Oct 19, 2026 •

Copy link
Copy Markdown

Description

Related Issue or Workpackage

  • Fixes <issue_link>

Screenshots (if appropriate):

Types of changes

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Tests only (no source changes)

Checklist:

  • Code changes
  • Unit tests added
  • Acceptance tests added
  • Updated CHANGELOG.md file

The existing audit log entries (app password created, replaced and
deleted, project folder setup deactivated, integration reset, OpenProject
URL and OAuth client set, Nextcloud OAuth client created) are now
dispatched as CriticalActionPerformedEvent with named parameters and,
on servers that support it, an operation. The messages keep their
meaning; the misplaced quote in the app password messages is fixed. The
operations are collected in the new OCA\OpenProject\AuditLog\Operation
enum, with a short description of each for the documentation.

Before, OpenProjectAPIService::logToAuditFile() created admin_audit's
internal AuditLogger itself. Because that bypasses the admin_audit app,
it had to guess whether the app was installed and configured, and only
wrote when the audit log was the default data/audit.log file, log.condition
listed admin_audit and the log level was at least 1. With a custom
logfile_audit, a syslog or systemd audit log, or without log.condition,
nothing was logged. AuditLogger is also not a public API: since
Nextcloud 35 its constructor takes IAppConfig, so the direct construction
fails there. Now admin_audit decides itself: its listener only runs when
the app is enabled and writes to the audit log as configured, like every
other audit entry. The ILogFactory dependency and the psalm suppression
for AuditLogger are gone.

isAdminAuditConfigSetCorrectly() is now only used for the hint in the
admin settings and only checks that admin_audit is enabled, as the file
path and log.condition no longer matter.

The app password entry is now written by generateAppPasswordTokenForUser()
itself, which is told whether it replaces an existing app password. The
app password, the OpenProject client secret and the Nextcloud client
secret are never logged.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ghassen kefi <ghassen.kefi.dev@gmail.com>
Saving the integration settings is now logged for every way they are
saved: the admin settings form, the setup API (POST, PATCH and DELETE
/setup) and the reset. The entry lists the names of the saved settings,
the OpenProject URL, the authorization method and whether all users were
disconnected from OpenProject because the OAuth client or the
authorization method changed. The reset entry also tells whether users
were disconnected. Setting values other than the URL and the
authorization method are not logged, so the OpenProject client secret
never ends up in the audit log.

This replaces the two entries written only by the admin settings form,
"OpenProject host url has been set" and "OpenProject OAuth credential
... has been set", which missed the setup API and changes of the OIDC
settings. The saved setting names are logged instead of comparing each
value with the stored one, which would need an extra app config read per
setting.

Creating the Nextcloud OAuth client used by OpenProject is now logged
wherever it happens (the admin settings button and the setup API), with
the ID of the client and whether it replaced the previous one, instead
of only from the admin settings button. The client secret is not logged.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ghassen kefi <ghassen.kefi.dev@gmail.com>
…rvice

Setting up the project folder is now written to the audit log with the
groupfolder ID, the OpenProject system user and its two groups. Creating
the user and the groups is already logged by admin_audit, but making the
user group admin of them and creating the groupfolder whose advanced
permissions the user manages were not. createGroupfolder() now returns
the ID of the groupfolder for this.

Each terms of service signed on behalf of the OpenProject system user is
logged with the terms ID. This covers terms signed by an admin, by the
project folder setup and automatically by the app when the terms change.
Terms that were already signed are not logged again. Logging happens in
signTermsOfServiceForUserOpenProject() instead of the admin endpoint to
cover all of these. To test it, the signatory mapper can be passed in,
as for isAllTermsOfServiceSignedForUserOpenProject(), and the signatory
is created in its own method, as the terms_of_service app is not
available in the unit tests.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ghassen kefi <ghassen.kefi.dev@gmail.com>
A user connecting their OpenProject account, through the OAuth flow or
with a personal access token, is now written to the audit log with the
user ID and the OpenProject user ID. Removing the token in the personal
settings is logged as a disconnection. The access and refresh tokens are
never logged.

Failed connections are not logged. Users of the OIDC authorization
method are not logged: they never connect explicitly, their token is
fetched from the identity provider whenever needed. Disconnecting all
users because the OpenProject OAuth client or the authorization method
changed is part of the integration settings entries.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ghassen kefi <ghassen.kefi.dev@gmail.com>
Linking a file to an OpenProject work package is now written to the
audit log, one entry per file, with the file ID, the work package ID,
the user and the ID of the file link OpenProject created. Linking sends
the file ID, name and type to OpenProject, where the members of the
project can see them. Deleting a file link is logged with the file link
ID and the user.

Only IDs are logged, not the file names. Nothing is logged when
OpenProject rejects the request or answers with a malformed response.

Assisted-by: ClaudeCode:claude-opus-5-5
Signed-off-by: Ghassen kefi <ghassen.kefi.dev@gmail.com>
@GhassenKefi
GhassenKefi force-pushed the feat/admin-audit-logging branch from f139b75 to 92b2404 Compare October 20, 2026 15:46
@github-actions

Copy link
Copy Markdown

PHP Code Coverage

Coverage after merging feat/admin-audit-logging into master will be
71.93%
Coverage Report
FileStmtsBranchesFuncsLinesUncovered Lines
integration_openproject/server/apps/integration_openproject/lib
   Capabilities.php0%100%0%0%24, 31–40
   OIDCClientMapper.php0%100%0%0%29, 40–49, 52
   ServerVersion.php0%100%0%0%17, 27–29, 32, 41–42
   TokenEventFactory.php100%100%100%100%
integration_openproject/server/apps/integration_openproject/lib/AppInfo
   Application.php37.04%100%50%36%116–118, 121–135, 138, 140, 142–143, 146, 151–152, 156–159, 161–172, 174, 177, 181, 185–187, 220
integration_openproject/server/apps/integration_openproject/lib/AuditLog
   Operation.php100%100%100%100%
integration_openproject/server/apps/integration_openproject/lib/BackgroundJob
   RemoveExpiredDirectUploadTokens.php0%100%0%0%28, 30–32, 41–42
integration_openproject/server/apps/integration_openproject/lib/Controller
   ConfigController.php87.47%100%88.89%87.40%209, 395–397, 399–401, 450, 546, 562–565, 573, 577–580, 622–637, 678, 682–684, 686–688, 693–696, 713–716
   DirectDownloadController.php0%100%0%0%33–35, 50–52, 54–61
   DirectUploadController.php71.03%100%100%70.21%117–119, 162–164, 175, 179–182, 184, 194, 201, 217–219, 221–222, 225–230, 233, 235, 245–247, 253–255, 263–265, 280–282, 301, 306, 312
   FilesController.php85.83%100%83.33%85.95%181–182, 246, 255, 272–275, 280–282, 287–289, 301, 303, 306
   OpenProjectAPIController.php81.09%100%82.35%80.98%116, 155, 202–204, 207–214, 216–220, 222, 241, 266, 331, 381, 401, 448, 473–475, 478–482, 484, 66
   OpenProjectController.php96.45%100%80%96.95%241–245
integration_openproject/server/apps/integration_openproject/lib/Dashboard
   OpenProjectWidget.php0%100%0%0%101, 108–109, 111–116, 118–122, 124–126, 129–140, 61–66, 73, 80, 87, 94
integration_openproject/server/apps/integration_openproject/lib/Exception
   OpenprojectAvatarErrorException.php100%100%100%100%
   OpenprojectErrorException.php100%100%100%100%
   OpenprojectFileNotUploadedException.php100%100%100%100%
   OpenprojectGroupfolderSetupConflictException.php100%100%100%100%
   OpenprojectResponseException.php100%100%100%100%
   OpenprojectUnauthorizedUserException.php0%100%0%0%21
integration_openproject/server/apps/integration_openproject/lib/Listener
   BeforeGroupDeletedListener.php100%100%100%100%
   BeforeNodeInsideOpenProjectGroupfilderChangedListener.php0%100%0%0%46–48, 52–55, 57, 59, 62–63, 65, 67–70, 72–75, 77–79
   BeforeUserDeletedListener.php40%100%50%37.50%30, 38, 42–43, 45
   LoadAdditionalScriptsListener.php0%100%0%0%37–38, 46–47, 49, 51–52, 54
   LoadSidebarScript.php65.91%100%100%64.29%105, 77–88, 95, 98
   OpenProjectReferenceListener.php0%100%0%0%45–47, 54–55, 57, 59–60, 62–74
   TermsOfServiceEventListener.php0%100%0%0%41–42, 47–48, 50–51, 53–55, 58–62
   UserChangedListener.php0%100%0%0%34, 41–42, 45–50, 53
   UserLoggedInEventListener.php0%100%0%0%27, 35–36, 39, 42
integration_openproject/server/apps/integration_openproject/lib/Migration
   Version2001Date20221213083550.php0%100%0%0%30, 41–43, 46–54, 56–64, 66–68, 70
   Version20100Date20250820101358.php0%100%0%0%38, 47–53, 56
   Version2310Date20230116153411.php0%100%0%0%29, 32–35, 37–62, 64–65, 67
   Version2400Date20230504144300.php0%100%0%0%30, 41–44, 46
   Version2640Date20240628114301.php0%100%0%0%35, 47–49, 52–53, 56
   Version2900Date20250718065820.php0%100%0%0%22, 33–36, 38–39, 41–42, 45
integration_openproject/server/apps/integration_openproject/lib/Reference
   WorkPackageReferenceProvider.php51.67%100%25%58.33%100–101, 104, 108, 142, 150–151, 159, 37, 44, 51, 58–60, 87, 93–96, 99
integration_openproject/server/apps/integration_openproject/lib/Search
   OpenProjectSearchProvider.php0%100%0%0%100–102, 104–106, 109–110, 112–113, 116–125, 127–131, 52–55, 62, 69, 77, 79, 82, 89–90, 93–97, 99
   OpenProjectSearchResultEntry.php100%100%100%100%
integration_openproject/server/apps/integration_openproject/lib/Service
   DatabaseService.php42.31%100%60%40.43%108–112, 114, 63–76, 78–85
   DirectDownloadService.php100%100%100%100%
   DirectUploadService.php42.86%100%66.67%40%101, 62–65, 67–75, 95
   OauthService.php96.15%100%100%95.65%124, 144
   OpenProjectAPIService.php79.88%100%80.28%79.84%1018–1019, 1021, 1023–1024, 1071–1073, 1075–1077, 1080–1084, 1086–1088, 1098–1101,

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant