Skip to content

Commit f12434f

Browse files
committed
add support for DefaultAzureCredentials
1 parent e51a97b commit f12434f

File tree

7 files changed

+285
-7
lines changed

7 files changed

+285
-7
lines changed

entraid/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,11 @@
6767
<artifactId>msal4j</artifactId>
6868
<version>1.17.2</version>
6969
</dependency>
70+
<dependency>
71+
<groupId>com.azure</groupId>
72+
<artifactId>azure-identity</artifactId>
73+
<version>1.15.3</version>
74+
</dependency>
7075
<dependency>
7176
<groupId>junit</groupId>
7277
<artifactId>junit</artifactId>
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
3+
*/
4+
package redis.clients.authentication.entraid;
5+
6+
import java.time.Duration;
7+
import java.util.ArrayList;
8+
import java.util.Set;
9+
import java.util.function.Supplier;
10+
11+
import com.azure.core.credential.AccessToken;
12+
import com.azure.core.credential.TokenRequestContext;
13+
import com.azure.identity.DefaultAzureCredential;
14+
import redis.clients.authentication.core.IdentityProvider;
15+
import redis.clients.authentication.core.Token;
16+
17+
public final class AzureIdentityProvider implements IdentityProvider {
18+
19+
private Supplier<AccessToken> accessTokenSupplier;
20+
21+
public AzureIdentityProvider(DefaultAzureCredential defaultAzureCredential, Set<String> scopes,
22+
int timeout) {
23+
TokenRequestContext ctx = new TokenRequestContext()
24+
.setScopes(new ArrayList<String>(scopes));
25+
accessTokenSupplier = () -> defaultAzureCredential.getToken(ctx)
26+
.block(Duration.ofMillis(timeout));
27+
}
28+
29+
@Override
30+
public Token requestToken() {
31+
return new JWToken(accessTokenSupplier.get().getToken());
32+
}
33+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication.entraid;
8+
9+
import java.util.Set;
10+
import java.util.function.Supplier;
11+
12+
import com.azure.identity.DefaultAzureCredential;
13+
14+
import redis.clients.authentication.core.IdentityProvider;
15+
import redis.clients.authentication.core.IdentityProviderConfig;
16+
17+
public final class AzureIdentityProviderConfig implements IdentityProviderConfig {
18+
19+
private final Supplier<IdentityProvider> providerSupplier;
20+
21+
public AzureIdentityProviderConfig(DefaultAzureCredential defaultAzureCredential, Set<String> scopes, int timeout) {
22+
providerSupplier = () -> new AzureIdentityProvider(defaultAzureCredential, scopes, timeout);
23+
}
24+
25+
@Override
26+
public IdentityProvider getProvider() {
27+
return providerSupplier.get();
28+
}
29+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
3+
*/
4+
package redis.clients.authentication.entraid;
5+
6+
import java.util.Collections;
7+
import java.util.Set;
8+
9+
import com.azure.identity.DefaultAzureCredential;
10+
11+
import redis.clients.authentication.core.TokenAuthConfig;
12+
import redis.clients.authentication.core.TokenManagerConfig;
13+
14+
public class AzureTokenAuthConfigBuilder
15+
extends TokenAuthConfig.Builder<AzureTokenAuthConfigBuilder> implements AutoCloseable {
16+
public static final float DEFAULT_EXPIRATION_REFRESH_RATIO = 0.75F;
17+
public static final int DEFAULT_LOWER_REFRESH_BOUND_MILLIS = 2 * 60 * 1000;
18+
public static final int DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS = 1000;
19+
public static final int DEFAULT_MAX_ATTEMPTS_TO_RETRY = 5;
20+
public static final int DEFAULT_DELAY_IN_MS_TO_RETRY = 100;
21+
public static final Set<String> DEFAULT_SCOPES = Collections.singleton("https://redis.azure.com/.default");;
22+
23+
private DefaultAzureCredential defaultAzureCredential;
24+
private Set<String> scopes = DEFAULT_SCOPES;
25+
private int tokenRequestExecTimeoutInMs;
26+
27+
public AzureTokenAuthConfigBuilder() {
28+
this.expirationRefreshRatio(DEFAULT_EXPIRATION_REFRESH_RATIO)
29+
.lowerRefreshBoundMillis(DEFAULT_LOWER_REFRESH_BOUND_MILLIS)
30+
.tokenRequestExecTimeoutInMs(DEFAULT_TOKEN_REQUEST_EXECUTION_TIMEOUT_IN_MS)
31+
.maxAttemptsToRetry(DEFAULT_MAX_ATTEMPTS_TO_RETRY)
32+
.delayInMsToRetry(DEFAULT_DELAY_IN_MS_TO_RETRY);
33+
}
34+
35+
public AzureTokenAuthConfigBuilder defaultAzureCredential(
36+
DefaultAzureCredential defaultAzureCredential) {
37+
this.defaultAzureCredential = defaultAzureCredential;
38+
return this;
39+
}
40+
41+
public AzureTokenAuthConfigBuilder scopes(Set<String> scopes) {
42+
this.scopes = scopes;
43+
return this;
44+
}
45+
46+
@Override
47+
public AzureTokenAuthConfigBuilder tokenRequestExecTimeoutInMs(
48+
int tokenRequestExecTimeoutInMs) {
49+
super.tokenRequestExecTimeoutInMs(tokenRequestExecTimeoutInMs);
50+
this.tokenRequestExecTimeoutInMs = tokenRequestExecTimeoutInMs;
51+
return this;
52+
}
53+
54+
public TokenAuthConfig build() {
55+
super.identityProviderConfig(new AzureIdentityProviderConfig(defaultAzureCredential, scopes,
56+
tokenRequestExecTimeoutInMs));
57+
return super.build();
58+
}
59+
60+
@Override
61+
public void close() throws Exception {
62+
defaultAzureCredential = null;
63+
scopes = null;
64+
}
65+
66+
public static AzureTokenAuthConfigBuilder builder() {
67+
return new AzureTokenAuthConfigBuilder();
68+
}
69+
70+
public static AzureTokenAuthConfigBuilder from(AzureTokenAuthConfigBuilder sample) {
71+
TokenAuthConfig tokenAuthConfig = TokenAuthConfig.Builder.from(sample).build();
72+
TokenManagerConfig tokenManagerConfig = tokenAuthConfig.getTokenManagerConfig();
73+
74+
AzureTokenAuthConfigBuilder builder = (AzureTokenAuthConfigBuilder) new AzureTokenAuthConfigBuilder()
75+
.expirationRefreshRatio(tokenManagerConfig.getExpirationRefreshRatio())
76+
.lowerRefreshBoundMillis(tokenManagerConfig.getLowerRefreshBoundMillis())
77+
.tokenRequestExecTimeoutInMs(tokenManagerConfig.getTokenRequestExecTimeoutInMs())
78+
.maxAttemptsToRetry(tokenManagerConfig.getRetryPolicy().getMaxAttempts())
79+
.delayInMsToRetry(tokenManagerConfig.getRetryPolicy().getdelayInMs())
80+
.identityProviderConfig(tokenAuthConfig.getIdentityProviderConfig());
81+
builder.scopes = sample.scopes;
82+
return builder;
83+
}
84+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors All rights reserved. Licensed under the MIT License.
3+
*/
4+
package redis.clients.authentication;
5+
6+
import static org.junit.Assert.assertFalse;
7+
import static org.junit.Assert.assertNotNull;
8+
import org.junit.Test;
9+
import com.azure.identity.DefaultAzureCredential;
10+
import com.azure.identity.DefaultAzureCredentialBuilder;
11+
12+
import redis.clients.authentication.core.Token;
13+
import redis.clients.authentication.entraid.AzureIdentityProvider;
14+
import redis.clients.authentication.entraid.AzureTokenAuthConfigBuilder;
15+
16+
public class AzureIdentityProviderIntegrationTests {
17+
18+
@Test
19+
public void requestTokenWithDefaultAzureCredential() {
20+
// ensure environment variables are set
21+
String client_id = System.getenv(TestContext.AZURE_CLIENT_ID);
22+
assertNotNull(client_id);
23+
assertFalse(client_id.isEmpty());
24+
String clientSecret = System.getenv(TestContext.AZURE_CLIENT_SECRET);
25+
assertNotNull(clientSecret);
26+
assertFalse(clientSecret.isEmpty());
27+
String tenantId = System.getenv("AZURE_TENANT_ID");
28+
assertNotNull(tenantId);
29+
assertFalse(tenantId.isEmpty());
30+
31+
DefaultAzureCredential defaultAzureCredential = new DefaultAzureCredentialBuilder().build();
32+
Token token = new AzureIdentityProvider(defaultAzureCredential,
33+
AzureTokenAuthConfigBuilder.DEFAULT_SCOPES, 2000).requestToken();
34+
assertNotNull(token.getValue());
35+
}
36+
}
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
/*
2+
* Copyright 2024, Redis Ltd. and Contributors
3+
* All rights reserved.
4+
*
5+
* Licensed under the MIT License.
6+
*/
7+
package redis.clients.authentication;
8+
9+
import static org.junit.Assert.assertEquals;
10+
import static org.junit.Assert.assertTrue;
11+
import static org.mockito.ArgumentMatchers.any;
12+
import static org.mockito.Mockito.atLeast;
13+
import static org.mockito.Mockito.mock;
14+
import static org.mockito.Mockito.mockConstruction;
15+
import static org.mockito.Mockito.verify;
16+
import static org.mockito.Mockito.when;
17+
18+
import java.time.OffsetDateTime;
19+
import java.util.Date;
20+
import java.util.Set;
21+
22+
import org.junit.Test;
23+
import org.mockito.ArgumentCaptor;
24+
import org.mockito.MockedConstruction;
25+
26+
import com.auth0.jwt.JWT;
27+
import com.auth0.jwt.algorithms.Algorithm;
28+
import com.azure.core.credential.AccessToken;
29+
import com.azure.core.credential.TokenRequestContext;
30+
import com.azure.identity.DefaultAzureCredential;
31+
32+
import reactor.core.publisher.Mono;
33+
import redis.clients.authentication.entraid.AzureIdentityProvider;
34+
import redis.clients.authentication.entraid.AzureIdentityProviderConfig;
35+
import redis.clients.authentication.entraid.AzureTokenAuthConfigBuilder;
36+
37+
public class AzureIdentityProviderUnitTests {
38+
@Test
39+
public void testAzureTokenAuthConfigBuilder() {
40+
DefaultAzureCredential mockCredential = mock(DefaultAzureCredential.class);
41+
Set<String> scopes = AzureTokenAuthConfigBuilder.DEFAULT_SCOPES;
42+
int timeout = 2000;
43+
44+
try (MockedConstruction<AzureIdentityProviderConfig> mockedConstructor = mockConstruction(
45+
AzureIdentityProviderConfig.class,
46+
(mock, context) -> {
47+
assertEquals(mockCredential, context.arguments().get(0));
48+
assertEquals(scopes, context.arguments().get(1));
49+
assertEquals(timeout, context.arguments().get(2));
50+
})) {
51+
AzureTokenAuthConfigBuilder.builder().defaultAzureCredential(mockCredential).scopes(scopes)
52+
.tokenRequestExecTimeoutInMs(timeout).build();
53+
}
54+
}
55+
56+
public void testAzureIdentityProviderConfig() {
57+
DefaultAzureCredential mockCredential = mock(DefaultAzureCredential.class);
58+
Set<String> scopes = AzureTokenAuthConfigBuilder.DEFAULT_SCOPES;
59+
int timeout = 2000;
60+
61+
try (MockedConstruction<AzureIdentityProvider> mockedConstructor = mockConstruction(
62+
AzureIdentityProvider.class,
63+
(mock, context) -> {
64+
assertEquals(mockCredential, context.arguments().get(0));
65+
assertEquals(scopes, context.arguments().get(1));
66+
assertEquals(timeout, context.arguments().get(2));
67+
})) {
68+
new AzureIdentityProviderConfig(mockCredential, scopes, timeout).getProvider();
69+
}
70+
}
71+
72+
@Test
73+
public void testRequestWithMockCredential() {
74+
String token = JWT.create().withExpiresAt(new Date(System.currentTimeMillis()
75+
- 1000))
76+
.withClaim("oid", "user1").sign(Algorithm.none());
77+
78+
AccessToken t = new AccessToken(token, OffsetDateTime.now());
79+
Mono<AccessToken> monoToken = Mono.just(t);
80+
DefaultAzureCredential mockCredential = mock(DefaultAzureCredential.class);
81+
when(mockCredential.getToken(any(TokenRequestContext.class))).thenReturn(monoToken);
82+
new AzureIdentityProviderConfig(mockCredential,
83+
AzureTokenAuthConfigBuilder.DEFAULT_SCOPES, 0).getProvider().requestToken();
84+
85+
ArgumentCaptor<TokenRequestContext> argument = ArgumentCaptor.forClass(TokenRequestContext.class);
86+
87+
verify(mockCredential, atLeast(1)).getToken(argument.capture());
88+
AzureTokenAuthConfigBuilder.DEFAULT_SCOPES
89+
.forEach((item) -> assertTrue(argument.getValue().getScopes().contains(item)));
90+
}
91+
}

entraid/src/test/java/redis/clients/authentication/TestContext.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,13 @@
1919

2020
public class TestContext {
2121

22-
private static final String AZURE_CLIENT_ID = "AZURE_CLIENT_ID";
23-
private static final String AZURE_AUTHORITY = "AZURE_AUTHORITY";
24-
private static final String AZURE_CLIENT_SECRET = "AZURE_CLIENT_SECRET";
25-
private static final String AZURE_PRIVATE_KEY = "AZURE_PRIVATE_KEY";
26-
private static final String AZURE_CERT = "AZURE_CERT";
27-
private static final String AZURE_REDIS_SCOPES = "AZURE_REDIS_SCOPES";
28-
private static final String AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID = "AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID";
22+
public static final String AZURE_CLIENT_ID = "AZURE_CLIENT_ID";
23+
public static final String AZURE_AUTHORITY = "AZURE_AUTHORITY";
24+
public static final String AZURE_CLIENT_SECRET = "AZURE_CLIENT_SECRET";
25+
public static final String AZURE_PRIVATE_KEY = "AZURE_PRIVATE_KEY";
26+
public static final String AZURE_CERT = "AZURE_CERT";
27+
public static final String AZURE_REDIS_SCOPES = "AZURE_REDIS_SCOPES";
28+
public static final String AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID = "AZURE_USER_ASSIGNED_MANAGED_IDENTITY_CLIENT_ID";
2929

3030
private String clientId;
3131
private String authority;

0 commit comments

Comments
 (0)