Skip to content

Commit 73483c8

Browse files
authored
Native Auth Backward Compatibility feature, Fixes AB#3287604 (#2669)
Related msal PR: AzureAD/microsoft-authentication-library-for-android#2305 Fixes [AB#{3287604}](https://dev.azure.com/IdentityDivision/Engineering/_workitems/edit/3287604) Native Auth Backward Compatibility feature includes: - [Add capabilities parameter to native auth public client application initialization, Fixes AB#3287412, Closed AB#3287412](ca1ed52) - [Add capabilities parameter to the flow start operations, Fixes AB#3288182, Closed AB#3288182](1a93a53) - [Handle redirect across all endpoints and return the redirect reason to the developers, Closed AB#3288191](0220406) The main addition to this PR is the testing section
1 parent f37b41d commit 73483c8

File tree

53 files changed

+1517
-294
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+1517
-294
lines changed

changelog.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ vNext
22
----------
33
- [MINOR] changes accommodating the WrappedKeyAlgoIdentifier module (#2667)
44
- [MINOR] Fixing the sign in screens when edge to edge is enabled (#2665)
5+
- [MINOR] Native auth: Make native auth MFA feature more backward compatible(#2669)
56

67
Version 21.2.0
78
----------

common/src/main/java/com/microsoft/identity/common/nativeauth/internal/controllers/NativeAuthMsalController.kt

Lines changed: 101 additions & 23 deletions
Large diffs are not rendered by default.
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
// Copyright (c) Microsoft Corporation.
2+
// All rights reserved.
3+
//
4+
// This code is licensed under the MIT License.
5+
//
6+
// Permission is hereby granted, free of charge, to any person obtaining a copy
7+
// of this software and associated documentation files(the "Software"), to deal
8+
// in the Software without restriction, including without limitation the rights
9+
// to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
10+
// copies of the Software, and to permit persons to whom the Software is
11+
// furnished to do so, subject to the following conditions :
12+
//
13+
// The above copyright notice and this permission notice shall be included in
14+
// all copies or substantial portions of the Software.
15+
//
16+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
// THE SOFTWARE.
23+
package com.microsoft.identity.common.internal.providers.microsoft.nativeauth.integration
24+
25+
import android.os.Build
26+
import com.microsoft.identity.common.nativeauth.ApiConstants
27+
import com.microsoft.identity.common.java.interfaces.PlatformComponents
28+
import com.microsoft.identity.common.java.logging.DiagnosticContext
29+
import com.microsoft.identity.common.java.nativeauth.commands.parameters.JITChallengeAuthMethodCommandParameters
30+
import com.microsoft.identity.common.java.nativeauth.commands.parameters.JITContinueCommandParameters
31+
import com.microsoft.identity.common.java.nativeauth.commands.parameters.JITIntrospectCommandParameters
32+
import com.microsoft.identity.common.java.net.UrlConnectionHttpClient
33+
import com.microsoft.identity.common.java.nativeauth.providers.NativeAuthOAuth2Configuration
34+
import com.microsoft.identity.common.java.nativeauth.providers.NativeAuthOAuth2Strategy
35+
import com.microsoft.identity.common.java.nativeauth.providers.NativeAuthRequestProvider
36+
import com.microsoft.identity.common.java.nativeauth.providers.NativeAuthResponseHandler
37+
import com.microsoft.identity.common.java.nativeauth.providers.interactors.JITInteractor
38+
import com.microsoft.identity.common.java.nativeauth.providers.interactors.ResetPasswordInteractor
39+
import com.microsoft.identity.common.java.nativeauth.providers.interactors.SignInInteractor
40+
import com.microsoft.identity.common.java.nativeauth.providers.interactors.SignUpInteractor
41+
import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITChallengeApiResult
42+
import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITContinueApiResult
43+
import com.microsoft.identity.common.java.nativeauth.providers.responses.jit.JITIntrospectApiResult
44+
import com.microsoft.identity.common.java.providers.oauth2.OAuth2StrategyParameters
45+
import com.microsoft.identity.common.nativeauth.MockApiEndpoint
46+
import com.microsoft.identity.common.nativeauth.MockApiResponseType
47+
import com.microsoft.identity.common.nativeauth.MockApiUtils
48+
import org.junit.Assert
49+
import org.junit.Before
50+
import org.junit.Test
51+
import org.junit.runner.RunWith
52+
import org.mockito.kotlin.mock
53+
import org.mockito.kotlin.whenever
54+
import org.powermock.core.classloader.annotations.PowerMockIgnore
55+
import org.powermock.core.classloader.annotations.PrepareForTest
56+
import org.robolectric.RobolectricTestRunner
57+
import org.robolectric.annotation.Config
58+
import java.util.UUID
59+
60+
/**
61+
* These are integration tests using mocked API responses. This class
62+
* covers all sign up endpoints.
63+
* These tests run on the mock API, see: $(MOCK_API_URL) in the variable of the pipeline.
64+
*/
65+
@RunWith(
66+
RobolectricTestRunner::class
67+
)
68+
@PowerMockIgnore("javax.net.ssl.*")
69+
@PrepareForTest(DiagnosticContext::class)
70+
@Config(sdk = [Build.VERSION_CODES.O_MR1])
71+
class JITOAuthStrategyTest {
72+
73+
private val CLIENT_ID = "079af063-4ea7-4dcd-91ff-2b24f54621ea"
74+
private val CHALLENGE_TYPE = "oob password redirect"
75+
private val CAPABILITIES = "mfa_required registration_required"
76+
private val CONTINUATION_TOKEN = "12345"
77+
private val VERIFICATION_CONTACT = "[email protected]"
78+
private val CHALLENGE_CHANNEL = "email"
79+
private val AUTH_METHOD_CHALLENGE_TYPE = "email"
80+
81+
private val OOB_CODE = "1234"
82+
private val GRANT_TYPE = "oob"
83+
84+
private val mockConfig = mock<NativeAuthOAuth2Configuration>()
85+
private val mockStrategyParams = mock<OAuth2StrategyParameters>()
86+
87+
private lateinit var nativeAuthOAuth2Strategy: NativeAuthOAuth2Strategy
88+
89+
@Before
90+
fun setup() {
91+
whenever(mockConfig.clientId).thenReturn(CLIENT_ID)
92+
whenever(mockConfig.tokenEndpoint).thenReturn(ApiConstants.MockApi.tokenEndpoint)
93+
whenever(mockConfig.getSignUpStartEndpoint()).thenReturn(ApiConstants.MockApi.signUpStartRequestUrl)
94+
whenever(mockConfig.getSignUpChallengeEndpoint()).thenReturn(ApiConstants.MockApi.signUpChallengeRequestUrl)
95+
whenever(mockConfig.getSignUpContinueEndpoint()).thenReturn(ApiConstants.MockApi.signUpContinueRequestUrl)
96+
whenever(mockConfig.getSignInInitiateEndpoint()).thenReturn(ApiConstants.MockApi.signInInitiateRequestUrl)
97+
whenever(mockConfig.getSignInChallengeEndpoint()).thenReturn(ApiConstants.MockApi.signInChallengeRequestUrl)
98+
whenever(mockConfig.getSignInIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.signInIntrospectRequestUrl)
99+
whenever(mockConfig.getSignInTokenEndpoint()).thenReturn(ApiConstants.MockApi.signInTokenRequestUrl)
100+
whenever(mockConfig.getResetPasswordStartEndpoint()).thenReturn(ApiConstants.MockApi.ssprStartRequestUrl)
101+
whenever(mockConfig.getResetPasswordChallengeEndpoint()).thenReturn(ApiConstants.MockApi.ssprChallengeRequestUrl)
102+
whenever(mockConfig.getResetPasswordContinueEndpoint()).thenReturn(ApiConstants.MockApi.ssprContinueRequestUrl)
103+
whenever(mockConfig.getResetPasswordSubmitEndpoint()).thenReturn(ApiConstants.MockApi.ssprSubmitRequestUrl)
104+
whenever(mockConfig.getResetPasswordPollCompletionEndpoint()).thenReturn(ApiConstants.MockApi.ssprPollCompletionRequestUrl)
105+
whenever(mockConfig.getJITIntrospectEndpoint()).thenReturn(ApiConstants.MockApi.jitIntrospectRequestUrl)
106+
whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl)
107+
whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl)
108+
whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE)
109+
whenever(mockConfig.capabilities).thenReturn(CAPABILITIES)
110+
111+
nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy(
112+
config = mockConfig,
113+
strategyParameters = mockStrategyParams,
114+
signUpInteractor = SignUpInteractor(
115+
httpClient = UrlConnectionHttpClient.getDefaultInstance(),
116+
nativeAuthRequestProvider = NativeAuthRequestProvider(
117+
mockConfig
118+
),
119+
nativeAuthResponseHandler = NativeAuthResponseHandler()
120+
),
121+
signInInteractor = SignInInteractor(
122+
httpClient = UrlConnectionHttpClient.getDefaultInstance(),
123+
nativeAuthRequestProvider = NativeAuthRequestProvider(
124+
mockConfig
125+
),
126+
nativeAuthResponseHandler = NativeAuthResponseHandler()
127+
),
128+
resetPasswordInteractor = ResetPasswordInteractor(
129+
httpClient = UrlConnectionHttpClient.getDefaultInstance(),
130+
nativeAuthRequestProvider = NativeAuthRequestProvider(mockConfig),
131+
nativeAuthResponseHandler = NativeAuthResponseHandler()
132+
),
133+
jitInteractor = JITInteractor(
134+
httpClient = UrlConnectionHttpClient.getDefaultInstance(),
135+
nativeAuthRequestProvider = NativeAuthRequestProvider(config = mockConfig),
136+
nativeAuthResponseHandler = NativeAuthResponseHandler()
137+
)
138+
)
139+
}
140+
141+
@Test
142+
fun testPerformJITIntrospectRedirect() {
143+
val correlationId = UUID.randomUUID().toString()
144+
145+
MockApiUtils.configureMockApi(
146+
endpointType = MockApiEndpoint.JITIntrospect,
147+
correlationId = correlationId,
148+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
149+
)
150+
151+
val parameters = JITIntrospectCommandParameters.builder()
152+
.platformComponents(mock<PlatformComponents>())
153+
.continuationToken(CONTINUATION_TOKEN)
154+
.correlationId(correlationId)
155+
.build()
156+
157+
val jitIntrospectResult = nativeAuthOAuth2Strategy.performJITIntrospectRequest(
158+
parameters = parameters
159+
)
160+
161+
Assert.assertTrue(jitIntrospectResult is JITIntrospectApiResult.Redirect)
162+
Assert.assertNotNull((jitIntrospectResult as JITIntrospectApiResult.Redirect).redirectReason)
163+
}
164+
165+
@Test
166+
fun testPerformJITChallengeRedirect() {
167+
val correlationId = UUID.randomUUID().toString()
168+
169+
MockApiUtils.configureMockApi(
170+
endpointType = MockApiEndpoint.JITChallenge,
171+
correlationId = correlationId,
172+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
173+
)
174+
175+
val parameters = JITChallengeAuthMethodCommandParameters.builder()
176+
.platformComponents(mock<PlatformComponents>())
177+
.continuationToken(CONTINUATION_TOKEN)
178+
.verificationContact(VERIFICATION_CONTACT)
179+
.authMethodChallengeType(AUTH_METHOD_CHALLENGE_TYPE)
180+
.challengeChannel(CHALLENGE_CHANNEL)
181+
.correlationId(correlationId)
182+
.build()
183+
184+
val jitChallengeResult = nativeAuthOAuth2Strategy.performJITChallengeRequest(
185+
parameters = parameters
186+
)
187+
188+
Assert.assertTrue(jitChallengeResult is JITChallengeApiResult.Redirect)
189+
Assert.assertNotNull((jitChallengeResult as JITChallengeApiResult.Redirect).redirectReason)
190+
}
191+
192+
@Test
193+
fun testPerformJITContinueRedirect() {
194+
val correlationId = UUID.randomUUID().toString()
195+
196+
MockApiUtils.configureMockApi(
197+
endpointType = MockApiEndpoint.JITContinue,
198+
correlationId = correlationId,
199+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
200+
)
201+
202+
val parameters = JITContinueCommandParameters.builder()
203+
.platformComponents(mock<PlatformComponents>())
204+
.continuationToken(CONTINUATION_TOKEN)
205+
.code(OOB_CODE)
206+
.grantType(GRANT_TYPE)
207+
.correlationId(correlationId)
208+
.build()
209+
210+
val jitContinueResult = nativeAuthOAuth2Strategy.performJITContinueRequest(
211+
parameters = parameters
212+
)
213+
214+
Assert.assertTrue(jitContinueResult is JITContinueApiResult.Redirect)
215+
Assert.assertNotNull((jitContinueResult as JITContinueApiResult.Redirect).redirectReason)
216+
}
217+
}

