diff --git a/sdk/communication/azure-communication-common/CHANGELOG.md b/sdk/communication/azure-communication-common/CHANGELOG.md index c70615a0bd91..4fe3fea269c5 100644 --- a/sdk/communication/azure-communication-common/CHANGELOG.md +++ b/sdk/communication/azure-communication-common/CHANGELOG.md @@ -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` diff --git a/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenCredential.java b/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenCredential.java index f27d9b8261f3..4e6091a1c8dc 100644 --- a/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenCredential.java +++ b/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenCredential.java @@ -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 * diff --git a/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenRefreshOptions.java b/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenRefreshOptions.java new file mode 100644 index 000000000000..6defb522bbad --- /dev/null +++ b/sdk/communication/azure-communication-common/src/main/java/com/azure/communication/common/CommunicationTokenRefreshOptions.java @@ -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; + } +} diff --git a/sdk/communication/azure-communication-common/src/test/java/com/azure/communication/common/CommunicationTokenCredentialTests.java b/sdk/communication/azure-communication-common/src/test/java/com/azure/communication/common/CommunicationTokenCredentialTests.java index 154723b62b45..cc25065924e2 100644 --- a/sdk/communication/azure-communication-common/src/test/java/com/azure/communication/common/CommunicationTokenCredentialTests.java +++ b/sdk/communication/azure-communication-common/src/test/java/com/azure/communication/common/CommunicationTokenCredentialTests.java @@ -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"); @@ -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()) @@ -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()); @@ -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()); @@ -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()); @@ -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 -> { @@ -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()); @@ -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()); @@ -262,7 +269,8 @@ public Mono 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); @@ -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()); @@ -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())