diff --git a/pom.xml b/pom.xml index 30c93637d..af3aa4e09 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.force dataloader - 65.0.0 + 66.0.0 jar Salesforce Data Loader https://github.com/forcedotcom/dataloader @@ -51,7 +51,7 @@ com.force.api force-partner-api - 64.0.3 + 66.0.0 commons-logging diff --git a/src/main/java/com/salesforce/dataloader/client/ClientBase.java b/src/main/java/com/salesforce/dataloader/client/ClientBase.java index acb15d62a..887a787a2 100644 --- a/src/main/java/com/salesforce/dataloader/client/ClientBase.java +++ b/src/main/java/com/salesforce/dataloader/client/ClientBase.java @@ -136,17 +136,27 @@ public final boolean connect(SessionInfo sess) { public static final String SFORCE_CALL_OPTIONS_HEADER = "Sforce-Call-Options"; public static String getClientName(AppConfig cfg) { + return getClientName(cfg.isBulkAPIEnabled(), cfg.isBulkV2APIEnabled(), + cfg.isBatchMode(), cfg.isExternalClientAppConfigured(), Controller.APP_VERSION); + } + + static String getClientName(boolean bulkAPI, boolean bulkV2API, + boolean batchMode, boolean externalClientApp, String appVersion) { String apiType = PARTNER_API_CLIENT_TYPE; - final String interfaceType = cfg.isBatchMode() ? BATCH_CLIENT_STRING : UI_CLIENT_STRING; - if (cfg.isBulkAPIEnabled()) { + if (bulkAPI) { apiType = BULK_API_CLIENT_TYPE; - }else if (cfg.isBulkV2APIEnabled()) { + } else if (bulkV2API) { apiType = BULK_V2_API_CLIENT_TYPE; } + + String interfaceType = externalClientApp + ? "" + : (batchMode ? BATCH_CLIENT_STRING : UI_CLIENT_STRING); + return new StringBuilder(32).append(BASE_CLIENT_NAME).append(apiType).append(interfaceType) .append("/") - .append(Controller.APP_VERSION) - .toString(); //$NON-NLS-1$ + .append(appVersion) + .toString(); } public static synchronized String getAPIVersionForTheSession() { diff --git a/src/main/java/com/salesforce/dataloader/client/LoginClient.java b/src/main/java/com/salesforce/dataloader/client/LoginClient.java index 44a2b1cb3..f85a5c6c0 100644 --- a/src/main/java/com/salesforce/dataloader/client/LoginClient.java +++ b/src/main/java/com/salesforce/dataloader/client/LoginClient.java @@ -245,7 +245,7 @@ private void setConfiguredSessionId(PartnerConnection conn, String sessionId, Ge private void loginInternal(final PartnerConnection conn) throws ConnectionException, PasswordExpiredException { final ConnectorConfig cc = conn.getConfig(); - cc.setRequestHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()); + cc.setRequestHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getEffectiveClientIdForCurrentEnv()); try { logger.info(Messages.getMessage(getClass(), "sforceLoginDetail", cc.getAuthEndpoint(), cc.getUsername())); LoginResult loginResult = runLoginOperation(conn); diff --git a/src/main/java/com/salesforce/dataloader/client/transport/HttpTransportImpl.java b/src/main/java/com/salesforce/dataloader/client/transport/HttpTransportImpl.java index f5afc3f51..c15542b44 100644 --- a/src/main/java/com/salesforce/dataloader/client/transport/HttpTransportImpl.java +++ b/src/main/java/com/salesforce/dataloader/client/transport/HttpTransportImpl.java @@ -444,7 +444,7 @@ private void setAuthAndClientHeadersForHttpMethod() { Header clientIdHeaderVal = this.httpMethod.getFirstHeader(AppConfig.CLIENT_ID_HEADER_NAME); if (clientIdHeaderVal == null) { AppConfig appConfig = AppConfig.getCurrentConfig(); - this.httpMethod.addHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getClientIDForCurrentEnv()); + this.httpMethod.addHeader(AppConfig.CLIENT_ID_HEADER_NAME, appConfig.getEffectiveClientIdForCurrentEnv()); } Header clientNameHeaderVal = this.httpMethod.getFirstHeader(ClientBase.SFORCE_CALL_OPTIONS_HEADER); if (clientNameHeaderVal == null) { diff --git a/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java index d27b92a5e..a3933c167 100644 --- a/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java +++ b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java @@ -29,6 +29,7 @@ import com.salesforce.dataloader.config.AppConfig; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.ui.Labels; +import com.salesforce.dataloader.util.AppUtil; import com.salesforce.dataloader.util.DLLogManager; import com.salesforce.dataloader.util.OAuthServerFlow; import org.apache.logging.log4j.Logger; @@ -53,6 +54,10 @@ public OAuthFlowHandler(AppConfig appConfig, Consumer statusConsumer, Co this.loginButtonEnabler = loginButtonEnabler; } + private boolean shouldRunLoginButtonEnabler() { + return loginButtonEnabler != null && !AppUtil.isRunningInBatchMode(); + } + /** * Handles Web Server OAuth flow leveraging Proof Key for Code Exchange(PKCE). * @@ -77,8 +82,10 @@ public boolean handleOAuthLogin() { try { if (controller.login()) { controller.saveConfig(); - Display.getDefault().asyncExec(() -> controller.updateLoaderWindowTitleAndCacheUserInfoForTheSession()); - if (loginButtonEnabler != null) { + if (!AppUtil.isRunningInBatchMode()) { + Display.getDefault().asyncExec(() -> controller.updateLoaderWindowTitleAndCacheUserInfoForTheSession()); + } + if (shouldRunLoginButtonEnabler()) { Display.getDefault().asyncExec(loginButtonEnabler); } return true; @@ -88,13 +95,13 @@ public boolean handleOAuthLogin() { if (statusConsumer != null) { statusConsumer.accept(Labels.getString("OAuthLoginControl.statusControllerUpdateError")); } - if (loginButtonEnabler != null) { + if (shouldRunLoginButtonEnabler()) { Display.getDefault().asyncExec(loginButtonEnabler); } return false; } } - if (loginButtonEnabler != null) { + if (shouldRunLoginButtonEnabler()) { Display.getDefault().asyncExec(loginButtonEnabler); } return true; @@ -111,7 +118,7 @@ public boolean handleOAuthLogin() { if (statusConsumer != null) { statusConsumer.accept(Labels.getString("OAuthLoginControl.statusPKCEFailedFallbackBrowser")); } - if (loginButtonEnabler != null) { + if (shouldRunLoginButtonEnabler()) { Display.getDefault().asyncExec(loginButtonEnabler); } return false; diff --git a/src/main/java/com/salesforce/dataloader/util/AppUtil.java b/src/main/java/com/salesforce/dataloader/util/AppUtil.java index 2ea5799d8..5c21acb45 100644 --- a/src/main/java/com/salesforce/dataloader/util/AppUtil.java +++ b/src/main/java/com/salesforce/dataloader/util/AppUtil.java @@ -238,6 +238,10 @@ public static void extractDirFromJar(String extractionPrefix, String destDirName public static APP_RUN_MODE getAppRunMode() { return appRunMode; } + + public static boolean isRunningInBatchMode() { + return appRunMode == APP_RUN_MODE.BATCH; + } public static boolean isContentSObject(String sObjectName) { return CONTENT_SOBJECT_LIST.contains(sObjectName.toLowerCase()); diff --git a/src/test/java/com/salesforce/dataloader/client/ClientBaseGetClientNameTest.java b/src/test/java/com/salesforce/dataloader/client/ClientBaseGetClientNameTest.java new file mode 100644 index 000000000..5128dc265 --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/client/ClientBaseGetClientNameTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.client; + +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +/** + * Unit tests for {@link ClientBase#getClientName}. + * Verifies that External Client App configurations produce neutral client names + * (without UI/Batch suffix) while legacy configurations retain the original format. + */ +public class ClientBaseGetClientNameTest { + + private static final String VERSION = "65.0.0"; + + // --- Legacy (no ECA) --- + + @Test + public void legacy_partnerUI() { + assertEquals("DataLoaderPartnerUI/65.0.0", + ClientBase.getClientName(false, false, false, false, VERSION)); + } + + @Test + public void legacy_partnerBatch() { + assertEquals("DataLoaderPartnerBatch/65.0.0", + ClientBase.getClientName(false, false, true, false, VERSION)); + } + + @Test + public void legacy_bulkUI() { + assertEquals("DataLoaderBulkUI/65.0.0", + ClientBase.getClientName(true, false, false, false, VERSION)); + } + + @Test + public void legacy_bulkv2Batch() { + assertEquals("DataLoaderBulkv2Batch/65.0.0", + ClientBase.getClientName(false, true, true, false, VERSION)); + } + + // --- ECA configured --- + + @Test + public void eca_partner() { + String name = ClientBase.getClientName(false, false, false, true, VERSION); + assertEquals("DataLoaderPartner/65.0.0", name); + assertFalse(name.contains("UI")); + assertFalse(name.contains("Batch")); + } + + @Test + public void eca_bulk() { + String name = ClientBase.getClientName(true, false, false, true, VERSION); + assertEquals("DataLoaderBulk/65.0.0", name); + assertFalse(name.contains("UI")); + assertFalse(name.contains("Batch")); + } + + @Test + public void eca_bulkv2() { + String name = ClientBase.getClientName(false, true, false, true, VERSION); + assertEquals("DataLoaderBulkv2/65.0.0", name); + assertFalse(name.contains("UI")); + assertFalse(name.contains("Batch")); + } + + @Test + public void eca_ignoresBatchMode() { + String name = ClientBase.getClientName(false, false, true, true, VERSION); + assertEquals("DataLoaderPartner/65.0.0", name); + assertFalse("ECA path should not include Batch suffix", name.contains("Batch")); + } +} diff --git a/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowHandlerTest.java b/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowHandlerTest.java new file mode 100644 index 000000000..bef9db79c --- /dev/null +++ b/src/test/java/com/salesforce/dataloader/oauth/OAuthFlowHandlerTest.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2015, salesforce.com, inc. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without modification, are permitted provided + * that the following conditions are met: + * + * Redistributions of source code must retain the above copyright notice, this list of conditions and the + * following disclaimer. + * + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or other materials provided with the distribution. + * + * Neither the name of salesforce.com, inc. nor the names of its contributors may be used to endorse or + * promote products derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.salesforce.dataloader.oauth; + +import com.salesforce.dataloader.config.AppConfig; +import com.salesforce.dataloader.controller.Controller; +import com.salesforce.dataloader.util.AppUtil; +import com.salesforce.dataloader.util.OAuthServerFlow; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.mockito.MockedConstruction; + +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.*; + +/** + * Verifies that OAuthFlowHandler works correctly in batch mode + * by skipping Display.getDefault() calls that would crash without SWT. + */ +public class OAuthFlowHandlerTest { + + private AppUtil.APP_RUN_MODE originalRunMode; + + @Before + public void setUp() { + originalRunMode = AppUtil.getAppRunMode(); + } + + @After + public void tearDown() { + AppUtil.setAppRunMode(originalRunMode.name().toLowerCase()); + } + + @Test + public void testHandleOAuthLoginSucceedsInBatchMode() throws Exception { + AppUtil.setAppRunMode("batch"); + + Controller mockController = mock(Controller.class); + when(mockController.login()).thenReturn(true); + AppConfig mockAppConfig = mock(AppConfig.class); + + try (MockedConstruction ignored = mockConstruction( + OAuthServerFlow.class, + (mock, context) -> when(mock.performOAuthFlow()).thenReturn(true))) { + + assertTrue("Run mode should be BATCH", AppUtil.isRunningInBatchMode()); + + OAuthFlowHandler handler = new OAuthFlowHandler( + mockAppConfig, status -> {}, mockController, () -> {}); + + boolean result = handler.handleOAuthLogin(); + assertTrue("OAuth login should succeed in batch mode with fix applied", result); + } + } +}