diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java index c4a2b676491ae..4f360b5cd41dc 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java @@ -676,7 +676,9 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio String authEndpoint = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT); String username = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_USER_NAME); String password = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_USER_PASSWORD); - tokenProvider = new UserPasswordTokenProvider(authEndpoint, username, password); + String clientId = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ID); + String clientSecret = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_SECRET); + tokenProvider = new UserPasswordTokenProvider(authEndpoint, username, password, clientId, clientSecret); LOG.trace("UserPasswordTokenProvider initialized"); } else if (tokenProviderClass == MsiTokenProvider.class) { String authEndpoint = getTrimmedPasswordString( diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java index 49f90feb22e95..1b9f732dca9f1 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/AzureADAuthenticator.java @@ -154,6 +154,53 @@ public static AzureADToken getTokenFromMsi(final String authEndpoint, return getTokenCall(authEndpoint, qp.serialize(), headers, "GET", true); } + + /** + * gets Azure Active Directory token using the user's username and password. This only + * works if the identity can be authenticated directly by microsoftonline.com. It will likely + * not work if the domain is federated and/or multi-factor authentication or other form of + * strong authentication is configured for the user. + * + * @param authEndpoint the OAuth 2.0 token endpoint associated + * with the user's directory (obtain from + * Active Directory configuration) + * @param username the user name of the user + * @param password the password of the user + * @param clientId the client ID (GUID) of the client web app + * obtained from Azure Active Directory configuration + * @param clientSecret (optional) the secret key of the client web app + * If the app is a confidential client, then it must be included + * @return {@link AzureADToken} obtained using the creds + * @throws IOException throws IOException if there is a failure in connecting to Azure AD + */ + public static AzureADToken getTokenUsingUserCreds(String authEndpoint, + String username, String password, String clientId, + String clientSecret) throws IOException { + Preconditions.checkNotNull(authEndpoint, "authEndpoint"); + Preconditions.checkNotNull(username, "username"); + Preconditions.checkNotNull(password, "password"); + Preconditions.checkNotNull(clientId, "clientId"); + boolean isVersion2AuthenticationEndpoint = authEndpoint.contains("/oauth2/v2.0/"); + + QueryParams qp = new QueryParams(); + if (isVersion2AuthenticationEndpoint) { + qp.add("scope", SCOPE); + } else { + qp.add("resource", RESOURCE_NAME); + } + qp.add("grant_type", "password"); + qp.add("client_id", clientId); + qp.add("username",username); + qp.add("password",password); + + if (clientSecret != null && clientSecret.length() > 0) { + qp.add("client_secret", clientSecret); + } + + LOG.debug("AADToken: starting to fetch token using username and password"); + return getTokenCall(authEndpoint, qp.serialize(), null, null); + } + /** * Gets Azure Active Directory token using refresh token. * diff --git a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java index 3d9d8b865a059..fd5fa20f62732 100644 --- a/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java +++ b/hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/oauth2/UserPasswordTokenProvider.java @@ -35,22 +35,30 @@ public class UserPasswordTokenProvider extends AccessTokenProvider { private final String password; + private final String clientId; + + private final String clientSecret; + private static final Logger LOG = LoggerFactory.getLogger(AccessTokenProvider.class); public UserPasswordTokenProvider(final String authEndpoint, - final String username, final String password) { + final String username, final String password, + final String clientId, final String clientSecret) { Preconditions.checkNotNull(authEndpoint, "authEndpoint"); Preconditions.checkNotNull(username, "username"); Preconditions.checkNotNull(password, "password"); + Preconditions.checkNotNull(clientId, "clientId"); this.authEndpoint = authEndpoint; this.username = username; this.password = password; + this.clientId = clientId; + this.clientSecret = clientSecret; } @Override protected AzureADToken refreshToken() throws IOException { LOG.debug("AADToken: refreshing user-password based token"); - return AzureADAuthenticator.getTokenUsingClientCreds(authEndpoint, username, password); + return AzureADAuthenticator.getTokenUsingUserCreds(authEndpoint, username, password, clientId, clientSecret); } } diff --git a/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md b/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md index 79b897b6bd25c..2c44e2adcbb12 100644 --- a/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md +++ b/hadoop-tools/hadoop-azure/src/site/markdown/abfs.md @@ -467,6 +467,20 @@ An OAuth 2.0 endpoint, username and password are provided in the configuration/J password for account + + fs.azure.account.oauth2.client.id + + + Client ID + + + + fs.azure.account.oauth2.client.secret + + + Optional Secret + + ``` ### OAuth 2.0: Refresh Token