diff --git a/src/main/java/com/salesforce/dataloader/client/LoginClient.java b/src/main/java/com/salesforce/dataloader/client/LoginClient.java index f85a5c6c0..cff19cf21 100644 --- a/src/main/java/com/salesforce/dataloader/client/LoginClient.java +++ b/src/main/java/com/salesforce/dataloader/client/LoginClient.java @@ -37,6 +37,7 @@ import com.salesforce.dataloader.config.Messages; import com.salesforce.dataloader.controller.Controller; import com.salesforce.dataloader.exception.PasswordExpiredException; +import com.salesforce.dataloader.util.OAuthServerFlow; import com.sforce.soap.partner.Connector; import com.sforce.soap.partner.GetUserInfoResult; import com.sforce.soap.partner.LimitInfo; @@ -180,6 +181,15 @@ && getAPIVersionForTheSession() != null private boolean dologin() throws ConnectionException, ApiFault { // Attempt the login giving the user feedback logger.info(Messages.getString("Client.sforceLogin")); //$NON-NLS-1$ + String refreshToken = + appConfig.getString(AppConfig.PROP_OAUTH_REFRESHTOKEN); + + String secret = + appConfig.getEffectiveClientSecretForCurrentEnv(); + + if (refreshToken != null) { + logger.info("Refresh token length=" + refreshToken.length()); + } String oauthAccessToken = appConfig.getString(AppConfig.PROP_OAUTH_ACCESSTOKEN); String serverUrl = appConfig.getAuthEndpointForCurrentEnv(); if (oauthAccessToken != null && oauthAccessToken.trim().length() > 0) { @@ -194,19 +204,70 @@ private boolean dologin() throws ConnectionException, ApiFault { final PartnerConnection conn = Connector.newConnection(cc); // identify the client as dataloader conn.setCallOptions(ClientBase.getClientName(this.appConfig), null); + + + try { - try { - if (oauthAccessToken != null && oauthAccessToken.trim().length() > 0) { - setConfiguredSessionId(conn, oauthAccessToken, null); - } else if (appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL) && appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { - setConfiguredSessionId(conn, appConfig.getString(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID), null); - } else { - setSessionRenewer(conn); - loginInternal(conn); - } - } catch (Exception ex) { - throw ex; - } finally { + if (refreshToken != null + && refreshToken.trim().length() > 0) { + + try { + + OAuthServerFlow flow = + new OAuthServerFlow(appConfig); + + String newAccessToken = + flow.refreshAccessToken( + refreshToken); + + logger.info("Refreshed token present=" + + (newAccessToken != null)); + + if (newAccessToken != null) { + + setConfiguredSessionId( + conn, + newAccessToken, + null); + + return true; + } + + } catch (Exception ex) { + + logger.error( + "Refresh token flow failed", + ex); + } + + setSessionRenewer(conn); + loginInternal(conn); + + } else if (oauthAccessToken != null + && oauthAccessToken.trim().length() > 0) { + + setConfiguredSessionId( + conn, + oauthAccessToken, + null); + + } else if (appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL) + && appConfig.getBoolean(AppConfig.PROP_SFDC_INTERNAL_IS_SESSION_ID_LOGIN)) { + + setConfiguredSessionId( + conn, + appConfig.getString(AppConfig.PROP_SFDC_INTERNAL_SESSION_ID), + null); + + } else { + + setSessionRenewer(conn); + loginInternal(conn); + } + + } catch (Exception ex) { + throw ex; + } finally { cc.setTraceMessage(savedIsTraceMessage); } return true; diff --git a/src/main/java/com/salesforce/dataloader/config/AppConfig.java b/src/main/java/com/salesforce/dataloader/config/AppConfig.java index 369e057fa..8a256599b 100644 --- a/src/main/java/com/salesforce/dataloader/config/AppConfig.java +++ b/src/main/java/com/salesforce/dataloader/config/AppConfig.java @@ -538,7 +538,6 @@ public class AppConfig { */ PROP_DELETE_WITH_EXTERNALID, PROP_OAUTH_ACCESSTOKEN, - PROP_OAUTH_REFRESHTOKEN, PROP_OAUTH_INSTANCE_URL, PROP_OAUTH_REDIRECT_URI_PROD, PROP_OAUTH_REDIRECT_URI_SB, @@ -1088,6 +1087,7 @@ public void load() throws IOException, ConfigInitializationException { } FileInputStream in = new FileInputStream(filename); try { + logger.info("Config file=" + filename); load(in); } finally { in.close(); @@ -1192,7 +1192,9 @@ private void postLoad(Map propMap, boolean isConfigFilePropsMap) throws Co removeUnsupportedProperties(); } } - + public void markDirty() { + this.inMemoryPropValuesHaveChanged = true; + } private void decryptAndCacheProperty(Map values, String propertyName) throws ConfigInitializationException { @SuppressWarnings("unchecked") Map propMap = (Map)values; @@ -1207,12 +1209,23 @@ private void decryptAndCacheProperty(Map values, String propertyName) thro return; // do not decrypt an already decrypted value } } - String propValue = decryptProperty(encrypter, propMap, propertyName, isBatchMode()); - if (propValue == null) propValue = ""; - propMap.put(propertyName, propValue); - - // cache decrypted value - propMap.put(propertyName + DECRYPTED_SUFFIX, propValue); + String propValue = + decryptProperty( + encrypter, + propMap, + propertyName, + isBatchMode()); + + if (propValue == null) { + propValue = ""; + } + + logger.info( + "Decrypted property: " + propertyName + + " length=" + propValue.length()); + + propMap.put(propertyName, propValue); + propMap.put(propertyName + DECRYPTED_SUFFIX, propValue); } } diff --git a/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java index a3933c167..a198a9d53 100644 --- a/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java +++ b/src/main/java/com/salesforce/dataloader/oauth/OAuthFlowHandler.java @@ -63,16 +63,55 @@ private boolean shouldRunLoginButtonEnabler() { * * @return true if login was successful, false otherwise */ - public boolean handleOAuthLogin() { - logger.info("Starting OAuth flow"); - appConfig.setLastOAuthFlow("PKCE"); + public boolean handleOAuthLogin() { + logger.info("Starting OAuth flow"); + appConfig.setLastOAuthFlow("PKCE"); - logger.info("Attempting OAuth WebServer with PKCE flow, launching browser for login"); + String refreshToken = + appConfig.getString( + AppConfig.PROP_OAUTH_REFRESHTOKEN); + + if (refreshToken != null + && !refreshToken.trim().isEmpty()) { + logger.info("USING REFRESH TOKEN PATH"); + logger.info( + "Refresh token found, skipping browser login"); + + try { + + if (controller != null) { + + if (controller.login()) { + + controller.saveConfig(); + + if (!AppUtil.isRunningInBatchMode()) { + + Display.getDefault().asyncExec( + () -> controller.updateLoaderWindowTitleAndCacheUserInfoForTheSession()); + + } + + return true; + } + } + + } catch (Exception ex) { + + logger.error( + "Refresh token login failed", + ex); + } + } + + logger.info( + "Attempting OAuth WebServer with PKCE flow, launching browser for login"); if (statusConsumer != null) { statusConsumer.accept(Labels.getString("OAuthLoginControl.statusAttemptingPKCE")); } try { OAuthServerFlow pkceFlow = new OAuthServerFlow(appConfig, true, statusConsumer); + logger.info("USING BROWSER PATH"); if (pkceFlow.performOAuthFlow()) { logger.info("WebServer with PKCE flow completed successfully"); if (statusConsumer != null) { diff --git a/src/main/java/com/salesforce/dataloader/util/OAuthServerFlow.java b/src/main/java/com/salesforce/dataloader/util/OAuthServerFlow.java index 15fc64ee3..0dc0e19c5 100644 --- a/src/main/java/com/salesforce/dataloader/util/OAuthServerFlow.java +++ b/src/main/java/com/salesforce/dataloader/util/OAuthServerFlow.java @@ -269,7 +269,7 @@ private String buildAuthorizationUrl() throws Exception { authUrl.append("?response_type=code"); authUrl.append("&client_id=").append(URLEncoder.encode(clientId, StandardCharsets.UTF_8.name())); authUrl.append("&redirect_uri=").append(URLEncoder.encode(redirectUri, StandardCharsets.UTF_8.name())); - authUrl.append("&scope=").append(URLEncoder.encode("api", StandardCharsets.UTF_8.name())); + authUrl.append("&scope=").append(URLEncoder.encode("api refresh_token", StandardCharsets.UTF_8.name())); authUrl.append("&state=").append(URLEncoder.encode(state, StandardCharsets.UTF_8.name())); if (usePkce && codeChallenge != null) { authUrl.append("&code_challenge=").append(URLEncoder.encode(codeChallenge, StandardCharsets.UTF_8.name())); @@ -355,11 +355,28 @@ private boolean exchangeCodeForTokens() throws com.salesforce.dataloader.excepti String refreshToken = (String) response.get("refresh_token"); String instanceUrl = (String) response.get("instance_url"); + logger.info("### ACCESS TOKEN PRESENT: " + + (accessToken != null)); + + logger.info("### TOKEN RESPONSE: " + response); + + logger.info("### REFRESH TOKEN PRESENT: " + + (refreshToken != null)); + + if (refreshToken != null) { + logger.info("### REFRESH TOKEN LENGTH: " + + refreshToken.length()); + } + // Configure AppConfig with OAuth tokens appConfig.setValue(AppConfig.PROP_OAUTH_ACCESSTOKEN, accessToken); if (refreshToken != null) { - appConfig.setValue(AppConfig.PROP_OAUTH_REFRESHTOKEN, refreshToken); - } + logger.info("OAuth refresh token received. Length=" + + refreshToken.length()); + + appConfig.setValue(AppConfig.PROP_OAUTH_REFRESHTOKEN, refreshToken); + appConfig.save(); + } if (instanceUrl != null) { appConfig.setAuthEndpointForCurrentEnv(instanceUrl); appConfig.setValue(AppConfig.PROP_OAUTH_INSTANCE_URL, instanceUrl); @@ -382,6 +399,92 @@ private boolean exchangeCodeForTokens() throws com.salesforce.dataloader.excepti } } + public String refreshAccessToken(String refreshToken) + throws Exception { + + logger.info("Refreshing OAuth access token"); + + String tokenUrl = + appConfig.getAuthEndpointForCurrentEnv() + + "/services/oauth2/token"; + + String clientId = + appConfig.getEffectiveClientIdForCurrentEnv(); + + String clientSecret = + appConfig.getEffectiveClientSecretForCurrentEnv(); + + List params = + new ArrayList<>(); + + params.add(new BasicNameValuePair( + "grant_type", + "refresh_token")); + + params.add(new BasicNameValuePair( + "client_id", + clientId)); + + params.add(new BasicNameValuePair( + "refresh_token", + refreshToken)); + + if (appConfig.isExternalClientAppConfigured() + && clientSecret != null + && !clientSecret.trim().isEmpty()) { + + params.add(new BasicNameValuePair( + "client_secret", + clientSecret)); + } + + SimplePostInterface client = + SimplePostFactory.getInstance( + appConfig, + tokenUrl, + params.toArray(new BasicNameValuePair[0])); + + client.post(); + + if (!client.isSuccessful()) { + logger.error("Refresh token request failed with status: "+ client.getStatusCode()); + logErrorResponse(client.getInput()); + return null; + } + + Map response = + parseJsonResponse(client.getInput()); + + String accessToken = (String) response.get("access_token"); + + String newRefreshToken = + (String) response.get("refresh_token"); + + if (accessToken != null) { + appConfig.setValue( + AppConfig.PROP_OAUTH_ACCESSTOKEN, + accessToken); + } + + if (newRefreshToken != null) { + + logger.info( + "Updated refresh token received. Length=" + + newRefreshToken.length()); + + appConfig.setValue( + AppConfig.PROP_OAUTH_REFRESHTOKEN, + newRefreshToken); + appConfig.markDirty(); + appConfig.save(); + + logger.info( + "Updated refresh token saved to config"); + + } + + return accessToken; + } /** * Handles the OAuth callback from Salesforce. */