Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ internal class LogoutManager(
ctOptions: CustomTabsOptions,
federated: Boolean = false,
private val launchAsTwa: Boolean = false,
private val customLogoutUrl: String? = null
) : ResumableManager() {
private val parameters: MutableMap<String, String>
private val ctOptions: CustomTabsOptions
Expand Down Expand Up @@ -42,7 +43,8 @@ internal class LogoutManager(
}

private fun buildLogoutUri(): Uri {
val logoutUri = Uri.parse(account.logoutUrl)
val urlToUse = customLogoutUrl ?: account.logoutUrl
val logoutUri = Uri.parse(urlToUse)
val builder = logoutUri.buildUpon()
for ((key, value) in parameters) {
builder.appendQueryParameter(key, value)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ internal class OAuthManager(
parameters: Map<String, String>,
ctOptions: CustomTabsOptions,
private val launchAsTwa: Boolean = false,
private val customAuthorizeUrl: String? = null
) : ResumableManager() {
private val parameters: MutableMap<String, String>
private val headers: MutableMap<String, String>
Expand Down Expand Up @@ -197,6 +198,7 @@ internal class OAuthManager(
auth0 = account,
idTokenVerificationIssuer = idTokenVerificationIssuer,
idTokenVerificationLeeway = idTokenVerificationLeeway,
customAuthorizeUrl = this.customAuthorizeUrl
)
}

Expand Down Expand Up @@ -235,7 +237,8 @@ internal class OAuthManager(
}

private fun buildAuthorizeUri(): Uri {
val authorizeUri = Uri.parse(account.authorizeUrl)
val urlToUse = customAuthorizeUrl ?: account.authorizeUrl
val authorizeUri = Uri.parse(urlToUse)
val builder = authorizeUri.buildUpon()
for ((key, value) in parameters) {
builder.appendQueryParameter(key, value)
Expand Down Expand Up @@ -357,7 +360,8 @@ internal fun OAuthManager.Companion.fromState(
account = state.auth0,
ctOptions = state.ctOptions,
parameters = state.parameters,
callback = callback
callback = callback,
customAuthorizeUrl = state.customAuthorizeUrl
).apply {
setHeaders(
state.headers
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ internal data class OAuthManagerState(
val ctOptions: CustomTabsOptions,
val pkce: PKCE?,
val idTokenVerificationLeeway: Int?,
val idTokenVerificationIssuer: String?
val idTokenVerificationIssuer: String?,
val customAuthorizeUrl: String? = null
) {

private class OAuthManagerJson(
Expand All @@ -32,7 +33,8 @@ internal data class OAuthManagerState(
val codeChallenge: String,
val codeVerifier: String,
val idTokenVerificationLeeway: Int?,
val idTokenVerificationIssuer: String?
val idTokenVerificationIssuer: String?,
val customAuthorizeUrl: String? = null
)

fun serializeToJson(
Expand All @@ -56,6 +58,7 @@ internal data class OAuthManagerState(
codeChallenge = pkce?.codeChallenge.orEmpty(),
idTokenVerificationIssuer = idTokenVerificationIssuer,
idTokenVerificationLeeway = idTokenVerificationLeeway,
customAuthorizeUrl = this.customAuthorizeUrl
)
return gson.toJson(json)
} finally {
Expand Down Expand Up @@ -103,6 +106,7 @@ internal data class OAuthManagerState(
),
idTokenVerificationIssuer = oauthManagerJson.idTokenVerificationIssuer,
idTokenVerificationLeeway = oauthManagerJson.idTokenVerificationLeeway,
customAuthorizeUrl = oauthManagerJson.customAuthorizeUrl
)
} finally {
parcel.recycle()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ public object WebAuthProvider {
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
private var federated: Boolean = false
private var launchAsTwa: Boolean = false
private var customLogoutUrl: String? = null

/**
* When using a Custom Tabs compatible Browser, apply these customization options.
Expand Down Expand Up @@ -215,6 +216,18 @@ public object WebAuthProvider {
return this
}

/**
* Specifies a custom Logout URL to use for this logout request, overriding the default
* generated from the Auth0 domain (account.logoutUrl).
*
* @param logoutUrl the custom logout URL.
* @return the current builder instance
*/
public fun withLogoutUrl(logoutUrl: String): LogoutBuilder {
this.customLogoutUrl = logoutUrl
return this
}

/**
* Request the user session to be cleared. When successful, the callback will get invoked.
* An error is raised if there are no browser applications installed in the device or if
Expand Down Expand Up @@ -248,7 +261,8 @@ public object WebAuthProvider {
returnToUrl!!,
ctOptions,
federated,
launchAsTwa
launchAsTwa,
customLogoutUrl
)
managerInstance = logoutManager
logoutManager.startLogout(context)
Expand Down Expand Up @@ -294,6 +308,7 @@ public object WebAuthProvider {
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
private var leeway: Int? = null
private var launchAsTwa: Boolean = false
private var customAuthorizeUrl: String? = null

/**
* Use a custom state in the requests
Expand Down Expand Up @@ -507,6 +522,18 @@ public object WebAuthProvider {
return this
}

/**
* Specifies a custom Authorize URL to use for this login request, overriding the default
* generated from the Auth0 domain (account.authorizeUrl).
*
* @param authorizeUrl the custom authorize URL.
* @return the current builder instance
*/
public fun withAuthorizeUrl(authorizeUrl: String): Builder {
this.customAuthorizeUrl = authorizeUrl
return this
}

@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
internal fun withPKCE(pkce: PKCE): Builder {
this.pkce = pkce
Expand Down Expand Up @@ -553,7 +580,8 @@ public object WebAuthProvider {
values[OAuthManager.KEY_ORGANIZATION] = organizationId
values[OAuthManager.KEY_INVITATION] = invitationId
}
val manager = OAuthManager(account, callback, values, ctOptions, launchAsTwa)
val manager = OAuthManager(account, callback, values, ctOptions, launchAsTwa,
customAuthorizeUrl)
manager.setHeaders(headers)
manager.setPKCE(pkce)
manager.setIdTokenVerificationLeeway(leeway)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public void setUp() {

@Test
public void shouldCallOnFailureWhenResumedWithCanceledResult() {
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false);
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false,null);
AuthorizeResult result = mock(AuthorizeResult.class);
when(result.isCanceled()).thenReturn(true);
manager.resume(result);
Expand All @@ -51,7 +51,7 @@ public void shouldCallOnFailureWhenResumedWithCanceledResult() {

@Test
public void shouldCallOnSuccessWhenResumedWithValidResult() {
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false);
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false,null);
AuthorizeResult result = mock(AuthorizeResult.class);
when(result.isCanceled()).thenReturn(false);
manager.resume(result);
Expand Down
127 changes: 127 additions & 0 deletions auth0/src/test/java/com/auth0/android/provider/OAuthManagerTest.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,67 @@
package com.auth0.android.provider;

import android.net.Uri;
import com.auth0.android.Auth0;
import com.auth0.android.authentication.AuthenticationException;
import com.auth0.android.callback.Callback;
import com.auth0.android.request.NetworkingClient;
import com.auth0.android.util.Auth0UserAgent;
import com.auth0.android.result.Credentials;

import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;
import org.robolectric.RobolectricTestRunner;

import java.lang.reflect.Method;
import java.util.Collections;
import java.util.Map;


@RunWith(RobolectricTestRunner.class)
public class OAuthManagerTest {

@Mock
private Auth0 mockAccount;
@Mock
private Callback<Credentials, AuthenticationException> mockCallback;
@Mock
private CustomTabsOptions mockCtOptions;
@Mock
private OAuthManagerState mockState;
@Mock
private PKCE mockPkce;
@Mock
private NetworkingClient mockNetworkingClient;
@Mock
private Auth0UserAgent mockUserAgent;

@Before
public void setUp() {
MockitoAnnotations.openMocks(this);
Mockito.when(mockAccount.getNetworkingClient()).thenReturn(mockNetworkingClient);
Mockito.when(mockAccount.getClientId()).thenReturn("test-client-id");
Mockito.when(mockAccount.getDomainUrl()).thenReturn("https://test.domain.com/");
Mockito.when(mockAccount.getAuth0UserAgent()).thenReturn(mockUserAgent);
Mockito.when(mockUserAgent.getValue()).thenReturn("test-user-agent/1.0");
Mockito.when(mockState.getAuth0()).thenReturn(mockAccount);
Mockito.when(mockState.getCtOptions()).thenReturn(mockCtOptions);
Mockito.when(mockState.getParameters()).thenReturn(Collections.emptyMap());
Mockito.when(mockState.getHeaders()).thenReturn(Collections.emptyMap());
Mockito.when(mockState.getPkce()).thenReturn(mockPkce);
Mockito.when(mockState.getIdTokenVerificationIssuer()).thenReturn("default-issuer");
Mockito.when(mockState.getIdTokenVerificationLeeway()).thenReturn(60);
}

@After
public void tearDown() {
}

@Test
public void shouldHaveValidState() {
OAuthManager.assertValidState("1234567890", "1234567890");
Expand All @@ -25,4 +76,80 @@ public void shouldHaveInvalidState() {
public void shouldHaveInvalidStateWhenOneIsNull() {
Assert.assertThrows(AuthenticationException.class, () -> OAuthManager.assertValidState("0987654321", null));
}

@Test
public void buildAuthorizeUriShouldUseDefaultUrlWhenCustomUrlIsNull() throws Exception {
final String defaultUrl = "https://default.auth0.com/authorize";
final Map<String, String> parameters = Collections.singletonMap("param1", "value1");
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
OAuthManager manager = new OAuthManager(mockAccount, mockCallback, parameters, mockCtOptions, false, null);
Uri resultUri = callBuildAuthorizeUri(manager);
Assert.assertNotNull(resultUri);
Assert.assertEquals("https", resultUri.getScheme());
Assert.assertEquals("default.auth0.com", resultUri.getHost());
Assert.assertEquals("/authorize", resultUri.getPath());
Assert.assertEquals("value1", resultUri.getQueryParameter("param1"));
Mockito.verify(mockAccount).getAuthorizeUrl();
}

@Test
public void buildAuthorizeUriShouldUseCustomUrlWhenProvided() throws Exception {
final String defaultUrl = "https://default.auth0.com/authorize";
final String customUrl = "https://custom.example.com/custom_auth";
final Map<String, String> parameters = Collections.singletonMap("param1", "value1");
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
OAuthManager manager = new OAuthManager(mockAccount, mockCallback, parameters, mockCtOptions, false, customUrl);
Uri resultUri = callBuildAuthorizeUri(manager);
Assert.assertNotNull(resultUri);
Assert.assertEquals("https", resultUri.getScheme());
Assert.assertEquals("custom.example.com", resultUri.getHost());
Assert.assertEquals("/custom_auth", resultUri.getPath());
Assert.assertEquals("value1", resultUri.getQueryParameter("param1"));
Mockito.verify(mockAccount, Mockito.never()).getAuthorizeUrl();
}

@Test
public void managerRestoredFromStateShouldUseRestoredCustomAuthorizeUrl() throws Exception {
final String restoredCustomUrl = "https://restored.com/custom_auth";
final String defaultUrl = "https://should-not-be-used.com/authorize";

Mockito.when(mockState.getCustomAuthorizeUrl()).thenReturn(restoredCustomUrl);
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);

OAuthManager restoredManager = new OAuthManager(
mockState.getAuth0(), mockCallback, mockState.getParameters(),
mockState.getCtOptions(), false, mockState.getCustomAuthorizeUrl()
);
Uri resultUri = callBuildAuthorizeUri(restoredManager);
Assert.assertNotNull(resultUri);
Assert.assertEquals("https", resultUri.getScheme());
Assert.assertEquals("restored.com", resultUri.getHost());
Assert.assertEquals("/custom_auth", resultUri.getPath());
Mockito.verify(mockAccount, Mockito.never()).getAuthorizeUrl();
}

@Test
public void managerRestoredFromStateShouldHandleNullCustomAuthorizeUrl() throws Exception {
final String defaultUrl = "https://default.auth0.com/authorize";

Mockito.when(mockState.getCustomAuthorizeUrl()).thenReturn(null);
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
OAuthManager restoredManager = new OAuthManager(
mockState.getAuth0(), mockCallback, mockState.getParameters(),
mockState.getCtOptions(), false, mockState.getCustomAuthorizeUrl()
);
Uri resultUri = callBuildAuthorizeUri(restoredManager);
Assert.assertNotNull(resultUri);
Assert.assertEquals("https", resultUri.getScheme());
Assert.assertEquals("default.auth0.com", resultUri.getHost());
Assert.assertEquals("/authorize", resultUri.getPath());
Mockito.verify(mockAccount).getAuthorizeUrl();
}

private Uri callBuildAuthorizeUri(OAuthManager instance) throws Exception {
Method method = OAuthManager.class.getDeclaredMethod("buildAuthorizeUri");
method.setAccessible(true);
return (Uri) method.invoke(instance);
}

}
Loading