Skip to content

Commit 4c5cd75

Browse files
authored
HADOOP-17053. ABFS: Fix Account-specific OAuth config setting parsing
Contributed by Sneha Vijayarajan
1 parent 53b993e commit 4c5cd75

File tree

2 files changed

+265
-19
lines changed

2 files changed

+265
-19
lines changed

hadoop-tools/hadoop-azure/src/main/java/org/apache/hadoop/fs/azurebfs/AbfsConfiguration.java

Lines changed: 79 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -325,31 +325,91 @@ public String getPasswordString(String key) throws IOException {
325325
}
326326

327327
/**
328-
* Returns the account-specific Class if it exists, then looks for an
329-
* account-agnostic value, and finally tries the default value.
328+
* Returns account-specific token provider class if it exists, else checks if
329+
* an account-agnostic setting is present for token provider class if AuthType
330+
* matches with authType passed.
331+
* @param authType AuthType effective on the account
330332
* @param name Account-agnostic configuration key
331333
* @param defaultValue Class returned if none is configured
332334
* @param xface Interface shared by all possible values
335+
* @param <U> Interface class type
333336
* @return Highest-precedence Class object that was found
334337
*/
335-
public <U> Class<? extends U> getClass(String name, Class<? extends U> defaultValue, Class<U> xface) {
338+
public <U> Class<? extends U> getTokenProviderClass(AuthType authType,
339+
String name,
340+
Class<? extends U> defaultValue,
341+
Class<U> xface) {
342+
Class<?> tokenProviderClass = getAccountSpecificClass(name, defaultValue,
343+
xface);
344+
345+
// If there is none set specific for account
346+
// fall back to generic setting if Auth Type matches
347+
if ((tokenProviderClass == null)
348+
&& (authType == getAccountAgnosticEnum(
349+
FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME, AuthType.SharedKey))) {
350+
tokenProviderClass = getAccountAgnosticClass(name, defaultValue, xface);
351+
}
352+
353+
return (tokenProviderClass == null)
354+
? null
355+
: tokenProviderClass.asSubclass(xface);
356+
}
357+
358+
/**
359+
* Returns the account-specific class if it exists, else returns default value.
360+
* @param name Account-agnostic configuration key
361+
* @param defaultValue Class returned if none is configured
362+
* @param xface Interface shared by all possible values
363+
* @param <U> Interface class type
364+
* @return Account specific Class object that was found
365+
*/
366+
public <U> Class<? extends U> getAccountSpecificClass(String name,
367+
Class<? extends U> defaultValue,
368+
Class<U> xface) {
336369
return rawConfig.getClass(accountConf(name),
337-
rawConfig.getClass(name, defaultValue, xface),
370+
defaultValue,
338371
xface);
339372
}
340373

341374
/**
342-
* Returns the account-specific password in string form if it exists, then
375+
* Returns account-agnostic Class if it exists, else returns the default value.
376+
* @param name Account-agnostic configuration key
377+
* @param defaultValue Class returned if none is configured
378+
* @param xface Interface shared by all possible values
379+
* @param <U> Interface class type
380+
* @return Account-Agnostic Class object that was found
381+
*/
382+
public <U> Class<? extends U> getAccountAgnosticClass(String name,
383+
Class<? extends U> defaultValue,
384+
Class<U> xface) {
385+
return rawConfig.getClass(name, defaultValue, xface);
386+
}
387+
388+
/**
389+
* Returns the account-specific enum value if it exists, then
343390
* looks for an account-agnostic value.
344391
* @param name Account-agnostic configuration key
345392
* @param defaultValue Value returned if none is configured
346-
* @return value in String form if one exists, else null
393+
* @param <T> Enum type
394+
* @return enum value if one exists, else null
347395
*/
348396
public <T extends Enum<T>> T getEnum(String name, T defaultValue) {
349397
return rawConfig.getEnum(accountConf(name),
350398
rawConfig.getEnum(name, defaultValue));
351399
}
352400

401+
/**
402+
* Returns the account-agnostic enum value if it exists, else
403+
* return default.
404+
* @param name Account-agnostic configuration key
405+
* @param defaultValue Value returned if none is configured
406+
* @param <T> Enum type
407+
* @return enum value if one exists, else null
408+
*/
409+
public <T extends Enum<T>> T getAccountAgnosticEnum(String name, T defaultValue) {
410+
return rawConfig.getEnum(name, defaultValue);
411+
}
412+
353413
/**
354414
* Unsets parameter in the underlying Configuration object.
355415
* Provided only as a convenience; does not add any account logic.
@@ -560,8 +620,10 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio
560620
if (authType == AuthType.OAuth) {
561621
try {
562622
Class<? extends AccessTokenProvider> tokenProviderClass =
563-
getClass(FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME, null,
564-
AccessTokenProvider.class);
623+
getTokenProviderClass(authType,
624+
FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME, null,
625+
AccessTokenProvider.class);
626+
565627
AccessTokenProvider tokenProvider = null;
566628
if (tokenProviderClass == ClientCredsTokenProvider.class) {
567629
String authEndpoint = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT);
@@ -604,14 +666,17 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio
604666
} catch(IllegalArgumentException e) {
605667
throw e;
606668
} catch (Exception e) {
607-
throw new TokenAccessProviderException("Unable to load key provider class.", e);
669+
throw new TokenAccessProviderException("Unable to load OAuth token provider class.", e);
608670
}
609671

610672
} else if (authType == AuthType.Custom) {
611673
try {
612674
String configKey = FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME;
613-
Class<? extends CustomTokenProviderAdaptee> customTokenProviderClass =
614-
getClass(configKey, null, CustomTokenProviderAdaptee.class);
675+
676+
Class<? extends CustomTokenProviderAdaptee> customTokenProviderClass
677+
= getTokenProviderClass(authType, configKey, null,
678+
CustomTokenProviderAdaptee.class);
679+
615680
if (customTokenProviderClass == null) {
616681
throw new IllegalArgumentException(
617682
String.format("The configuration value for \"%s\" is invalid.", configKey));
@@ -647,7 +712,9 @@ public SASTokenProvider getSASTokenProvider() throws AzureBlobFileSystemExceptio
647712
try {
648713
String configKey = FS_AZURE_SAS_TOKEN_PROVIDER_TYPE;
649714
Class<? extends SASTokenProvider> sasTokenProviderClass =
650-
getClass(configKey, null, SASTokenProvider.class);
715+
getTokenProviderClass(authType, configKey, null,
716+
SASTokenProvider.class);
717+
651718
Preconditions.checkArgument(sasTokenProviderClass != null,
652719
String.format("The configuration value for \"%s\" is invalid.", configKey));
653720

hadoop-tools/hadoop-azure/src/test/java/org/apache/hadoop/fs/azurebfs/TestAccountConfiguration.java

Lines changed: 186 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,24 @@
2020

2121
import java.io.IOException;
2222

23+
import org.assertj.core.api.Assertions;
24+
import org.junit.Test;
25+
2326
import org.apache.hadoop.conf.Configuration;
2427
import org.apache.hadoop.fs.azurebfs.contracts.exceptions.InvalidConfigurationValueException;
28+
import org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider;
29+
import org.apache.hadoop.fs.azurebfs.oauth2.CustomTokenProviderAdapter;
30+
import org.apache.hadoop.fs.azurebfs.services.AuthType;
2531

2632
import static org.junit.Assert.assertEquals;
2733
import static org.junit.Assert.assertNull;
2834

29-
import org.junit.Test;
35+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME;
36+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT;
37+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_CLIENT_ID;
38+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_OAUTH_CLIENT_SECRET;
39+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME;
40+
import static org.apache.hadoop.fs.azurebfs.constants.ConfigurationKeys.FS_AZURE_SAS_TOKEN_PROVIDER_TYPE;
3041

3142
/**
3243
* Tests correct precedence of various configurations that might be returned.
@@ -40,6 +51,14 @@
4051
* that do allow default values (all others) follow another form.
4152
*/
4253
public class TestAccountConfiguration {
54+
private static final String TEST_OAUTH_PROVIDER_CLASS_CONFIG = "org.apache.hadoop.fs.azurebfs.oauth2.ClientCredsTokenProvider";
55+
private static final String TEST_CUSTOM_PROVIDER_CLASS_CONFIG = "org.apache.hadoop.fs.azurebfs.oauth2.RetryTestTokenProvider";
56+
private static final String TEST_SAS_PROVIDER_CLASS_CONFIG_1 = "org.apache.hadoop.fs.azurebfs.extensions.MockErrorSASTokenProvider";
57+
private static final String TEST_SAS_PROVIDER_CLASS_CONFIG_2 = "org.apache.hadoop.fs.azurebfs.extensions.MockSASTokenProvider";
58+
59+
private static final String TEST_OAUTH_ENDPOINT = "oauthEndpoint";
60+
private static final String TEST_CLIENT_ID = "clientId";
61+
private static final String TEST_CLIENT_SECRET = "clientSecret";
4362

4463
@Test
4564
public void testStringPrecedence()
@@ -248,7 +267,7 @@ private class GetClassImpl1 implements GetClassInterface {
248267
}
249268

250269
@Test
251-
public void testClassPrecedence()
270+
public void testClass()
252271
throws IllegalAccessException, IOException, InvalidConfigurationValueException {
253272

254273
final String accountName = "account";
@@ -264,22 +283,182 @@ public void testClassPrecedence()
264283

265284
conf.setClass(globalKey, class0, xface);
266285
assertEquals("Default value returned even though account-agnostic config was set",
267-
abfsConf.getClass(globalKey, class1, xface), class0);
286+
abfsConf.getAccountAgnosticClass(globalKey, class1, xface), class0);
268287
conf.unset(globalKey);
269288
assertEquals("Default value not returned even though config was unset",
270-
abfsConf.getClass(globalKey, class1, xface), class1);
289+
abfsConf.getAccountAgnosticClass(globalKey, class1, xface), class1);
271290

272291
conf.setClass(accountKey, class0, xface);
273292
assertEquals("Default value returned even though account-specific config was set",
274-
abfsConf.getClass(globalKey, class1, xface), class0);
293+
abfsConf.getAccountSpecificClass(globalKey, class1, xface), class0);
275294
conf.unset(accountKey);
276295
assertEquals("Default value not returned even though config was unset",
277-
abfsConf.getClass(globalKey, class1, xface), class1);
296+
abfsConf.getAccountSpecificClass(globalKey, class1, xface), class1);
278297

