Skip to content
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

Bugfix: Add safe navigation operator on LoginHistory from authSession records #831

Merged
merged 6 commits into from
Feb 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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