Skip to content

Commit 7d0e21a

Browse files
authored
Merge branch 'dev' into pedroro/moveRestrictionManager
2 parents 94ffab2 + 1e36669 commit 7d0e21a

5 files changed

Lines changed: 195 additions & 3 deletions

File tree

common/src/main/java/com/microsoft/identity/common/internal/providers/oauth2/AuthorizationActivityFactory.kt

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
package com.microsoft.identity.common.internal.providers.oauth2
2424

2525
import android.content.Intent
26+
import android.os.Bundle
2627
import androidx.fragment.app.Fragment
2728
import com.microsoft.identity.common.adal.internal.AuthenticationConstants
2829
import com.microsoft.identity.common.internal.msafederation.getIdProviderExtraQueryParamForAuthorization
@@ -227,4 +228,26 @@ object AuthorizationActivityFactory {
227228
)
228229
return getAuthorizationActivityIntent(newAuthorizationActivityParameters)
229230
}
231+
232+
/**
233+
* OnaAuth method
234+
* Returns the correct authorization fragment for local (non-broker) authorization flows,
235+
* supplying a start bundle for the Fragment state.
236+
* Fragments include:
237+
* [WebViewAuthorizationFragment]
238+
* [BrowserAuthorizationFragment]
239+
* [CurrentTaskBrowserAuthorizationFragment]
240+
*
241+
* @param intent the intent to use to create the fragment.
242+
* @param bundle the bundle to add to the Fragment if it is an AuthorizationFragment.
243+
* @return returns an Fragment that's used as to authorize a token request.
244+
*/
245+
@JvmStatic
246+
fun getAuthorizationFragmentFromStartIntentWithState(intent: Intent, bundle: Bundle): Fragment {
247+
val fragment = getAuthorizationFragmentFromStartIntent(intent)
248+
if (fragment is AuthorizationFragment) {
249+
fragment.setInstanceState(bundle)
250+
}
251+
return fragment
252+
}
230253
}

common/src/main/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClient.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import androidx.annotation.NonNull;
3737
import androidx.annotation.Nullable;
3838
import androidx.annotation.RequiresApi;
39+
import androidx.annotation.VisibleForTesting;
3940
import androidx.lifecycle.ViewTreeLifecycleOwner;
4041