279298
conf.setClass(accountKey, class1, xface);
280299
conf.setClass(globalKey, class0, xface);
281300
assertEquals("Account-agnostic or default value returned even though account-specific config was set",
282-
abfsConf.getClass(globalKey, class0, xface), class1);
301+
abfsConf.getAccountSpecificClass(globalKey, class0, xface), class1);
302+
}
303+
304+
@Test
305+
public void testSASProviderPrecedence()
306+
throws IOException, IllegalAccessException {
307+
final String accountName = "account";
308+
309+
final Configuration conf = new Configuration();
310+
final AbfsConfiguration abfsConf = new AbfsConfiguration(conf, accountName);
311+
312+
// AccountSpecific: SAS with provider set as SAS_Provider_1
313+
abfsConf.set(FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME + "." + accountName,
314+
"SAS");
315+
abfsConf.set(FS_AZURE_SAS_TOKEN_PROVIDER_TYPE + "." + accountName,
316+
TEST_SAS_PROVIDER_CLASS_CONFIG_1);
317+
318+
// Global: SAS with provider set as SAS_Provider_2
319+
abfsConf.set(FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME,
320+
AuthType.SAS.toString());
321+
abfsConf.set(FS_AZURE_SAS_TOKEN_PROVIDER_TYPE,
322+
TEST_SAS_PROVIDER_CLASS_CONFIG_2);
323+
324+
Assertions.assertThat(
325+
abfsConf.getSASTokenProvider().getClass().getName())
326+
.describedAs(
327+
"Account-specific SAS token provider should be in effect.")
328+
.isEqualTo(TEST_SAS_PROVIDER_CLASS_CONFIG_1);
329+
}
330+
331+
@Test
332+
public void testAccessTokenProviderPrecedence()
333+
throws IllegalAccessException, IOException {
334+
final String accountName = "account";
335+
336+
final Configuration conf = new Configuration();
337+
final AbfsConfiguration abfsConf = new AbfsConfiguration(conf, accountName);
338+
339+
// Global: Custom , AccountSpecific: OAuth
340+
testGlobalAndAccountOAuthPrecedence(abfsConf, AuthType.Custom,
341+
AuthType.OAuth);
342+
343+
// Global: OAuth , AccountSpecific: Custom
344+
testGlobalAndAccountOAuthPrecedence(abfsConf, AuthType.OAuth,
345+
AuthType.Custom);
346+
347+
// Global: (non-oAuth) SAS , AccountSpecific: Custom
348+
testGlobalAndAccountOAuthPrecedence(abfsConf, AuthType.SAS,
349+
AuthType.Custom);
350+
351+
// Global: Custom , AccountSpecific: -
352+
testGlobalAndAccountOAuthPrecedence(abfsConf, AuthType.Custom, null);
353+
354+
// Global: OAuth , AccountSpecific: -
355+
testGlobalAndAccountOAuthPrecedence(abfsConf, AuthType.OAuth, null);
356+
357+
// Global: - , AccountSpecific: Custom
358+
testGlobalAndAccountOAuthPrecedence(abfsConf, null, AuthType.Custom);
359+
360+
// Global: - , AccountSpecific: OAuth
361+
testGlobalAndAccountOAuthPrecedence(abfsConf, null, AuthType.OAuth);
362+
}
363+
364+
public void testGlobalAndAccountOAuthPrecedence(AbfsConfiguration abfsConf,
365+
AuthType globalAuthType,
366+
AuthType accountSpecificAuthType)
367+
throws IOException {
368+
if (globalAuthType == null) {
369+
unsetAuthConfig(abfsConf, false);
370+
} else {
371+
setAuthConfig(abfsConf, false, globalAuthType);
372+
}
373+
374+
if (accountSpecificAuthType == null) {
375+
unsetAuthConfig(abfsConf, true);
376+
} else {
377+
setAuthConfig(abfsConf, true, accountSpecificAuthType);
378+
}
379+
380+
// If account specific AuthType is present, precedence is always for it.
381+
AuthType expectedEffectiveAuthType;
382+
if (accountSpecificAuthType != null) {
383+
expectedEffectiveAuthType = accountSpecificAuthType;
384+
} else {
385+
expectedEffectiveAuthType = globalAuthType;
386+
}
387+
388+
Class<?> expectedEffectiveTokenProviderClassType =
389+
(expectedEffectiveAuthType == AuthType.OAuth)
390+
? ClientCredsTokenProvider.class
391+
: CustomTokenProviderAdapter.class;
392+
393+
Assertions.assertThat(
394+
abfsConf.getTokenProvider().getClass().getTypeName())
395+
.describedAs(
396+
"Account-specific settings takes precendence to global"
397+
+ " settings. In absence of Account settings, global settings "
398+
+ "should take effect.")
399+
.isEqualTo(expectedEffectiveTokenProviderClassType.getTypeName());
400+
401+
402+
unsetAuthConfig(abfsConf, false);
403+
unsetAuthConfig(abfsConf, true);
404+
}
405+
406+
public void setAuthConfig(AbfsConfiguration abfsConf,
407+
boolean isAccountSetting,
408+
AuthType authType) {
409+
final String accountNameSuffix = "." + abfsConf.getAccountName();
410+
String authKey = FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME
411+
+ (isAccountSetting ? accountNameSuffix : "");
412+
String providerClassKey = "";
413+
String providerClassValue = "";
414+
415+
switch (authType) {
416+
case OAuth:
417+
providerClassKey = FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME
418+
+ (isAccountSetting ? accountNameSuffix : "");
419+
providerClassValue = TEST_OAUTH_PROVIDER_CLASS_CONFIG;
420+
421+
abfsConf.set(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT
422+
+ ((isAccountSetting) ? accountNameSuffix : ""),
423+
TEST_OAUTH_ENDPOINT);
424+
abfsConf.set(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ID
425+
+ ((isAccountSetting) ? accountNameSuffix : ""),
426+
TEST_CLIENT_ID);
427+
abfsConf.set(FS_AZURE_ACCOUNT_OAUTH_CLIENT_SECRET
428+
+ ((isAccountSetting) ? accountNameSuffix : ""),
429+
TEST_CLIENT_SECRET);
430+
break;
431+
432+
case Custom:
433+
providerClassKey = FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME
434+
+ (isAccountSetting ? accountNameSuffix : "");
435+
providerClassValue = TEST_CUSTOM_PROVIDER_CLASS_CONFIG;
436+
break;
437+
438+
case SAS:
439+
providerClassKey = FS_AZURE_SAS_TOKEN_PROVIDER_TYPE
440+
+ (isAccountSetting ? accountNameSuffix : "");
441+
providerClassValue = TEST_SAS_PROVIDER_CLASS_CONFIG_1;
442+
break;
443+
444+
default: // set nothing
445+
}
446+
447+
abfsConf.set(authKey, authType.toString());
448+
abfsConf.set(providerClassKey, providerClassValue);
449+
}
450+
451+
private void unsetAuthConfig(AbfsConfiguration abfsConf, boolean isAccountSettings) {
452+
String accountNameSuffix =
453+
isAccountSettings ? ("." + abfsConf.getAccountName()) : "";
454+
455+
abfsConf.unset(FS_AZURE_ACCOUNT_AUTH_TYPE_PROPERTY_NAME + accountNameSuffix);
456+
abfsConf.unset(FS_AZURE_ACCOUNT_TOKEN_PROVIDER_TYPE_PROPERTY_NAME + accountNameSuffix);
457+
abfsConf.unset(FS_AZURE_SAS_TOKEN_PROVIDER_TYPE + accountNameSuffix);
458+
459+
abfsConf.unset(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ENDPOINT + accountNameSuffix);
460+
abfsConf.unset(FS_AZURE_ACCOUNT_OAUTH_CLIENT_ID + accountNameSuffix);
461+
abfsConf.unset(FS_AZURE_ACCOUNT_OAUTH_CLIENT_SECRET + accountNameSuffix);
283462
}
284463

285464
}

0 commit comments

Comments
 (0)