Skip to content

Commit 16c3634

Browse files
Allow updating the logout and authorize url (#822)
Co-authored-by: Prince Mathew <[email protected]>
1 parent 7c2602b commit 16c3634

File tree

7 files changed

+227
-9
lines changed

7 files changed

+227
-9
lines changed

auth0/src/main/java/com/auth0/android/provider/LogoutManager.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ internal class LogoutManager(
1515
ctOptions: CustomTabsOptions,
1616
federated: Boolean = false,
1717
private val launchAsTwa: Boolean = false,
18+
private val customLogoutUrl: String? = null
1819
) : ResumableManager() {
1920
private val parameters: MutableMap<String, String>
2021
private val ctOptions: CustomTabsOptions
@@ -42,7 +43,8 @@ internal class LogoutManager(
4243
}
4344

4445
private fun buildLogoutUri(): Uri {
45-
val logoutUri = Uri.parse(account.logoutUrl)
46+
val urlToUse = customLogoutUrl ?: account.logoutUrl
47+
val logoutUri = Uri.parse(urlToUse)
4648
val builder = logoutUri.buildUpon()
4749
for ((key, value) in parameters) {
4850
builder.appendQueryParameter(key, value)

auth0/src/main/java/com/auth0/android/provider/OAuthManager.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ internal class OAuthManager(
2525
parameters: Map<String, String>,
2626
ctOptions: CustomTabsOptions,
2727
private val launchAsTwa: Boolean = false,
28+
private val customAuthorizeUrl: String? = null
2829
) : ResumableManager() {
2930
private val parameters: MutableMap<String, String>
3031
private val headers: MutableMap<String, String>
@@ -197,6 +198,7 @@ internal class OAuthManager(
197198
auth0 = account,
198199
idTokenVerificationIssuer = idTokenVerificationIssuer,
199200
idTokenVerificationLeeway = idTokenVerificationLeeway,
201+
customAuthorizeUrl = this.customAuthorizeUrl
200202
)
201203
}
202204

@@ -235,7 +237,8 @@ internal class OAuthManager(
235237
}
236238

237239
private fun buildAuthorizeUri(): Uri {
238-
val authorizeUri = Uri.parse(account.authorizeUrl)
240+
val urlToUse = customAuthorizeUrl ?: account.authorizeUrl
241+
val authorizeUri = Uri.parse(urlToUse)
239242
val builder = authorizeUri.buildUpon()
240243
for ((key, value) in parameters) {
241244
builder.appendQueryParameter(key, value)
@@ -357,7 +360,8 @@ internal fun OAuthManager.Companion.fromState(
357360
account = state.auth0,
358361
ctOptions = state.ctOptions,
359362
parameters = state.parameters,
360-
callback = callback
363+
callback = callback,
364+
customAuthorizeUrl = state.customAuthorizeUrl
361365
).apply {
362366
setHeaders(
363367
state.headers

auth0/src/main/java/com/auth0/android/provider/OAuthManagerState.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ internal data class OAuthManagerState(
1717
val ctOptions: CustomTabsOptions,
1818
val pkce: PKCE?,
1919
val idTokenVerificationLeeway: Int?,
20-
val idTokenVerificationIssuer: String?
20+
val idTokenVerificationIssuer: String?,
21+
val customAuthorizeUrl: String? = null
2122
) {
2223

2324
private class OAuthManagerJson(
@@ -32,7 +33,8 @@ internal data class OAuthManagerState(
3233
val codeChallenge: String,
3334
val codeVerifier: String,
3435
val idTokenVerificationLeeway: Int?,
35-
val idTokenVerificationIssuer: String?
36+
val idTokenVerificationIssuer: String?,
37+
val customAuthorizeUrl: String? = null
3638
)
3739

3840
fun serializeToJson(
@@ -56,6 +58,7 @@ internal data class OAuthManagerState(
5658
codeChallenge = pkce?.codeChallenge.orEmpty(),
5759
idTokenVerificationIssuer = idTokenVerificationIssuer,
5860
idTokenVerificationLeeway = idTokenVerificationLeeway,
61+
customAuthorizeUrl = this.customAuthorizeUrl
5962
)
6063
return gson.toJson(json)
6164
} finally {
@@ -103,6 +106,7 @@ internal data class OAuthManagerState(
103106
),
104107
idTokenVerificationIssuer = oauthManagerJson.idTokenVerificationIssuer,
105108
idTokenVerificationLeeway = oauthManagerJson.idTokenVerificationLeeway,
109+
customAuthorizeUrl = oauthManagerJson.customAuthorizeUrl
106110
)
107111
} finally {
108112
parcel.recycle()

auth0/src/main/java/com/auth0/android/provider/WebAuthProvider.kt

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,7 @@ public object WebAuthProvider {
148148
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
149149
private var federated: Boolean = false
150150
private var launchAsTwa: Boolean = false
151+
private var customLogoutUrl: String? = null
151152

152153
/**
153154
* When using a Custom Tabs compatible Browser, apply these customization options.
@@ -215,6 +216,18 @@ public object WebAuthProvider {
215216
return this
216217
}
217218

219+
/**
220+
* Specifies a custom Logout URL to use for this logout request, overriding the default
221+
* generated from the Auth0 domain (account.logoutUrl).
222+
*
223+
* @param logoutUrl the custom logout URL.
224+
* @return the current builder instance
225+
*/
226+
public fun withLogoutUrl(logoutUrl: String): LogoutBuilder {
227+
this.customLogoutUrl = logoutUrl
228+
return this
229+
}
230+
218231
/**
219232
* Request the user session to be cleared. When successful, the callback will get invoked.
220233
* An error is raised if there are no browser applications installed in the device or if
@@ -248,7 +261,8 @@ public object WebAuthProvider {
248261
returnToUrl!!,
249262
ctOptions,
250263
federated,
251-
launchAsTwa
264+
launchAsTwa,
265+
customLogoutUrl
252266
)
253267
managerInstance = logoutManager
254268
logoutManager.startLogout(context)
@@ -294,6 +308,7 @@ public object WebAuthProvider {
294308
private var ctOptions: CustomTabsOptions = CustomTabsOptions.newBuilder().build()
295309
private var leeway: Int? = null
296310
private var launchAsTwa: Boolean = false
311+
private var customAuthorizeUrl: String? = null
297312

298313
/**
299314
* Use a custom state in the requests
@@ -507,6 +522,18 @@ public object WebAuthProvider {
507522
return this
508523
}
509524

525+
/**
526+
* Specifies a custom Authorize URL to use for this login request, overriding the default
527+
* generated from the Auth0 domain (account.authorizeUrl).
528+
*
529+
* @param authorizeUrl the custom authorize URL.
530+
* @return the current builder instance
531+
*/
532+
public fun withAuthorizeUrl(authorizeUrl: String): Builder {
533+
this.customAuthorizeUrl = authorizeUrl
534+
return this
535+
}
536+
510537
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
511538
internal fun withPKCE(pkce: PKCE): Builder {
512539
this.pkce = pkce
@@ -553,7 +580,8 @@ public object WebAuthProvider {
553580
values[OAuthManager.KEY_ORGANIZATION] = organizationId
554581
values[OAuthManager.KEY_INVITATION] = invitationId
555582
}
556-
val manager = OAuthManager(account, callback, values, ctOptions, launchAsTwa)
583+
val manager = OAuthManager(account, callback, values, ctOptions, launchAsTwa,
584+
customAuthorizeUrl)
557585
manager.setHeaders(headers)
558586
manager.setPKCE(pkce)
559587
manager.setIdTokenVerificationLeeway(leeway)

auth0/src/test/java/com/auth0/android/provider/LogoutManagerTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ public void setUp() {
3838

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

5252
@Test
5353
public void shouldCallOnSuccessWhenResumedWithValidResult() {
54-
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false);
54+
LogoutManager manager = new LogoutManager(account, callback, "https://auth0.com/android/my.app.name/callback", customTabsOptions, false, false,null);
5555
AuthorizeResult result = mock(AuthorizeResult.class);
5656
when(result.isCanceled()).thenReturn(false);
5757
manager.resume(result);

auth0/src/test/java/com/auth0/android/provider/OAuthManagerTest.java

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,67 @@
11
package com.auth0.android.provider;
22

3+
import android.net.Uri;
4+
import com.auth0.android.Auth0;
35
import com.auth0.android.authentication.AuthenticationException;
6+
import com.auth0.android.callback.Callback;
7+
import com.auth0.android.request.NetworkingClient;
8+
import com.auth0.android.util.Auth0UserAgent;
9+
import com.auth0.android.result.Credentials;
410

11+
import org.junit.After;
512
import org.junit.Assert;
13+
import org.junit.Before;
614
import org.junit.Test;
715
import org.junit.runner.RunWith;
16+
import org.mockito.Mock;
17+
import org.mockito.Mockito;
18+
import org.mockito.MockitoAnnotations;
819
import org.robolectric.RobolectricTestRunner;
920

21+
import java.lang.reflect.Method;
22+
import java.util.Collections;
23+
import java.util.Map;
24+
1025

1126
@RunWith(RobolectricTestRunner.class)
1227
public class OAuthManagerTest {
1328

29+
@Mock
30+
private Auth0 mockAccount;
31+
@Mock
32+
private Callback<Credentials, AuthenticationException> mockCallback;
33+
@Mock
34+
private CustomTabsOptions mockCtOptions;
35+
@Mock
36+
private OAuthManagerState mockState;
37+
@Mock
38+
private PKCE mockPkce;
39+
@Mock
40+
private NetworkingClient mockNetworkingClient;
41+
@Mock
42+
private Auth0UserAgent mockUserAgent;
43+
44+
@Before
45+
public void setUp() {
46+
MockitoAnnotations.openMocks(this);
47+
Mockito.when(mockAccount.getNetworkingClient()).thenReturn(mockNetworkingClient);
48+
Mockito.when(mockAccount.getClientId()).thenReturn("test-client-id");
49+
Mockito.when(mockAccount.getDomainUrl()).thenReturn("https://test.domain.com/");
50+
Mockito.when(mockAccount.getAuth0UserAgent()).thenReturn(mockUserAgent);
51+
Mockito.when(mockUserAgent.getValue()).thenReturn("test-user-agent/1.0");
52+
Mockito.when(mockState.getAuth0()).thenReturn(mockAccount);
53+
Mockito.when(mockState.getCtOptions()).thenReturn(mockCtOptions);
54+
Mockito.when(mockState.getParameters()).thenReturn(Collections.emptyMap());
55+
Mockito.when(mockState.getHeaders()).thenReturn(Collections.emptyMap());
56+
Mockito.when(mockState.getPkce()).thenReturn(mockPkce);
57+
Mockito.when(mockState.getIdTokenVerificationIssuer()).thenReturn("default-issuer");
58+
Mockito.when(mockState.getIdTokenVerificationLeeway()).thenReturn(60);
59+
}
60+
61+
@After
62+
public void tearDown() {
63+
}
64+
1465
@Test
1566
public void shouldHaveValidState() {
1667
OAuthManager.assertValidState("1234567890", "1234567890");
@@ -25,4 +76,80 @@ public void shouldHaveInvalidState() {
2576
public void shouldHaveInvalidStateWhenOneIsNull() {
2677
Assert.assertThrows(AuthenticationException.class, () -> OAuthManager.assertValidState("0987654321", null));
2778
}
79+
80+
@Test
81+
public void buildAuthorizeUriShouldUseDefaultUrlWhenCustomUrlIsNull() throws Exception {
82+
final String defaultUrl = "https://default.auth0.com/authorize";
83+
final Map<String, String> parameters = Collections.singletonMap("param1", "value1");
84+
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
85+
OAuthManager manager = new OAuthManager(mockAccount, mockCallback, parameters, mockCtOptions, false, null);
86+
Uri resultUri = callBuildAuthorizeUri(manager);
87+
Assert.assertNotNull(resultUri);
88+
Assert.assertEquals("https", resultUri.getScheme());
89+
Assert.assertEquals("default.auth0.com", resultUri.getHost());
90+
Assert.assertEquals("/authorize", resultUri.getPath());
91+
Assert.assertEquals("value1", resultUri.getQueryParameter("param1"));
92+
Mockito.verify(mockAccount).getAuthorizeUrl();
93+
}
94+
95+
@Test
96+
public void buildAuthorizeUriShouldUseCustomUrlWhenProvided() throws Exception {
97+
final String defaultUrl = "https://default.auth0.com/authorize";
98+
final String customUrl = "https://custom.example.com/custom_auth";
99+
final Map<String, String> parameters = Collections.singletonMap("param1", "value1");
100+
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
101+
OAuthManager manager = new OAuthManager(mockAccount, mockCallback, parameters, mockCtOptions, false, customUrl);
102+
Uri resultUri = callBuildAuthorizeUri(manager);
103+
Assert.assertNotNull(resultUri);
104+
Assert.assertEquals("https", resultUri.getScheme());
105+
Assert.assertEquals("custom.example.com", resultUri.getHost());
106+
Assert.assertEquals("/custom_auth", resultUri.getPath());
107+
Assert.assertEquals("value1", resultUri.getQueryParameter("param1"));
108+
Mockito.verify(mockAccount, Mockito.never()).getAuthorizeUrl();
109+
}
110+
111+
@Test
112+
public void managerRestoredFromStateShouldUseRestoredCustomAuthorizeUrl() throws Exception {
113+
final String restoredCustomUrl = "https://restored.com/custom_auth";
114+
final String defaultUrl = "https://should-not-be-used.com/authorize";
115+
116+
Mockito.when(mockState.getCustomAuthorizeUrl()).thenReturn(restoredCustomUrl);
117+
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
118+
119+
OAuthManager restoredManager = new OAuthManager(
120+
mockState.getAuth0(), mockCallback, mockState.getParameters(),
121+
mockState.getCtOptions(), false, mockState.getCustomAuthorizeUrl()
122+
);
123+
Uri resultUri = callBuildAuthorizeUri(restoredManager);
124+
Assert.assertNotNull(resultUri);
125+
Assert.assertEquals("https", resultUri.getScheme());
126+
Assert.assertEquals("restored.com", resultUri.getHost());
127+
Assert.assertEquals("/custom_auth", resultUri.getPath());
128+
Mockito.verify(mockAccount, Mockito.never()).getAuthorizeUrl();
129+
}
130+
131+
@Test
132+
public void managerRestoredFromStateShouldHandleNullCustomAuthorizeUrl() throws Exception {
133+
final String defaultUrl = "https://default.auth0.com/authorize";
134+
135+
Mockito.when(mockState.getCustomAuthorizeUrl()).thenReturn(null);
136+
Mockito.when(mockAccount.getAuthorizeUrl()).thenReturn(defaultUrl);
137+
OAuthManager restoredManager = new OAuthManager(
138+
mockState.getAuth0(), mockCallback, mockState.getParameters(),
139+
mockState.getCtOptions(), false, mockState.getCustomAuthorizeUrl()
140+
);
141+
Uri resultUri = callBuildAuthorizeUri(restoredManager);
142+
Assert.assertNotNull(resultUri);
143+
Assert.assertEquals("https", resultUri.getScheme());
144+
Assert.assertEquals("default.auth0.com", resultUri.getHost());
145+
Assert.assertEquals("/authorize", resultUri.getPath());
146+
Mockito.verify(mockAccount).getAuthorizeUrl();
147+
}
148+
149+
private Uri callBuildAuthorizeUri(OAuthManager instance) throws Exception {
150+
Method method = OAuthManager.class.getDeclaredMethod("buildAuthorizeUri");
151+
method.setAccessible(true);
152+
return (Uri) method.invoke(instance);
153+
}
154+
28155
}

0 commit comments

Comments
 (0)