4142
import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
@@ -643,8 +644,13 @@ private void processCloudRedirectAndPrtHeader(@NonNull final WebView view, @NonN
643644
final SpanContext spanContext = getActivity() instanceof AuthorizationActivity ? ((AuthorizationActivity) getActivity()).getSpanContext() : null;
644645
final Span span = spanContext != null ?
645646
OTelUtility.createSpanFromParent(SpanName.ProcessCrossCloudRedirect.name(), spanContext) : OTelUtility.createSpan(SpanName.ProcessCrossCloudRedirect.name());
647+
final CrossCloudChallengeHandler crossCloudChallengeHandler = new CrossCloudChallengeHandler(view, mRequestHeaders, span);
648+
processCloudRedirectAndPrtHeaderInternal(url, crossCloudChallengeHandler, view, methodTag, span);
649+
}
650+
651+
@VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
652+
public void processCloudRedirectAndPrtHeaderInternal(@NonNull final String url, @NonNull final CrossCloudChallengeHandler crossCloudChallengeHandler, @NonNull final WebView view, @NonNull final String methodTag, @NonNull final Span span) {
646653
try (final Scope scope = SpanExtension.makeCurrentSpan(span)) {
647-
final CrossCloudChallengeHandler crossCloudChallengeHandler = new CrossCloudChallengeHandler(view, mRequestHeaders, span);
648654
crossCloudChallengeHandler.processChallenge(url);
649655
span.setStatus(StatusCode.OK);
650656
} catch (final Exception e) {

common/src/main/java/com/microsoft/identity/common/internal/ui/webview/challengehandlers/CrossCloudChallengeHandler.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ class CrossCloudChallengeHandler(
5050
}
5151

5252
// Updates the headers by attaching a refresh token credential header.
53-
private fun modifyHeadersWithRefreshTokenCredential(
53+
// Making it accessible for testing.
54+
fun modifyHeadersWithRefreshTokenCredential(
5455
url: String,
5556
) {
5657
val methodTag = "$TAG:modifyHeadersWithRefreshTokenCredential"

common/src/test/java/com/microsoft/identity/common/internal/ui/webview/AzureActiveDirectoryWebViewClientTest.java

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,10 +30,14 @@
3030
import androidx.test.core.app.ApplicationProvider;
3131

3232
import com.microsoft.identity.common.adal.internal.AuthenticationConstants;
33+
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.CrossCloudChallengeHandler;
34+
import com.microsoft.identity.common.java.exception.ClientException;
35+
import com.microsoft.identity.common.java.providers.microsoft.azureactivedirectory.AzureActiveDirectory;
3336
import com.microsoft.identity.common.internal.ui.webview.challengehandlers.SwitchBrowserRequestHandler;
3437
import com.microsoft.identity.common.java.ui.webview.authorization.IAuthorizationCompletionCallback;
3538
import com.microsoft.identity.common.java.providers.RawAuthorizationResult;
3639

40+
import org.junit.Assert;
3741
import org.junit.Before;
3842
import org.junit.Test;
3943
import org.junit.runner.RunWith;
@@ -49,6 +53,8 @@
4953

5054
import java.util.HashMap;
5155

56+
import io.opentelemetry.api.trace.Span;
57+
5258

5359
@RunWith(RobolectricTestRunner.class)
5460
public class AzureActiveDirectoryWebViewClientTest {
@@ -82,9 +88,11 @@ public class AzureActiveDirectoryWebViewClientTest {
8288
private static final String TEST_MSA_HEADER_FORWARDING_NEGATIVE_URL = "https://login.blah.com/oauth20_authorize.srf";
8389

8490
private static final String TEST_NONCE_REDIRECT_URL = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize?&sso_nonce=ABCD";
91+
private static final String TEST_CROSS_CLOUD_REDIRECT_URL = "https://login.microsoftonline.us/organizations/oAuth2/v2.0/authorize?x=10";
92+
private static final String TEST_PUBLIC_CLOUD_REDIRECT_URL = "https://login.microsoftonline.com/organizations/oAuth2/v2.0/authorize?x=10";
8593

8694
@Before
87-
public void setup() {
95+
public void setup() throws ClientException {
8896
mContext = ApplicationProvider.getApplicationContext();
8997
mMockWebView = new WebView(mContext);
9098
mActivity = Robolectric.buildActivity(Activity.class).get();
@@ -112,6 +120,10 @@ public void onPageLoaded(final String url) {
112120
HashMap<String, String> dummyHeaders = new HashMap<>();
113121
dummyHeaders.put("key", "value");
114122
mWebViewClient.setRequestHeaders(dummyHeaders);
123+
mWebViewClient.setRequestUrl(TEST_PUBLIC_CLOUD_REDIRECT_URL);
124+
if (!AzureActiveDirectory.isInitialized()) {
125+
AzureActiveDirectory.performCloudDiscovery();
126+
}
115127
}
116128

117129
@Test(expected = IllegalArgumentException.class)
@@ -193,4 +205,33 @@ public void testUrlOverrideHandlesHeaderForwardingRequiredUrl() {
193205
public void testUrlOverrideHandlesNonceRedirectUrl() {
194206
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_NONCE_REDIRECT_URL));
195207
}
208+
209+
@Test
210+
public void testUrlOverrideHandlesCrossCloudRedirectUrl() {
211+
assertTrue(mWebViewClient.shouldOverrideUrlLoading(mMockWebView, TEST_CROSS_CLOUD_REDIRECT_URL));
212+
}
213+
214+
@Test
215+
public void testProcessCloudRedirectAndPrtHeaderInternalSuccess() {
216+
CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class);
217+
try {
218+
mWebViewClient.processCloudRedirectAndPrtHeaderInternal(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mMockWebView, "methodTag", Span.current());
219+
} catch (Exception e) {
220+
Assert.fail("Unexpected exception occured " + e);
221+
}
222+
}
223+
224+
@Test
225+
public void testProcessCloudRedirectAndPrtHeaderInternalException() {
226+
CrossCloudChallengeHandler mockCrossCloudChallengeHandler = Mockito.mock(CrossCloudChallengeHandler.class);
227+
WebView mockWebView = Mockito.mock(WebView.class);
228+
Mockito.doThrow(new RuntimeException("Test Exception")).when(mockCrossCloudChallengeHandler).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL);
229+
try {
230+
mWebViewClient.processCloudRedirectAndPrtHeaderInternal(TEST_CROSS_CLOUD_REDIRECT_URL, mockCrossCloudChallengeHandler, mockWebView, "methodTag", Span.current());
231+
Mockito.verify(mockCrossCloudChallengeHandler, Mockito.times(1)).processChallenge(TEST_CROSS_CLOUD_REDIRECT_URL);
232+
Mockito.verify(mockWebView).loadUrl(Mockito.anyString(), Mockito.any());
233+
} catch (Exception e) {
234+
Assert.fail("Failure is not expected. We should have caught the exception and ignored it. " + e);
235+
}
236+
}
196237
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// All rights reserved.
2+
//
3+
// This code is licensed under the MIT License.
4+
//
5+
// Permission is hereby granted, free of charge, to any person obtaining a copy
6+
// of this software and associated documentation files(the "Software"), to deal
7+
// in the Software without restriction, including without limitation the rights
8+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
9+
// copies of the Software, and to permit persons to whom the Software is
10+
// furnished to do so, subject to the following conditions :
11+
//
12+
// The above copyright notice and this permission notice shall be included in
13+
// all copies or substantial portions of the Software.
14+
//
15+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
// THE SOFTWARE.
22+
package com.microsoft.identity.common.internal.ui.webview.challengehandlers
23+
24+
import org.junit.runner.RunWith
25+
import org.robolectric.RobolectricTestRunner
26+
import android.webkit.WebView
27+
import com.microsoft.identity.common.java.broker.CommonRefreshTokenCredentialProvider
28+
import com.microsoft.identity.common.java.opentelemetry.AttributeName
29+
import io.mockk.every
30+
import io.mockk.mockkObject
31+
import io.mockk.unmockkAll
32+
import io.opentelemetry.api.trace.Span
33+
import org.junit.Before
34+
import org.junit.Test
35+
import org.mockito.Mockito.*
36+
37+
@RunWith(RobolectricTestRunner::class)
38+
class CrossCloudChallengeHandlerTest {
39+
40+
private lateinit var webView: WebView
41+
private lateinit var headers: HashMap<String, String>
42+
private lateinit var span: Span
43+
private lateinit var crossCloudChallengeHandler: CrossCloudChallengeHandler
44+
45+
@Before
46+
fun setUp() {
47+
webView = mock(WebView::class.java)
48+
headers = HashMap()
49+
span = mock(Span::class.java)
50+
crossCloudChallengeHandler = CrossCloudChallengeHandler(webView, headers, span)
51+
}
52+
53+
@Test
54+
fun `testProcessChallenge success`() {
55+
val testUrl = "https://example.com?login_hint=testuser"
56+
crossCloudChallengeHandler.processChallenge(testUrl)
57+
verify(webView).loadUrl(eq(testUrl), eq(headers))
58+
}
59+
60+
@Test
61+
fun `testProcessChallenge when exception is thrown`() {
62+
val testUrl = "https://example.com?login_hint=testuser"
63+
64+
mockkObject(CommonRefreshTokenCredentialProvider)
65+
every {
66+
CommonRefreshTokenCredentialProvider.getRefreshTokenCredential(
67+
testUrl,
68+
"testuser"
69+
)
70+
} throws Exception()
71+
72+
try {
73+
crossCloudChallengeHandler.processChallenge(testUrl)
74+
} catch (e: Exception) {
75+
verify(webView, never()).loadUrl(eq(testUrl), eq(headers))
76+
}
77+
}
78+
79+
@Test
80+
fun `modifyHeadersWithRefreshTokenCredential should update headers when prt is available`() {
81+
val url = "https://login.microsoftonline.com?login_hint=testuser"
82+
val username = "testuser"
83+
val refreshTokenCredential = "refreshTokenCredential"
84+
85+
mockkObject(CommonRefreshTokenCredentialProvider)
86+
every {
87+
CommonRefreshTokenCredentialProvider.getRefreshTokenCredential(
88+
url,
89+
username
90+
)
91+
} returns refreshTokenCredential
92+
93+
// Call the method
94+
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
95+
verify(span).setAttribute(
96+
AttributeName.is_new_refresh_token_cred_header_attached.name,
97+
true
98+
)
99+
unmockkAll()
100+
}
101+
102+
@Test
103+
fun `modifyHeadersWithRefreshTokenCredential should not update headers when login_hint is missing`() {
104+
val url = "https://login.microsoftonline.com"
105+
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
106+
verify(span, never()).setAttribute(
107+
AttributeName.is_new_refresh_token_cred_header_attached.name,
108+
true
109+
)
110+
}
111+
112+
@Test
113+
fun `modifyHeadersWithRefreshTokenCredential null refresh token credential`() {
114+
val url = "https://login.microsoftonline.com?login_hint=testuser"
115+
crossCloudChallengeHandler.modifyHeadersWithRefreshTokenCredential(url)
116+
verify(span, never()).setAttribute(
117+
AttributeName.is_new_refresh_token_cred_header_attached.name,
118+
true
119+
)
120+
}
121+
}

0 commit comments

Comments
 (0)