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

[Communication] - Common - Adding TokenRefreshOptions to CommunicationTokenCredential ctor #18060

Merged
merged 5 commits into from
Dec 15, 2020
Merged
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
1 change: 1 addition & 0 deletions sdk/communication/azure-communication-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
## 1.0.0-beta.4 (Unreleased)
### Breaking Changes
- Renamed `CommunicationUserCredential` to `CommunicationTokenCredential`
- Replaced constructor `CommunicationTokenCredential(TokenRefresher tokenRefresher, String initialToken, boolean refreshProactively)` and `CommunicationTokenCredential(TokenRefresher tokenRefresher)` with `CommunicationTokenCredential(CommunicationTokenRefreshOptions tokenRefreshOptions)`
- Renamed `PhoneNumber` to `PhoneNumberIdentifier`
- Renamed `CommunicationUser` to `CommunicationUserIdentifier `
- Renamed `CallingApplication` to `CallingApplicationIdentifier`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,49 +37,35 @@ public final class CommunicationTokenCredential implements AutoCloseable {
/**
* Create with serialized JWT token
*
* @param initialToken serialized JWT token
* @param token serialized JWT token
*/
public CommunicationTokenCredential(String initialToken) {
Objects.requireNonNull(initialToken, "'initialToken' cannot be null.");
setToken(initialToken);
public CommunicationTokenCredential(String token) {
Objects.requireNonNull(token, "'token' cannot be null.");
setToken(token);
}

/**
* Create with a tokenRefresher
*
* @param tokenRefresher implementation to supply fresh token when reqested
*/
public CommunicationTokenCredential(TokenRefresher tokenRefresher) {
Objects.requireNonNull(tokenRefresher, "'tokenRefresher' cannot be null.");
refresher = tokenRefresher;
}

/**
* Create with serialized JWT token and a token supplier to auto-refresh the
* token before it expires. Callback function tokenRefresher will be called
* Create with tokenRefreshOptions, which includes a token supplier and optional serialized JWT token.
* If refresh proactively is true, callback function tokenRefresher will be called
* ahead of the token expiry by the number of minutes specified by
* CallbackOffsetMinutes defaulted to two minutes. To modify this default, call
* CallbackOffsetMinutes defaulted to ten minutes. To modify this default, call
* setCallbackOffsetMinutes after construction
*
* @param tokenRefresher implementation to supply fresh token when reqested
* @param initialToken serialized JWT token
* @param refreshProactively when set to true, turn on proactive fetching to call
* tokenRefresher before token expiry by minutes set
* with setCallbackOffsetMinutes or default value of
* two minutes
* @param tokenRefreshOptions implementation to supply fresh token when reqested
*/
public CommunicationTokenCredential(TokenRefresher tokenRefresher, String initialToken,
boolean refreshProactively) {
this(tokenRefresher);
Objects.requireNonNull(initialToken, "'initialToken' cannot be null.");
setToken(initialToken);
if (refreshProactively) {
OffsetDateTime nextFetchTime = accessToken.getExpiresAt().minusMinutes(DEFAULT_EXPIRING_OFFSET_MINUTES);
fetchingTask = new FetchingTask(this, nextFetchTime);
public CommunicationTokenCredential(CommunicationTokenRefreshOptions tokenRefreshOptions) {
TokenRefresher tokenRefresher = tokenRefreshOptions.getTokenRefresher();
Objects.requireNonNull(tokenRefresher, "'tokenRefresher' cannot be null.");
refresher = tokenRefresher;
if (tokenRefreshOptions.getToken() != null) {
setToken(tokenRefreshOptions.getToken());
if (tokenRefreshOptions.getRefreshProactively()) {
OffsetDateTime nextFetchTime = accessToken.getExpiresAt().minusMinutes(DEFAULT_EXPIRING_OFFSET_MINUTES);
fetchingTask = new FetchingTask(this, nextFetchTime);
}
}
}


/**
* Get Azure core access token from credential
*
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.communication.common;

/**
* Options for refreshing CommunicationTokenCredential
*/
public class CommunicationTokenRefreshOptions {
private final TokenRefresher tokenRefresher;
private final boolean refreshProactively;
private final String token;

/**
* Creates a CommunicationTokenRefreshOptions object
*
* @param tokenRefresher the token refresher to provide capacity to fetch fresh token
* @param refreshProactively when set to true, turn on proactive fetching to call
* tokenRefresher before token expiry by minutes set
* with setCallbackOffsetMinutes or default value of
* two minutes
*/
public CommunicationTokenRefreshOptions(TokenRefresher tokenRefresher, boolean refreshProactively) {
this.tokenRefresher = tokenRefresher;
this.refreshProactively = refreshProactively;
this.token = null;
}

/**
* Creates a CommunicationTokenRefreshOptions object
*
* @param tokenRefresher the token refresher to provide capacity to fetch fresh token
* @param refreshProactively when set to true, turn on proactive fetching to call
* tokenRefresher before token expiry by minutes set
* with setCallbackOffsetMinutes or default value of
* two minutes
* @param token the optional serialized JWT token
*/
public CommunicationTokenRefreshOptions(TokenRefresher tokenRefresher, boolean refreshProactively, String token) {
this.tokenRefresher = tokenRefresher;
this.refreshProactively = refreshProactively;
this.token = token;
}

/**
* @return the token refresher to provide capacity to fetch fresh token
*/
public TokenRefresher getTokenRefresher() {
return tokenRefresher;
}

/**
* @return whether or not to refresh token proactively
*/
public boolean getRefreshProactively() {
return refreshProactively;
}

/**
* @return the serialized JWT token
*/
public String getToken() {
return token;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,8 +88,8 @@ public void fresherShouldNotBeCalledBeforeExpiringTime()
throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", 15 * 60);
immediateFresher.resetCallCount();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr,
true);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
StepVerifier.create(tokenCredential.getToken()).assertNext(token -> {
assertFalse(token.isExpired(),
"Refreshable AccessToken should not expire when expiry is set to 5 minutes later");
Expand All @@ -106,8 +106,8 @@ public void fresherShouldBeCalledAfterExpiringTime() throws InterruptedException
immediateFresher.resetCallCount();
CountDownLatch countDownLatch = new CountDownLatch(1);
immediateFresher.setOnCallReturn(countDownLatch::countDown);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, true);

CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
countDownLatch.await();
assertEquals(1, immediateFresher.numCalls());
StepVerifier.create(tokenCredential.getToken())
Expand All @@ -125,7 +125,8 @@ public void refresherShouldBeCalledImmediatelyWithExpiredToken()
immediateFresher.resetCallCount();
CountDownLatch countDownLatch = new CountDownLatch(1);
immediateFresher.setOnCallReturn(countDownLatch::countDown);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, true);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);

countDownLatch.await();
assertEquals(1, immediateFresher.numCalls());
Expand All @@ -143,7 +144,8 @@ public void refresherShouldBeCalledAgainAfterFirstRefreshCall()
immediateFresher.resetCallCount();
CountDownLatch firstCountDownLatch = new CountDownLatch(1);
immediateFresher.setOnCallReturn(firstCountDownLatch::countDown);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, true);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);

firstCountDownLatch.await();
assertEquals(1, immediateFresher.numCalls());
Expand All @@ -170,7 +172,9 @@ public void shouldNotCallRefresherWhenTokenStillValid()
throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", 15 * 60);
immediateFresher.resetCallCount();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, false);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, false, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);

