Skip to content

Commit

Permalink
Bugfix: Add safe navigation operator on LoginHistory from authSession…
Browse files Browse the repository at this point in the history
… records (#831)

* Added safe navigation operator on LoginHistory from AuthSession records

* Removed a unuseful safe navigation operator

* Updated LogEntryEventHandler to set Log__c.ImpersonatedBy__c when AuthSession data is queried async
  • Loading branch information
camillerev authored Feb 5, 2025
1 parent d897e3f commit c8e2502
Show file tree
Hide file tree
Showing 9 changed files with 118 additions and 18 deletions.
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@

The most robust observability solution for Salesforce experts. Built 100% natively on the platform, and designed to work seamlessly with Apex, Lightning Components, Flow, OmniStudio, and integrations.

## Unlocked Package - v4.15.4
## Unlocked Package - v4.15.5

[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oklQAA)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015oklQAA)
[![Install Unlocked Package in a Sandbox](./images/btn-install-unlocked-package-sandbox.png)](https://test.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015p5jQAA)
[![Install Unlocked Package in Production](./images/btn-install-unlocked-package-production.png)](https://login.salesforce.com/packaging/installPackage.apexp?p0=04t5Y0000015p5jQAA)
[![View Documentation](./images/btn-view-documentation.png)](https://github.com/jongpie/NebulaLogger/wiki)

`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015oklQAA`
`sf package install --wait 20 --security-type AdminsOnly --package 04t5Y0000015p5jQAA`

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -617,17 +617,21 @@ public without sharing class LogEntryEventHandler extends LoggerSObjectHandler {
continue;
}

log.LoginApplication__c = matchingAuthSessionProxy.LoginHistory.Application;
log.LoginBrowser__c = matchingAuthSessionProxy.LoginHistory.Browser;
log.LoginApplication__c = matchingAuthSessionProxy.LoginHistory?.Application;
log.LoginBrowser__c = matchingAuthSessionProxy.LoginHistory?.Browser;
log.LoginHistoryId__c = matchingAuthSessionProxy.LoginHistoryId;
log.LoginPlatform__c = matchingAuthSessionProxy.LoginHistory.Platform;
log.LoginPlatform__c = matchingAuthSessionProxy.LoginHistory?.Platform;
log.LoginType__c = matchingAuthSessionProxy.LoginType;
log.LogoutUrl__c = matchingAuthSessionProxy.LogoutUrl;
log.ParentSessionId__c = matchingAuthSessionProxy.ParentId;
log.SessionId__c = matchingAuthSessionProxy.Id;
log.SessionSecurityLevel__c = matchingAuthSessionProxy.SessionSecurityLevel;
log.SessionType__c = matchingAuthSessionProxy.SessionType;
log.SourceIp__c = matchingAuthSessionProxy.SourceIp;

if (matchingAuthSessionProxy.LoginHistory?.UserId != log.LoggedBy__c) {
log.ImpersonatedBy__c = matchingAuthSessionProxy.LoginHistory?.UserId;
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -980,10 +980,10 @@ global with sharing class LogEntryEventBuilder {
return;
}

logEntryEvent.LoginApplication__c = CACHED_AUTH_SESSION_PROXY.LoginHistory.Application;
logEntryEvent.LoginBrowser__c = CACHED_AUTH_SESSION_PROXY.LoginHistory.Browser;
logEntryEvent.LoginApplication__c = CACHED_AUTH_SESSION_PROXY.LoginHistory?.Application;
logEntryEvent.LoginBrowser__c = CACHED_AUTH_SESSION_PROXY.LoginHistory?.Browser;
logEntryEvent.LoginHistoryId__c = CACHED_AUTH_SESSION_PROXY.LoginHistoryId;
logEntryEvent.LoginPlatform__c = CACHED_AUTH_SESSION_PROXY.LoginHistory.Platform;
logEntryEvent.LoginPlatform__c = CACHED_AUTH_SESSION_PROXY.LoginHistory?.Platform;
logEntryEvent.LoginType__c = CACHED_AUTH_SESSION_PROXY.LoginType;
logEntryEvent.LogoutUrl__c = CACHED_AUTH_SESSION_PROXY.LogoutUrl;
logEntryEvent.ParentSessionId__c = CACHED_AUTH_SESSION_PROXY.ParentId;
Expand All @@ -992,8 +992,8 @@ global with sharing class LogEntryEventBuilder {
logEntryEvent.SessionType__c = CACHED_AUTH_SESSION_PROXY.SessionType;
logEntryEvent.SourceIp__c = CACHED_AUTH_SESSION_PROXY.SourceIp;

if (CACHED_AUTH_SESSION_PROXY.LoginHistory.UserId != CURRENT_USER.Id) {
logEntryEvent.ImpersonatedById__c = CACHED_AUTH_SESSION_PROXY.LoginHistory.UserId;
if (CACHED_AUTH_SESSION_PROXY.LoginHistory?.UserId != CURRENT_USER.Id) {
logEntryEvent.ImpersonatedById__c = CACHED_AUTH_SESSION_PROXY.LoginHistory?.UserId;
}
}

Expand Down
2 changes: 1 addition & 1 deletion nebula-logger/core/main/logger-engine/classes/Logger.cls
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
global with sharing class Logger {
// There's no reliable way to get the version number dynamically in Apex
@TestVisible
private static final String CURRENT_VERSION_NUMBER = 'v4.15.4';
private static final String CURRENT_VERSION_NUMBER = 'v4.15.5';
private static final System.LoggingLevel FALLBACK_LOGGING_LEVEL = System.LoggingLevel.DEBUG;
private static final List<LogEntryEventBuilder> LOG_ENTRIES_BUFFER = new List<LogEntryEventBuilder>();
private static final String MISSING_SCENARIO_ERROR_MESSAGE = 'No logger scenario specified. A scenario is required for logging in this org.';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import LoggerServiceTaskQueue from './loggerServiceTaskQueue';
import getSettings from '@salesforce/apex/ComponentLogger.getSettings';
import saveComponentLogEntries from '@salesforce/apex/ComponentLogger.saveComponentLogEntries';

const CURRENT_VERSION_NUMBER = 'v4.15.4';
const CURRENT_VERSION_NUMBER = 'v4.15.5';

const CONSOLE_OUTPUT_CONFIG = {
messagePrefix: `%c Nebula Logger ${CURRENT_VERSION_NUMBER} `,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,9 @@ private class LogEntryEventHandler_Tests {
mockLoginHistoryProxy.Application = 'Application';
mockLoginHistoryProxy.Browser = 'Browser';
mockLoginHistoryProxy.Platform = 'Platform';
// TODO also test for when this UserId is a different user when an admin is impersonating another user
// This could be a bit tricky because creating a User in tests could fail in some orgs, and querying existing
// users in tests could fail in other orgs 🙃
mockLoginHistoryProxy.UserId = System.UserInfo.getUserId();
LoggerSObjectProxy.AuthSession mockAuthSessionProxy = new LoggerSObjectProxy.AuthSession();
mockAuthSessionProxy.Id = LoggerMockDataCreator.createId(Schema.AuthSession.SObjectType);
Expand Down Expand Up @@ -680,6 +683,62 @@ private class LogEntryEventHandler_Tests {
System.Assert.areEqual(mockAuthSessionProxy.SourceIp, log.SourceIp__c);
}

@IsTest
static void it_should_query_and_handle_authSession_when_logingHistory_is_null() {
LoggerDataStore.setMock(LoggerMockDataStore.getEventBus());
LoggerTestConfigurator.setupMockSObjectHandlerConfigurations();
LoggerTestConfigurator.getSObjectHandlerConfiguration(Schema.LoggerScenario__c.SObjectType).IsEnabled__c = false;
LoggerTestConfigurator.getSObjectHandlerConfiguration(Schema.Log__c.SObjectType).IsEnabled__c = false;
LoggerTestConfigurator.getSObjectHandlerConfiguration(Schema.LogEntry__c.SObjectType).IsEnabled__c = false;
LogEntryEvent__e logEntryEvent = createLogEntryEvent();
// The createLogEntryEvent() method sets all fields on LogEntryEvent__e, so clear the relevant fields to ensure
// that the values are only set based on the queried Schema.AuthSession record
logEntryEvent.LoginApplication__c = null;
logEntryEvent.LoginBrowser__c = null;
logEntryEvent.LoginHistoryId__c = null;
logEntryEvent.LoginPlatform__c = null;
logEntryEvent.LoginType__c = null;
logEntryEvent.LogoutUrl__c = null;
logEntryEvent.ParentSessionId__c = null;
logEntryEvent.SessionId__c = null;
logEntryEvent.SessionSecurityLevel__c = null;
logEntryEvent.SessionType__c = null;
logEntryEvent.SourceIp__c = null;
LoggerTestConfigurator.setMock(new LoggerParameter__mdt(DeveloperName = 'QueryAuthSessionDataSynchronously', Value__c = String.valueOf(false)));
System.Assert.isFalse(LoggerParameter.QUERY_AUTH_SESSION_DATA_SYNCHRONOUSLY);
MockLoggerEngineDataSelector mockSelector = new MockLoggerEngineDataSelector();
LoggerEngineDataSelector.setMock(mockSelector);
LoggerSObjectProxy.AuthSession mockAuthSessionProxy = new LoggerSObjectProxy.AuthSession();
mockAuthSessionProxy.Id = LoggerMockDataCreator.createId(Schema.AuthSession.SObjectType);
mockAuthSessionProxy.LoginHistory = null;
mockAuthSessionProxy.LoginHistoryId = null;
mockAuthSessionProxy.LoginType = 'LoginType';
mockAuthSessionProxy.LogoutUrl = 'LogoutUrl';
mockAuthSessionProxy.ParentId = LoggerMockDataCreator.createId(Schema.AuthSession.SObjectType);
mockAuthSessionProxy.SessionSecurityLevel = 'SessionSecurityLevel';
mockAuthSessionProxy.SessionType = 'SessionType';
mockAuthSessionProxy.SourceIp = 'SourceIp';
mockAuthSessionProxy.UsersId = System.UserInfo.getUserId();
mockSelector.setCachedAuthSessionProxy(mockAuthSessionProxy);
System.Assert.areEqual(0, mockSelector.getCachedAuthSessionQueryCount());

LoggerMockDataStore.getEventBus().publishRecord(logEntryEvent);
LoggerMockDataStore.getEventBus().deliver(new LogEntryEventHandler());

Log__c log = getLog();
System.Assert.areEqual(mockAuthSessionProxy.Id, log.SessionId__c);
System.Assert.isNull(log.LoginApplication__c);
System.Assert.isNull(log.LoginBrowser__c);
System.Assert.isNull(log.LoginPlatform__c);
System.Assert.isNull(log.LoginHistoryId__c);
System.Assert.areEqual(mockAuthSessionProxy.LoginType, log.LoginType__c);
System.Assert.areEqual(mockAuthSessionProxy.LogoutUrl, log.LogoutUrl__c);
System.Assert.areEqual(mockAuthSessionProxy.ParentId, log.ParentSessionId__c);
System.Assert.areEqual(mockAuthSessionProxy.SessionSecurityLevel, log.SessionSecurityLevel__c);
System.Assert.areEqual(mockAuthSessionProxy.SessionType, log.SessionType__c);
System.Assert.areEqual(mockAuthSessionProxy.SourceIp, log.SourceIp__c);
}

@IsTest
static void it_should_query_and_set_organization_data_when_synchronous_querying_is_disabled() {
LoggerDataStore.setMock(LoggerMockDataStore.getEventBus());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,42 @@ private class LogEntryEventBuilder_Tests {
System.Assert.areEqual(mockAuthSessionProxy.SourceIp, logEntryEvent.SourceIp__c);
}

@IsTest
static void it_should_query_and_handle_authSession_when_logingHistory_is_null() {
LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'QueryAuthSessionDataSynchronously', Value__c = String.valueOf(true)));
System.Assert.isTrue(LoggerParameter.QUERY_AUTH_SESSION_DATA_SYNCHRONOUSLY);
MockLoggerEngineDataSelector mockSelector = new MockLoggerEngineDataSelector();
LoggerEngineDataSelector.setMock(mockSelector);
LoggerSObjectProxy.AuthSession mockAuthSessionProxy = new LoggerSObjectProxy.AuthSession();
mockAuthSessionProxy.Id = LoggerMockDataCreator.createId(Schema.AuthSession.SObjectType);
mockAuthSessionProxy.LoginHistory = null;
mockAuthSessionProxy.LoginHistoryId = null;
mockAuthSessionProxy.LoginType = 'LoginType';
mockAuthSessionProxy.LogoutUrl = 'LogoutUrl';
mockAuthSessionProxy.ParentId = LoggerMockDataCreator.createId(Schema.AuthSession.SObjectType);
mockAuthSessionProxy.SessionSecurityLevel = 'SessionSecurityLevel';
mockAuthSessionProxy.SessionType = 'SessionType';
mockAuthSessionProxy.SourceIp = 'SourceIp';
mockAuthSessionProxy.UsersId = System.UserInfo.getUserId();
mockSelector.setCachedAuthSessionProxy(mockAuthSessionProxy);
System.Assert.areEqual(0, mockSelector.getCachedAuthSessionQueryCount());

LogEntryEvent__e logEntryEvent = new LogEntryEventBuilder(getUserSettings(), System.LoggingLevel.INFO, true).setMessage('some message').getLogEntryEvent();

System.Assert.areEqual(1, mockSelector.getCachedAuthSessionQueryCount());
System.Assert.isNull(logEntryEvent.LoginApplication__c);
System.Assert.isNull(logEntryEvent.LoginBrowser__c);
System.Assert.isNull(logEntryEvent.LoginPlatform__c);
System.Assert.isNull(logEntryEvent.LoginHistoryId__c);
System.Assert.areEqual(mockAuthSessionProxy.LoginType, logEntryEvent.LoginType__c);
System.Assert.areEqual(mockAuthSessionProxy.LogoutUrl, logEntryEvent.LogoutUrl__c);
System.Assert.areEqual(mockAuthSessionProxy.ParentId, logEntryEvent.ParentSessionId__c);
System.Assert.areEqual(mockAuthSessionProxy.Id, logEntryEvent.SessionId__c);
System.Assert.areEqual(mockAuthSessionProxy.SessionSecurityLevel, logEntryEvent.SessionSecurityLevel__c);
System.Assert.areEqual(mockAuthSessionProxy.SessionType, logEntryEvent.SessionType__c);
System.Assert.areEqual(mockAuthSessionProxy.SourceIp, logEntryEvent.SourceIp__c);
}

@IsTest
static void it_should_not_run_organization_query_when_disabled_via_logger_parameter() {
LoggerParameter.setMock(new LoggerParameter__mdt(DeveloperName = 'QueryOrganizationDataSynchronously', Value__c = String.valueOf(false)));
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "nebula-logger",
"version": "4.15.4",
"version": "4.15.5",
"description": "The most robust logger for Salesforce. Works with Apex, Lightning Components, Flow, Process Builder & Integrations. Designed for Salesforce admins, developers & architects.",
"author": "Jonathan Gillespie",
"license": "MIT",
Expand Down
7 changes: 4 additions & 3 deletions sfdx-project.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
"path": "./nebula-logger/core",
"definitionFile": "./config/scratch-orgs/base-scratch-def.json",
"scopeProfiles": true,
"versionNumber": "4.15.4.NEXT",
"versionName": "FlowLogger exception message handling",
"versionDescription": "FlowLogger now properly truncates the LogEntryEvent__e.ExceptionMessage__c field",
"versionNumber": "4.15.5.NEXT",
"versionName": "Bugfix for AuthSession with null LoginHistory exception message handling",
"versionDescription": "Added safe navigation operator on LoginHistory from authSession records to avoid a NullPointerException",
"postInstallUrl": "https://github.com/jongpie/NebulaLogger/wiki",
"releaseNotesUrl": "https://github.com/jongpie/NebulaLogger/releases",
"unpackagedMetadata": {
Expand Down Expand Up @@ -209,6 +209,7 @@
"Nebula Logger - [email protected]": "04t5Y0000015oifQAA",
"Nebula Logger - [email protected]": "04t5Y0000015ok2QAA",
"Nebula Logger - [email protected]": "04t5Y0000015oklQAA",
"Nebula Logger - Core@4.15.5-bugfix-for-authsession-with-null-loginhistory-exception-message-handling": "04t5Y0000015p5jQAA",
"Nebula Logger - Core Plugin - Async Failure Additions": "0Ho5Y000000blO4SAI",
"Nebula Logger - Core Plugin - Async Failure [email protected]": "04t5Y0000015lhiQAA",
"Nebula Logger - Core Plugin - Async Failure [email protected]": "04t5Y0000015lhsQAA",
Expand Down

0 comments on commit c8e2502

Please sign in to comment.