common/src/test/java/com/microsoft/identity/common/internal/providers/microsoft/nativeauth/integration/ResetPasswordOAuth2StrategyTest.kt

Lines changed: 88 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.microsoft.identity.common.nativeauth.MockApiUtils.Companion.configure
4949
import io.mockk.every
5050
import io.mockk.mockk
5151
import org.junit.Assert.assertEquals
52+
import org.junit.Assert.assertNotNull
5253
import org.junit.Assert.assertTrue
5354
import org.junit.Before
5455
import org.junit.Test
@@ -62,7 +63,7 @@ import org.robolectric.annotation.Config
6263
import java.util.UUID
6364

6465
/**
65-
* These are integration tests using real API responses instead of mocked API responses. This class
66+
* These are integration tests using mocked API responses. This class
6667
* covers all sign up endpoints.
6768
* These tests run on the mock API, see: $(MOCK_API_URL) in the variable of the pipeline.
6869
*/
@@ -80,6 +81,7 @@ class ResetPasswordOAuth2StrategyTest {
8081
private val TENANT = "samtoso.onmicrosoft.com"
8182
private val CLIENT_ID = "079af063-4ea7-4dcd-91ff-2b24f54621ea"
8283
private val CHALLENGE_TYPE = "oob redirect"
84+
private val CAPABILITIES = "mfa_required registration_required"
8385
private val OOB_CODE = "123456"
8486
private val CONTINUATION_TOKEN = "1234"
8587
private val INVALID_GRANT_ERROR = "invalid_grant"
@@ -117,6 +119,7 @@ class ResetPasswordOAuth2StrategyTest {
117119
whenever(mockConfig.getJITChallengeEndpoint()).thenReturn(ApiConstants.MockApi.jitChallengeRequestUrl)
118120
whenever(mockConfig.getJITContinueEndpoint()).thenReturn(ApiConstants.MockApi.jitContinueRequestUrl)
119121
whenever(mockConfig.challengeType).thenReturn(CHALLENGE_TYPE)
122+
whenever(mockConfig.capabilities).thenReturn(CAPABILITIES)
120123

121124
nativeAuthOAuth2Strategy = NativeAuthOAuth2Strategy(
122125
config = mockConfig,
@@ -210,7 +213,7 @@ class ResetPasswordOAuth2StrategyTest {
210213
}
211214

212215
@Test
213-
fun testPerformResetPasswordStartChallengeTypeRedirectError() {
216+
fun testPerformResetPasswordStartChallengeTypeRedirect() {
214217
val correlationId = UUID.randomUUID().toString()
215218

216219
configureMockApi(
@@ -227,6 +230,7 @@ class ResetPasswordOAuth2StrategyTest {
227230
mockResetPasswordStartCommandParameters
228231
)
229232
assertTrue(ssprStartResult is ResetPasswordStartApiResult.Redirect)
233+
assertNotNull((ssprStartResult as ResetPasswordStartApiResult.Redirect).redirectReason)
230234
}
231235

232236
/**
@@ -273,6 +277,25 @@ class ResetPasswordOAuth2StrategyTest {
273277
assertTrue(ssprChallengeResult is ResetPasswordChallengeApiResult.CodeRequired)
274278
}
275279

280+
@Test
281+
fun testPerformResetPasswordChallengeRedirect() {
282+
val correlationId = UUID.randomUUID().toString()
283+
284+
configureMockApi(
285+
endpointType = MockApiEndpoint.SSPRChallenge,
286+
correlationId = correlationId,
287+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
288+
)
289+
290+
val ssprChallengeResult = nativeAuthOAuth2Strategy.performResetPasswordChallenge(
291+
continuationToken = CONTINUATION_TOKEN,
292+
correlationId = correlationId
293+
)
294+
295+
assertTrue(ssprChallengeResult is ResetPasswordChallengeApiResult.Redirect)
296+
assertNotNull((ssprChallengeResult as ResetPasswordChallengeApiResult.Redirect).redirectReason)
297+
}
298+
276299
@Test
277300
fun testPerformResetPasswordChallengeExpiredTokenError() {
278301
val correlationId = UUID.randomUUID().toString()
@@ -292,6 +315,29 @@ class ResetPasswordOAuth2StrategyTest {
292315
assertEquals((ssprChallengeResult as ResetPasswordChallengeApiResult.ExpiredToken).error, EXPIRED_TOKEN_ERROR)
293316
}
294317

318+
@Test
319+
fun testPerformResetPasswordContinueRedirect() {
320+
val correlationId = UUID.randomUUID().toString()
321+
322+
configureMockApi(
323+
endpointType = MockApiEndpoint.SSPRContinue,
324+
correlationId = correlationId,
325+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
326+
)
327+
328+
val mockResetPasswordSubmitCodeCommandParameters = mockk<ResetPasswordSubmitCodeCommandParameters>()
329+
every { mockResetPasswordSubmitCodeCommandParameters.getContinuationToken() } returns CONTINUATION_TOKEN
330+
every { mockResetPasswordSubmitCodeCommandParameters.getCode() } returns OOB_CODE
331+
every { mockResetPasswordSubmitCodeCommandParameters.getCorrelationId() } returns correlationId
332+
333+
val ssprContinueApiResult = nativeAuthOAuth2Strategy.performResetPasswordContinue(
334+
mockResetPasswordSubmitCodeCommandParameters
335+
)
336+
337+
assertTrue(ssprContinueApiResult is ResetPasswordContinueApiResult.Redirect)
338+
assertNotNull((ssprContinueApiResult as ResetPasswordContinueApiResult.Redirect).redirectReason)
339+
}
340+
295341
@Test
296342
fun testPerformResetPasswordContinueSuccess() {
297343
val correlationId = UUID.randomUUID().toString()
@@ -329,6 +375,28 @@ class ResetPasswordOAuth2StrategyTest {
329375
assertTrue(ssprSubmitResult is ResetPasswordSubmitApiResult.SubmitSuccess)
330376
}
331377

378+
@Test
379+
fun testPerformResetPasswordSubmitRedirect() {
380+
val correlationId = UUID.randomUUID().toString()
381+
382+
configureMockApi(
383+
endpointType = MockApiEndpoint.SSPRSubmit,
384+
correlationId = correlationId,
385+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
386+
)
387+
388+
val mockResetPasswordSubmitCommandParameters = mockk<ResetPasswordSubmitNewPasswordCommandParameters>()
389+
every { mockResetPasswordSubmitCommandParameters.getContinuationToken() } returns CONTINUATION_TOKEN
390+
every { mockResetPasswordSubmitCommandParameters.getNewPassword() } returns PASSWORD
391+
every { mockResetPasswordSubmitCommandParameters.getCorrelationId() } returns correlationId
392+
393+
val ssprSubmitResult = nativeAuthOAuth2Strategy.performResetPasswordSubmit(
394+
mockResetPasswordSubmitCommandParameters
395+
)
396+
assertTrue(ssprSubmitResult is ResetPasswordSubmitApiResult.Redirect)
397+
assertNotNull((ssprSubmitResult as ResetPasswordSubmitApiResult.Redirect).redirectReason)
398+
}
399+
332400
@Test
333401
fun testPerformResetPasswordSubmitPasswordTooWeakError() {
334402
val correlationId = UUID.randomUUID().toString()
@@ -448,4 +516,22 @@ class ResetPasswordOAuth2StrategyTest {
448516
)
449517
assertTrue(ssprPollCompletionResult is ResetPasswordPollCompletionApiResult.PollingSucceeded)
450518
}
519+
520+
@Test
521+
fun testPerformResetPasswordPollCompletionRedirect() {
522+
val correlationId = UUID.randomUUID().toString()
523+
524+
configureMockApi(
525+
endpointType = MockApiEndpoint.SSPRPoll,
526+
correlationId = correlationId,
527+
responseType = MockApiResponseType.CHALLENGE_TYPE_REDIRECT
528+
)
529+
530+
val ssprPollCompletionResult = nativeAuthOAuth2Strategy.performResetPasswordPollCompletion(
531+
continuationToken = CONTINUATION_TOKEN,
532+
correlationId = correlationId
533+
)
534+
assertTrue(ssprPollCompletionResult is ResetPasswordPollCompletionApiResult.Redirect)
535+
assertNotNull((ssprPollCompletionResult as ResetPasswordPollCompletionApiResult.Redirect).redirectReason)
536+
}
451537
}

0 commit comments

Comments
 (0)