StepVerifier.create(tokenCredential.getToken())
.assertNext(token -> {
assertFalse(token.isExpired());
Expand All @@ -191,7 +195,8 @@ public void expiredTokenShouldBeRefreshedOnDemandWithoutProactiveFetch()
throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", -5 * 60);
immediateFresher.resetCallCount();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, false);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, false, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
assertEquals(0, immediateFresher.numCalls());
StepVerifier.create(tokenCredential.getToken())
.assertNext(token -> {
Expand All @@ -211,7 +216,8 @@ public void expiredTokenShouldBeRefreshedOnDemandWithoutProactiveFetch()
@Test
public void shouldCallbackOnDemandWithoutRefresher() throws InterruptedException, ExecutionException, IOException {
immediateFresher.resetCallCount();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
StepVerifier.create(tokenCredential.getToken())
.assertNext(token -> {
assertEquals(1, immediateFresher.numCalls());
Expand All @@ -224,7 +230,8 @@ public void shouldCallbackOnDemandWithoutRefresher() throws InterruptedException
@Test
public void shouldStopRefreshTimerWhenClosed() throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", 12 * 60);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher, tokenStr, true);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(immediateFresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
assertTrue(tokenCredential.hasProactiveFetcher());
tokenCredential.close();
assertFalse(tokenCredential.hasProactiveFetcher());
Expand Down Expand Up @@ -262,7 +269,8 @@ public Mono<String> getTokenAsync() {
@Test
public void shouldNotModifyTokenWhenRefresherThrows() throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", 601);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(exceptionRefresher, tokenStr, true);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(exceptionRefresher, true, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
CountDownLatch countDownLatch = new CountDownLatch(1);
exceptionRefresher.setOnCallReturn(countDownLatch::countDown);

Expand All @@ -281,7 +289,8 @@ public void doNotSwallowExceptionWithoutProactiveFetching()
throws InterruptedException, ExecutionException, IOException {
String tokenStr = tokenMocker.generateRawToken("resourceId", "userIdentity", -5 * 60);
exceptionRefresher.resetCallCount();
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(exceptionRefresher, tokenStr, false);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(exceptionRefresher, false, tokenStr);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
StepVerifier.create(tokenCredential.getToken())
.verifyError(RuntimeException.class);
assertEquals(1, exceptionRefresher.numCalls());
Expand All @@ -291,7 +300,8 @@ public void doNotSwallowExceptionWithoutProactiveFetching()
@Test
public void shouldThrowWhenGetTokenCalledOnClosedObject() throws IOException, InterruptedException,
ExecutionException {
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(immediateFresher);
CommunicationTokenRefreshOptions tokenRefreshOptions = new CommunicationTokenRefreshOptions(exceptionRefresher, true);
CommunicationTokenCredential tokenCredential = new CommunicationTokenCredential(tokenRefreshOptions);
tokenCredential.close();

StepVerifier.create(tokenCredential.getToken())
Expand Down