Skip to content
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@
import org.apache.hadoop.fs.azurebfs.oauth2.RefreshTokenBasedTokenProvider;
import org.apache.hadoop.fs.azurebfs.oauth2.UserPasswordTokenProvider;
import org.apache.hadoop.fs.azurebfs.oauth2.WorkloadIdentityTokenProvider;
import org.apache.hadoop.fs.azurebfs.oauth2.PassthroughTokenProvider;
import org.apache.hadoop.fs.azurebfs.security.AbfsDelegationTokenManager;
import org.apache.hadoop.fs.azurebfs.services.AuthType;
import org.apache.hadoop.fs.azurebfs.services.ExponentialRetryPolicy;
Expand Down Expand Up @@ -1585,6 +1586,11 @@ public AccessTokenProvider getTokenProvider() throws TokenAccessProviderExceptio
authority, tenantGuid, clientId, tokenFile);
LOG.trace("WorkloadIdentityTokenProvider initialized with file-based token");
}
} else if (tokenProviderClass == PassthroughTokenProvider.class) {
String token = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_PASSTHROUGH_TOKEN);
String expiresOn = getPasswordString(FS_AZURE_ACCOUNT_OAUTH_PASSTHROUGH_EXPIRES_ON);
tokenProvider = new PassthroughTokenProvider(token, Integer.parseInt(expiresOn));
LOG.trace("PassthroughTokenProvider initialized");
} else {
throw new IllegalArgumentException("Failed to initialize " + tokenProviderClass);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,10 @@ public final class ConfigurationKeys {
public static final String FS_AZURE_ACCOUNT_OAUTH_REFRESH_TOKEN = "fs.azure.account.oauth2.refresh.token";
/** Key for oauth AAD refresh token endpoint: {@value}. */
public static final String FS_AZURE_ACCOUNT_OAUTH_REFRESH_TOKEN_ENDPOINT = "fs.azure.account.oauth2.refresh.token.endpoint";
/** Key for oauth token passed in from outside: {@value}. */
public static final String FS_AZURE_ACCOUNT_OAUTH_PASSTHROUGH_TOKEN = "fs.azure.account.oauth2.passthrough.token";
/** Key for oauth token expires_on passed in from outside: {@value}. */
public static final String FS_AZURE_ACCOUNT_OAUTH_PASSTHROUGH_EXPIRES_ON = "fs.azure.account.oauth2.passthrough.expires_on";
/** Key for oauth AAD workload identity token file path: {@value}. */
public static final String FS_AZURE_ACCOUNT_OAUTH_TOKEN_FILE = "fs.azure.account.oauth2.token.file";
/** Key for custom client assertion provider class for WorkloadIdentityTokenProvider */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,8 +223,11 @@ public static AzureADToken getTokenUsingRefreshToken(
if (clientId != null) {
qp.add("client_id", clientId);
}
Hashtable<String, String> headers = new Hashtable<>();
qp.add("scope", SCOPE);
headers.put("Origin", "*");
LOG.debug("AADToken: starting to fetch token using refresh token for client ID " + clientId);
return getTokenCall(authEndpoint, qp.serialize(), null, null);
return getTokenCall(authEndpoint, qp.serialize(), headers, "POST");
}


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
* <p>
* http://www.apache.org/licenses/LICENSE-2.0
* <p>
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.hadoop.fs.azurebfs.oauth2;

import org.apache.hadoop.util.Preconditions;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Date;

/**
* Provides tokens passed in from outside hadoop-azure.
*/
public class PassthroughTokenProvider extends AccessTokenProvider {

private final String token;

private final int expiresOn;

private static final Logger LOG = LoggerFactory.getLogger(AccessTokenProvider.class);

public PassthroughTokenProvider(final String token, int expiresOn) {
Preconditions.checkNotNull(token, "token");

this.token = token;
this.expiresOn = expiresOn;
}

@Override
protected AzureADToken refreshToken() throws IOException {
LOG.debug("AADToken: returning the passthrough token");
AzureADToken adToken = new AzureADToken();
adToken.setAccessToken(token);
LOG.debug("Expiry based on expires_on: {}", expiresOn);
adToken.setExpiry(new Date(expiresOn * 1000L));
return adToken;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.apache.hadoop.fs.azurebfs.oauth2;

import java.util.Date;

import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;

import org.apache.hadoop.fs.azurebfs.AbstractAbfsTestWithTimeout;

import static org.apache.hadoop.test.LambdaTestUtils.intercept;

/**
* Test the passthrough logic for tokens passed in from outside hadoop-azure.
*/
public class TestPassthroughTokenProvider extends AbstractAbfsTestWithTimeout {

private static final String TOKEN = "dummy-token";

public TestPassthroughTokenProvider() {
}

/**
* Verify constructor validates token parameter.
*/
@Test
public void testConstructorRejectsNullToken() throws Exception {
Throwable ex = intercept(RuntimeException.class, () -> {
new PassthroughTokenProvider(null, 0);
});
Assertions.assertThat(ex.getMessage())
.describedAs("Should validate token parameter")
.contains("token");
}

/**
* Verify refreshToken returns the token passed in and sets expiry based on expires_on seconds.
*/
@Test
public void testRefreshTokenReturnsPassthroughTokenAndExpiry() throws Exception {
int expiresOnSeconds = (int) (System.currentTimeMillis() / 1000L) + 300; // +5 minutes
PassthroughTokenProvider provider =
new PassthroughTokenProvider(TOKEN, expiresOnSeconds);

AzureADToken adToken = provider.getToken();

Assertions.assertThat(adToken)
.describedAs("Token should be returned")
.isNotNull();
Assertions.assertThat(adToken.getAccessToken())
.describedAs("Access token should match the passthrough token")
.isEqualTo(TOKEN);

Date expectedExpiry = new Date(expiresOnSeconds * 1000L);
Assertions.assertThat(adToken.getExpiry())
.describedAs("Expiry should be derived from expiresOn seconds")
.isEqualTo(expectedExpiry);
}

/**
* Verify provider caches the refreshed token, so multiple getToken() calls
* return the same token value and same expiry (no change).
*/
@Test
public void testGetTokenIsStableAcrossCalls() throws Exception {
int expiresOnSeconds = (int) (System.currentTimeMillis() / 1000L) + 300; // +5 minutes
PassthroughTokenProvider provider =
new PassthroughTokenProvider(TOKEN, expiresOnSeconds);

AzureADToken t1 = provider.getToken();
AzureADToken t2 = provider.getToken();

Assertions.assertThat(t2.getAccessToken())
.describedAs("Access token should remain stable across calls")
.isEqualTo(TOKEN);
Assertions.assertThat(t2.getExpiry())
.describedAs("Expiry should remain stable across calls")
.isEqualTo(t1.getExpiry());
}

/**
* Verify expiry is set correctly for an epoch-based expires_on value.
*/
@Test
public void testExpiryUsesEpochSeconds() throws Exception {
int expiresOnSeconds = 12345;
PassthroughTokenProvider provider =
new PassthroughTokenProvider(TOKEN, expiresOnSeconds);

AzureADToken adToken = provider.getToken();

Assertions.assertThat(adToken.getExpiry())
.describedAs("Expiry should be epoch seconds converted to milliseconds")
.isEqualTo(new Date(12345L * 1000L));
}
}