-
Notifications
You must be signed in to change notification settings - Fork 2.8k
Audit entries rework #19345
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v17/dev
Are you sure you want to change the base?
Audit entries rework #19345
Conversation
- Moved logic related to the IAuditEntryRepository from the AuditService to the new service - Introduced new Async methods - Using ids (for easier transition from the previous Write method) - Using keys - Moved and updated integration tests related to the audit entries to a new test class `AuditEntryServiceTests` - Added unit tests class `AuditEntryServiceTests` and added a few unit tests - Added migration to add columns for `performingUserKey` and `affectedUserKey` and convert existing user ids - Adjusted usages of the old AuditService.Write method to use the new one (mostly notification handlers)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Few pedantic comments 😁
private string FormatEmail(IMember? member) => | ||
member == null ? string.Empty : member.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{member.Email}>"; | ||
|
||
private string FormatEmail(IUser user) => user == null ? string.Empty : user.Email.IsNullOrWhiteSpace() ? string.Empty : $"<{user.Email}>"; | ||
private string FormatEmail(IUser? user) => user is not null && !user.Email.IsNullOrWhiteSpace() | ||
? $"<{user.Email}>" | ||
: string.Empty; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Might be pedantic here, but couldn't we combine these methods into one with:
private string FormatEmail(string? email) => email is not null && !email.IsNullOrWhiteSpace() ? $"<{email}>" : string.Empty;
And then just pass in the user?.Email and member?.Email respectively
async Task<int> GetUserId(Guid userKey) | ||
{ | ||
return userKey switch | ||
{ | ||
_ when userKey == Constants.Security.UnknownUserKey => Constants.Security.UnknownUserId, | ||
_ when await _userIdKeyResolver.TryGetAsync(userKey) is { Success: true } attempt => attempt.Result, | ||
_ => Constants.Security.UnknownUserId, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm personally not a fan of inline methods 🙈 I'd rather it be in its own method, and maybe even have a unit test or 2 covering the behavior 🫡
Also at least for me personally (this might be a old man take), I feel like the IF syntax here is a lot more readable as a human, and IIRC, it's not really a performance difference, as the compiler will optimize it.
private async Task<int> GetUserId(Guid userKey)
{
if (userKey == Constants.Security.UnknownUserKey)
{
return Constants.Security.UnknownUserId;
}
if (await _userIdKeyResolver.TryGetAsync(userKey) is {Success: true} attempt)
{
return attempt.Result;
}
return Constants.Security.UnknownUserId;
}
async Task<Guid?> GetUserKey(int userId) | ||
{ | ||
return userId switch | ||
{ | ||
Constants.Security.UnknownUserId => Constants.Security.UnknownUserKey, | ||
_ when await _userIdKeyResolver.TryGetAsync(userId) is { Success: true } attempt => attempt.Result, | ||
_ => Constants.Security.UnknownUserKey, | ||
}; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same comment as GetUserId 💪
@@ -264,115 +280,29 @@ public async Task<PagedModel<IAuditItem>> GetPagedItemsByUserAsync( | |||
} | |||
|
|||
/// <inheritdoc /> | |||
public IAuditEntry Write(int performingUserId, string perfomingDetails, string performingIp, DateTime eventDateUtc, int affectedUserId, string? affectedDetails, string eventType, string eventDetails) | |||
[Obsolete("Use AuditEntryService.WriteAsync() instead. Scheduled for removal in Umbraco 18.")] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why can we remove the method in 18, but the constructor in 19 🤔
I think everything we obsolete in this PR, should be scheduled for the same major (19) 😁
Description
IAuditEntryRepository
(umbracoAudit
) from theAuditService
to the new serviceperformingUserKey
andaffectedUserKey
and convert existing user idsAuditEntryServiceTests
AuditEntryServiceTests
and added a few unit testsTask<Attempt<Guid>> TryGetAsync(int id)
andTask<Attempt<int>> TryGetAsync(Guid key)
methods toIUserIdKeyResolver
Affected notification handlers that trigger audit logs
UserLoginSuccessNotification
UserLogoutSuccessNotification
UserLoginFailedNotification
UserForgotPasswordRequestedNotification
UserForgotPasswordChangedNotification
UserPasswordChangedNotification
UserPasswordResetNotification
MemberSavedNotification
MemberDeletedNotification
AssignedMemberRolesNotification
RemovedMemberRolesNotification
ExportedMemberNotification
UserSavedNotification
UserDeletedNotification
UserGroupWithUsersSavedNotification
AssignedUserGroupPermissionsNotification
Testing
Run the project in the main branch and do a few user operations. Then, switch to this branch, perform the update and do additional user operations.
Check the database to ensure that:
❓ Pending questions ❓
SYSTEM
,Unknown
)?00000000-0000-0000-0000-000000000000
or do we want it to be null? (both in the database and code)null
is preferred, some changes might be needed in theAddAsync
methods (at the moment if the id is 0 or the overload accepting id is used and it cannot convert/find that id, it is saving as empty guid)