Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merge feature/tokenrefreshtoptions to master #13498

Merged
merged 35 commits into from
Jul 27, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
6d787fe
Prep for beta release
jianghaolu Jul 6, 2020
badf483
Set CHANGELOG release date
alzimmermsft Jul 8, 2020
d909ece
Add azure core to versioning_client (#12891)
jianghaolu Jul 8, 2020
91c5a21
Update change log date (#12918)
jianghaolu Jul 8, 2020
9d40496
Ignored flaky CF tests (#12915) (#12922)
jianghaolu Jul 8, 2020
ea841ea
disable flaky assert (#12793) (#12929)
jianghaolu Jul 8, 2020
f0b934a
Increment azure core version to 1.7.0-beta.2 (#13013)
jianghaolu Jul 10, 2020
015d5f2
Add token refresh options to identity library (#13029)
jianghaolu Jul 10, 2020
3f7a922
Add change log for identity 1.1.0-beta.6 (#13030)
jianghaolu Jul 10, 2020
dea32ab
Increment package version after release of com.azure azure-identity (…
azure-sdk Jul 10, 2020
8974f69
Remove token refresh options and default to 5 minutes (#13148)
jianghaolu Jul 14, 2020
cd555ed
Merge master into feature/tokenrefreshoptions (#13424)
jianghaolu Jul 22, 2020
35b18f7
Prepare azure core for 1.7.0-beta.2 release (#13428)
jianghaolu Jul 23, 2020
7014022
Add changelog to core 1.7.0-beta.2 (#13443)
jianghaolu Jul 23, 2020
7d2f80f
Update changelog date for azure-core 1.7.0-beta.2
jianghaolu Jul 23, 2020
305d562
Increment package version after release of com.azure azure-core (#13455)
azure-sdk Jul 23, 2020
b0042c7
Increment version for core release 1.7.0-beta.2
jianghaolu Jul 23, 2020
bdf44fe
update beta core versions (#13462)
g2vinay Jul 23, 2020
152c2e7
Increment package version after release of com.azure azure-identity (…
azure-sdk Jul 24, 2020
15ae1d4
Merge branch 'master' of github.com:Azure/azure-sdk-for-java
jianghaolu Jul 24, 2020
efaf94e
Merge branch 'feature/tokenrefreshoptions' of github.com:Azure/azure-…
jianghaolu Jul 24, 2020
ce145cb
Replace identity with feature branch
jianghaolu Jul 24, 2020
fb2251f
Merge branch 'ftro2m' of github.com:jianghaolu/azure-sdk-for-java int…
jianghaolu Jul 24, 2020
89bfd57
Update identity versions everywhere
jianghaolu Jul 24, 2020
b5efda6
reset changes in cosmos & form recognizer
jianghaolu Jul 24, 2020
c818504
Reset merge errors
jianghaolu Jul 24, 2020
bfe179e
Reset tables
jianghaolu Jul 24, 2020
7c4d57e
Reset latest cosmos changes
jianghaolu Jul 24, 2020
9943d17
Fix azure-core change log merge error
jianghaolu Jul 24, 2020
f603b89
Use beta_ versions in necessary modules only
jianghaolu Jul 24, 2020
87c4b5a
Spring boot doesn't need beta_
jianghaolu Jul 24, 2020
a19c201
Identity current version should 1.1.0-beta.8
jianghaolu Jul 24, 2020
7d2b1b7
Use current for core & identity instead of beta_
jianghaolu Jul 24, 2020
579e785
Use unreleased dependency
jianghaolu Jul 24, 2020
8d24937
Remove unreleased reference in event hubs
jianghaolu Jul 25, 2020
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
2 changes: 1 addition & 1 deletion eng/jacoco-test-coverage/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.1.0-beta.7</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
<version>1.1.0-beta.8</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand Down
2 changes: 2 additions & 0 deletions eng/versioning/version_client.txt
Original file line number Diff line number Diff line change
Expand Up @@ -87,3 +87,5 @@ unreleased_com.azure:azure-security-keyvault-keys;4.2.0-beta.6
# Format;
# beta_<groupId>:<artifactId>;dependency-version
# note: Released beta versions will not be manipulated with the automatic PR creation code.
beta_com.azure:azure-core;1.7.0-beta.2
beta_com.azure:azure-identity;1.1.0-beta.7
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,7 @@ public void teardown() {
@Test
public void authorizesSasToken() {
// Arrange
// Subtracting two minutes because the AccessToken does this internally.
final Date expectedDate = Date.from(validUntil.minusMinutes(2).toInstant());
final Date expectedDate = Date.from(validUntil.toInstant());
jianghaolu marked this conversation as resolved.
Show resolved Hide resolved
final ClaimsBasedSecurityChannel cbsChannel = new ClaimsBasedSecurityChannel(Mono.just(requestResponseChannel),
tokenCredential, CbsAuthorizationType.SHARED_ACCESS_SIGNATURE, options);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public class AccessToken {
*/
public AccessToken(String token, OffsetDateTime expiresAt) {
this.token = token;
this.expiresAt = expiresAt.minusMinutes(2); // 2 minutes before token expires
this.expiresAt = expiresAt;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,53 +3,139 @@

package com.azure.core.credential;

import reactor.core.publisher.FluxSink;
import reactor.core.publisher.FluxSink.OverflowStrategy;
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;
import reactor.core.publisher.ReplayProcessor;
import reactor.core.publisher.MonoProcessor;

import java.util.concurrent.atomic.AtomicBoolean;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Predicate;
import java.util.function.Supplier;

/**
* A token cache that supports caching a token and refreshing it.
*/
public class SimpleTokenCache {
private static final int REFRESH_TIMEOUT_SECONDS = 30;

private final AtomicBoolean wip;
private AccessToken cache;
private final ReplayProcessor<AccessToken> emitterProcessor = ReplayProcessor.create(1);
private final FluxSink<AccessToken> sink = emitterProcessor.sink(OverflowStrategy.BUFFER);
// The delay after a refresh to attempt another token refresh
private static final Duration REFRESH_DELAY = Duration.ofSeconds(30);
// the offset before token expiry to attempt proactive token refresh
private static final Duration REFRESH_OFFSET = Duration.ofMinutes(5);
private final AtomicReference<MonoProcessor<AccessToken>> wip;
private volatile AccessToken cache;
private volatile OffsetDateTime nextTokenRefresh = OffsetDateTime.now();
private final Supplier<Mono<AccessToken>> tokenSupplier;
private final Predicate<AccessToken> shouldRefresh;
private final ClientLogger logger = new ClientLogger(SimpleTokenCache.class);

/**
* Creates an instance of RefreshableTokenCredential with default scheme "Bearer".
*
* @param tokenSupplier a method to get a new token
*/
public SimpleTokenCache(Supplier<Mono<AccessToken>> tokenSupplier) {
this.wip = new AtomicBoolean(false);
this.wip = new AtomicReference<>();
this.tokenSupplier = tokenSupplier;
this.shouldRefresh = accessToken -> OffsetDateTime.now()
.isAfter(accessToken.getExpiresAt().minus(REFRESH_OFFSET));
}

/**
* Asynchronously get a token from either the cache or replenish the cache with a new token.
* @return a Publisher that emits an AccessToken
*/
public Mono<AccessToken> getToken() {
if (cache != null && !cache.isExpired()) {
return Mono.just(cache);
}
return Mono.defer(() -> {
if (!wip.getAndSet(true)) {
return tokenSupplier.get().doOnNext(ac -> cache = ac)
.doOnNext(sink::next)
.doOnError(sink::error)
.doOnTerminate(() -> wip.set(false));
} else {
return emitterProcessor.next();
try {
if (wip.compareAndSet(null, MonoProcessor.create())) {
final MonoProcessor<AccessToken> monoProcessor = wip.get();
OffsetDateTime now = OffsetDateTime.now();
Mono<AccessToken> tokenRefresh;
Mono<AccessToken> fallback;
if (cache != null && !shouldRefresh.test(cache)) {
// fresh cache & no need to refresh
tokenRefresh = Mono.empty();
fallback = Mono.just(cache);
} else if (cache == null || cache.isExpired()) {
// no token to use
if (now.isAfter(nextTokenRefresh)) {
// refresh immediately
tokenRefresh = Mono.defer(tokenSupplier);
} else {
// wait for timeout, then refresh
tokenRefresh = Mono.defer(tokenSupplier)
.delaySubscription(Duration.between(now, nextTokenRefresh));
}
// cache doesn't exist or expired, no fallback
fallback = Mono.empty();
} else {
// token available, but close to expiry
if (now.isAfter(nextTokenRefresh)) {
// refresh immediately
tokenRefresh = Mono.defer(tokenSupplier);
} else {
// still in timeout, do not refresh
tokenRefresh = Mono.empty();
}
// cache hasn't expired, ignore refresh error this time
fallback = Mono.just(cache);
}
return tokenRefresh
.materialize()
.flatMap(signal -> {
AccessToken accessToken = signal.get();
Throwable error = signal.getThrowable();
if (signal.isOnNext() && accessToken != null) { // SUCCESS
logger.info(refreshLog(cache, now, "Acquired a new access token"));
cache = accessToken;
monoProcessor.onNext(accessToken);
monoProcessor.onComplete();
nextTokenRefresh = OffsetDateTime.now().plus(REFRESH_DELAY);
return Mono.just(accessToken);
} else if (signal.isOnError() && error != null) { // ERROR
logger.error(refreshLog(cache, now, "Failed to acquire a new access token"));
nextTokenRefresh = OffsetDateTime.now().plus(REFRESH_DELAY);
return fallback.switchIfEmpty(Mono.error(error));
} else { // NO REFRESH
monoProcessor.onComplete();
return fallback;
}
})
.doOnError(monoProcessor::onError)
.doOnTerminate(() -> wip.set(null));
} else if (cache != null && !cache.isExpired()) {
// another thread might be refreshing the token proactively, but the current token is still valid
return Mono.just(cache);
} else {
// another thread is definitely refreshing the expired token
MonoProcessor<AccessToken> monoProcessor = wip.get();
if (monoProcessor == null) {
// the refreshing thread has finished
return Mono.just(cache);
} else {
// wait for refreshing thread to finish but defer to updated cache in case just missed onNext()
return monoProcessor.switchIfEmpty(Mono.defer(() -> Mono.just(cache)));
}
}
} catch (Throwable t) {
return Mono.error(t);
}
});
}

private String refreshLog(AccessToken cache, OffsetDateTime now, String log) {
StringBuilder info = new StringBuilder(log);
if (cache == null) {
info.append(".");
} else {
Duration tte = Duration.between(now, cache.getExpiresAt());
info.append(" at ").append(tte.abs().getSeconds()).append(" seconds ")
.append(tte.isNegative() ? "after" : "before").append(" expiry. ")
.append("Retry may be attempted after ").append(REFRESH_DELAY.getSeconds()).append(" seconds.");
if (!tte.isNegative()) {
info.append(" The token currently cached will be used.");
}
}
return info.toString();
}
}
2 changes: 1 addition & 1 deletion sdk/e2e/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.1.0-beta.7</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
<version>1.1.0-beta.8</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand Down
4 changes: 2 additions & 2 deletions sdk/eventhubs/azure-messaging-eventhubs/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.6.0</version> <!-- {x-version-update;com.azure:azure-core;dependency} -->
<version>1.7.0-beta.2</version> <!-- {x-version-update;beta_com.azure:azure-core;dependency} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand All @@ -49,7 +49,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.0.9</version> <!-- {x-version-update;com.azure:azure-identity;dependency} -->
<version>1.1.0-beta.7</version> <!-- {x-version-update;beta_com.azure:azure-identity;dependency} -->
<scope>test</scope>
</dependency>
<dependency>
Expand Down
2 changes: 1 addition & 1 deletion sdk/identity/azure-identity-perf/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.1.0-beta.7</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
<version>1.1.0-beta.8</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
</dependency>
<dependency>
<groupId>com.azure</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,16 +9,13 @@
import com.azure.perf.test.core.PerfStressOptions;
import reactor.core.publisher.Mono;

import java.time.Duration;

public class WriteCache extends ServiceTest<PerfStressOptions> {
private final SharedTokenCacheCredential credential;

public WriteCache(PerfStressOptions options) {
super(options);
credential = new SharedTokenCacheCredentialBuilder()
.clientId(CLI_CLIENT_ID)
.tokenRefreshOffset(Duration.ofMinutes(60))
.build();
}

Expand Down
19 changes: 17 additions & 2 deletions sdk/identity/azure-identity/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
# Release History

## 1.1.0-beta.7 (Unreleased)
- Added support for web apps (confidential apps) for `InteractiveBrowserCredential` and `AuthorizationCodeCredential`. A client secret is required on the builder for web apps.
## 1.1.0-beta.8 (Unreleased)


## 1.1.0-beta.7 (2020-07-23)

### Features
- Added support for web apps (confidential apps) for `AuthorizationCodeCredential`. A client secret is required on the builder for web apps.
- Added support for user assigned managed identities for `DefaultAzureCredential` with `.managedIdentityClientId()`.
- Added`AzureAuthorityHosts` to access well knwon authority hosts.
- Added `getClientId()` method in `AuthenticationRecord`

### Breaking Changes
- Removed persistent caching support from `AuthorizationCodeCredential`.
- Removed `KnownAuthorityHosts`
- Removed `getCredentials()` method in `ChainedTokenCredential` & `DefaultAzureCredential`
- Changed return type of `serialize` method in `AuthenticationRecord` to `Mono<OutputStream>`.
- Changed method signatures`enablePersistentCache(boolean)` and `allowUnencryptedCache(boolean)` on credential builders to `enablePersistentCache()` and `allowUnencryptedCache()`


## 1.1.0-beta.6 (2020-07-10)
- Added `.getCredentials()` method to `DefaultAzureCredential` and `ChainedTokenCredential` and added option `.addAll(Collection<? extends TokenCredential>)` on `ChainedtokenCredentialBuilder`.
Expand Down
4 changes: 2 additions & 2 deletions sdk/identity/azure-identity/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

<groupId>com.azure</groupId>
<artifactId>azure-identity</artifactId>
<version>1.1.0-beta.7</version> <!-- {x-version-update;com.azure:azure-identity;current} -->
<version>1.1.0-beta.8</version> <!-- {x-version-update;com.azure:azure-identity;current} -->

<name>Microsoft Azure client library for Identity</name>
<description>This module contains client library for Microsoft Azure Identity.</description>
Expand All @@ -27,7 +27,7 @@
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-core</artifactId>
<version>1.6.0</version> <!-- {x-version-update;com.azure:azure-core;dependency} -->
<version>1.7.0-beta.2</version> <!-- {x-version-update;beta_com.azure:azure-core;dependency} -->
</dependency>
<dependency>
<groupId>com.microsoft.azure</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,21 +89,4 @@ public T httpClient(HttpClient client) {
this.identityClientOptions.setHttpClient(client);
return (T) this;
}

/**
* Sets how long before the actual token expiry to refresh the token. The
* token will be considered expired at and after the time of (actual
* expiry - token refresh offset). The default offset is 2 minutes.
*
* This is useful when network is congested and a request containing the
* token takes longer than normal to get to the server.
*
* @param tokenRefreshOffset the duration before the actual expiry of a token to refresh it
* @return An updated instance of this builder with the token refresh offset set as specified.
*/
@SuppressWarnings("unchecked")
public T tokenRefreshOffset(Duration tokenRefreshOffset) {
this.identityClientOptions.setTokenRefreshOffset(tokenRefreshOffset);
return (T) this;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@
*/
@Immutable
public final class DefaultAzureCredential extends ChainedTokenCredential {

/**
* Creates default DefaultAzureCredential instance to use. This will use AZURE_CLIENT_ID,
* AZURE_CLIENT_SECRET, and AZURE_TENANT_ID environment variables to create a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
@Immutable
public class EnvironmentCredential implements TokenCredential {
private final Configuration configuration;
private final IdentityClientOptions identityClientOptions;
private final ClientLogger logger = new ClientLogger(EnvironmentCredential.class);
private final TokenCredential tokenCredential;

Expand All @@ -48,7 +47,6 @@ public class EnvironmentCredential implements TokenCredential {
*/
EnvironmentCredential(IdentityClientOptions identityClientOptions) {
this.configuration = Configuration.getGlobalConfiguration().clone();
this.identityClientOptions = identityClientOptions;
TokenCredential targetCredential = null;

String clientId = configuration.get(Configuration.PROPERTY_AZURE_CLIENT_ID);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,5 +122,4 @@ private AccessToken updateCache(MsalToken msalToken) {
identityClient.getTenantId(), identityClient.getClientId())));
return msalToken;
}

}
Loading