From 5c9a56871e78dacaf4d98e4f0755cbecc7f06d88 Mon Sep 17 00:00:00 2001 From: Disha Janardhan Date: Mon, 5 Oct 2026 11:13:58 -0700 Subject: [PATCH 1/3] Refactor Native Auth V2 state inheritance Separate shared correlation metadata from the V1 raw-token contract. Remove V2 token placeholders and their parcel slot without changing authentication flows or the opaque Common continuation state. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../states/AttributesInvalidStateV2.kt | 5 +- .../states/AttributesRequiredStateV2.kt | 5 +- .../statemachine/states/BaseStates.kt | 13 +- .../states/CodeRequiredStateV2.kt | 5 +- .../statemachine/states/MFARequiredStateV2.kt | 5 +- .../states/MFAVerificationRequiredStateV2.kt | 5 +- .../states/NativeAuthBaseStateV2.kt | 15 +- .../states/NewPasswordRequiredStateV2.kt | 5 +- .../states/PasswordRequiredStateV2.kt | 5 +- .../ResetPasswordMethodRequiredStateV2.kt | 5 +- .../states/SignInAfterResetPasswordStateV2.kt | 5 +- .../states/SignInAfterSignUpStateV2.kt | 5 +- .../StrongAuthRegistrationRequiredStateV2.kt | 4 +- .../StrongAuthVerificationRequiredStateV2.kt | 4 +- .../v2/NativeAuthV2InterfaceJavaTest.java | 2 - .../v2/NativeAuthV2InterfaceKotlinTest.kt | 97 +++++++-- .../nativeauth/v2/NativeAuthV2ResultsTest.kt | 19 +- .../nativeauth/v2/NativeAuthV2SignInTest.kt | 10 +- .../nativeauth/v2/NativeAuthV2SignUpTest.kt | 2 - .../nativeauth/v2/NativeAuthV2StatesTest.kt | 199 ++++++++++++++---- 20 files changed, 276 insertions(+), 139 deletions(-) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesInvalidStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesInvalidStateV2.kt index 424e139964..6e80c82606 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesInvalidStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesInvalidStateV2.kt @@ -41,12 +41,11 @@ import kotlinx.coroutines.launch * State that requires the user to correct previously rejected account attributes. */ class AttributesInvalidStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = AttributesInvalidStateV2::class.java.simpleName internal constructor( @@ -54,7 +53,6 @@ class AttributesInvalidStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -62,7 +60,6 @@ class AttributesInvalidStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesRequiredStateV2.kt index f74ccfae62..47458b7156 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/AttributesRequiredStateV2.kt @@ -63,12 +63,11 @@ import kotlinx.coroutines.withContext * State that requires the user to submit account attributes. */ class AttributesRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = AttributesRequiredStateV2::class.java.simpleName internal constructor( @@ -76,7 +75,6 @@ class AttributesRequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -84,7 +82,6 @@ class AttributesRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt index 74fac9ce01..ae58d7c43d 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt @@ -26,9 +26,16 @@ package com.microsoft.identity.nativeauth.statemachine.states interface State /** - * BaseState is the base class for various states in the Native Auth state machine. + * Common metadata for Native Auth states, independent of the continuation protocol. + */ +abstract class NativeAuthState internal constructor() { + internal abstract val correlationId: String +} + +/** + * Base class for V1 Native Auth states carrying a raw continuation token. */ abstract class BaseState( internal open val continuationToken: String?, - internal open val correlationId: String -) + internal override val correlationId: String +) : NativeAuthState() diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/CodeRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/CodeRequiredStateV2.kt index 81d0a71268..14dda9d250 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/CodeRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/CodeRequiredStateV2.kt @@ -67,12 +67,11 @@ import kotlin.coroutines.coroutineContext * State that requires the user to submit a verification code. */ class CodeRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = CodeRequiredStateV2::class.java.simpleName internal constructor( @@ -80,7 +79,6 @@ class CodeRequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -88,7 +86,6 @@ class CodeRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFARequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFARequiredStateV2.kt index 56825b5545..f09afa4f8d 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFARequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFARequiredStateV2.kt @@ -66,7 +66,6 @@ import java.util.Collections * rather than following an unsupported method. */ class MFARequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, @@ -76,7 +75,7 @@ class MFARequiredStateV2 internal constructor( * The authentication methods the server offered for this multi-factor step. */ authMethods: List = emptyList() -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = MFARequiredStateV2::class.java.simpleName val authMethods: List = Collections.unmodifiableList(ArrayList(authMethods)) @@ -86,7 +85,6 @@ class MFARequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -95,7 +93,6 @@ class MFARequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAVerificationRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAVerificationRequiredStateV2.kt index 935816d53e..961d202b1a 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAVerificationRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/MFAVerificationRequiredStateV2.kt @@ -65,12 +65,11 @@ import kotlinx.coroutines.withContext * required for subsequent submit/resend calls. No error result carries a next state. */ class MFAVerificationRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = MFAVerificationRequiredStateV2::class.java.simpleName internal constructor( @@ -78,7 +77,6 @@ class MFAVerificationRequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -86,7 +84,6 @@ class MFAVerificationRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt index 6cff54cae7..32cf7652c2 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt @@ -40,20 +40,17 @@ import com.microsoft.identity.nativeauth.utils.serializable * Base class for the Native Auth V2 states. Each concrete state exposes only the methods that are * valid at that point in the flow. * - * @property continuationToken Legacy placeholder used only by states that do not yet carry the - * opaque V2 continuation state. Implemented V2 states keep this null. * @property correlationId Correlation ID taken from the previous API response and passed to the next request. * @property scenario Identifies which part of the Native Auth V2 surface this state belongs to. * @property config Configuration used by Native Auth. * @property continuationState Opaque DTO carrying mid-flow state; null in legacy/test paths and for states that don't drive a server call. */ abstract class NativeAuthBaseStateV2 internal constructor( - override val continuationToken: String?, override val correlationId: String, internal val scenario: NativeAuthFlowScenarioV2, internal val config: NativeAuthPublicClientApplicationConfiguration, internal val continuationState: NativeAuthV2ContinuationState? = null -) : BaseState(continuationToken = continuationToken, correlationId = correlationId), State, Parcelable { +) : NativeAuthState(), State, Parcelable { private val TAG: String = NativeAuthBaseStateV2::class.java.simpleName @@ -97,14 +94,12 @@ abstract class NativeAuthBaseStateV2 internal constructor( } /** - * Subclasses must not override this method to append base fields; each subclass's `Parcel` - * constructor is required - * to read the fields in exactly this order - continuationToken, correlationId, scenario, config, - * continuationState - otherwise the reads desynchronise from the writes. Subclasses that never - * carry a continuation state should read config via [readConfigAndSkipContinuationState]. + * Writes correlationId, scenario, config, then continuationState. Each subclass's `Parcel` + * constructor must read the same order. Subclasses with additional fields must call this + * implementation before writing them. Subclasses that never carry a continuation state + * should read config via [readConfigAndSkipContinuationState]. */ override fun writeToParcel(parcel: Parcel, flags: Int) { - parcel.writeString(continuationToken) parcel.writeString(correlationId) parcel.writeString(scenario.name) parcel.writeSerializable(config) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NewPasswordRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NewPasswordRequiredStateV2.kt index 69bc9e572a..9d905b641b 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NewPasswordRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NewPasswordRequiredStateV2.kt @@ -59,12 +59,11 @@ import kotlinx.coroutines.withContext * State that requires the user to submit a new password. */ class NewPasswordRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = NewPasswordRequiredStateV2::class.java.simpleName internal constructor( @@ -72,7 +71,6 @@ class NewPasswordRequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -80,7 +78,6 @@ class NewPasswordRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/PasswordRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/PasswordRequiredStateV2.kt index 420caa4f79..0c5c75961e 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/PasswordRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/PasswordRequiredStateV2.kt @@ -71,12 +71,11 @@ import kotlinx.coroutines.withContext * caller retries on this same state instance; no error result carries a next state. */ class PasswordRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = PasswordRequiredStateV2::class.java.simpleName internal constructor( @@ -84,7 +83,6 @@ class PasswordRequiredStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -92,7 +90,6 @@ class PasswordRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordMethodRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordMethodRequiredStateV2.kt index ace1323e39..e48f58bb05 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordMethodRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/ResetPasswordMethodRequiredStateV2.kt @@ -61,13 +61,12 @@ import java.util.Collections * No challenge is sent until the app explicitly selects one of [authMethods]. */ class ResetPasswordMethodRequiredStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null, authMethods: List = emptyList() -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = ResetPasswordMethodRequiredStateV2::class.java.simpleName val authMethods: List = Collections.unmodifiableList(ArrayList(authMethods)) @@ -76,7 +75,6 @@ class ResetPasswordMethodRequiredStateV2 internal constructor( authMethods: List, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = NativeAuthFlowScenarioV2.RESET_PASSWORD, config = config, @@ -85,7 +83,6 @@ class ResetPasswordMethodRequiredStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf( parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterResetPasswordStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterResetPasswordStateV2.kt index 2fd22603df..7a4fab6118 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterResetPasswordStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterResetPasswordStateV2.kt @@ -57,12 +57,11 @@ import kotlinx.coroutines.withContext * State reached once a password reset has completed server-side. */ class SignInAfterResetPasswordStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = SignInAfterResetPasswordStateV2::class.java.simpleName internal constructor( @@ -70,7 +69,6 @@ class SignInAfterResetPasswordStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -78,7 +76,6 @@ class SignInAfterResetPasswordStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterSignUpStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterSignUpStateV2.kt index b9aff9df32..faf6bb9e97 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterSignUpStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/SignInAfterSignUpStateV2.kt @@ -57,12 +57,11 @@ import kotlinx.coroutines.withContext * State reached once a sign-up has completed server-side. */ class SignInAfterSignUpStateV2 internal constructor( - continuationToken: String?, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration, continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config, continuationState) { +) : NativeAuthBaseStateV2(correlationId, scenario, config, continuationState) { private val TAG: String = SignInAfterSignUpStateV2::class.java.simpleName internal constructor( @@ -70,7 +69,6 @@ class SignInAfterSignUpStateV2 internal constructor( scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration ) : this( - continuationToken = null, correlationId = continuationState.correlationId, scenario = scenario, config = config, @@ -78,7 +76,6 @@ class SignInAfterSignUpStateV2 internal constructor( ) private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString(), correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), config = parcel.serializable() as NativeAuthPublicClientApplicationConfiguration, diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthRegistrationRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthRegistrationRequiredStateV2.kt index 8d3da0ebfd..fa95c2712a 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthRegistrationRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthRegistrationRequiredStateV2.kt @@ -39,15 +39,13 @@ import kotlinx.coroutines.launch * State that requires the user to select a strong authentication method to register. */ class StrongAuthRegistrationRequiredStateV2 internal constructor( - continuationToken: String, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config) { +) : NativeAuthBaseStateV2(correlationId, scenario, config) { private val TAG: String = StrongAuthRegistrationRequiredStateV2::class.java.simpleName private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString() ?: "", correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), // Also drains the continuationState field written by the base class, so the read order diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthVerificationRequiredStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthVerificationRequiredStateV2.kt index a2b8b50913..c2f6908eec 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthVerificationRequiredStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/StrongAuthVerificationRequiredStateV2.kt @@ -38,15 +38,13 @@ import kotlinx.coroutines.launch * State that requires the user to submit a strong authentication challenge. */ class StrongAuthVerificationRequiredStateV2 internal constructor( - continuationToken: String, correlationId: String, scenario: NativeAuthFlowScenarioV2, config: NativeAuthPublicClientApplicationConfiguration -) : NativeAuthBaseStateV2(continuationToken, correlationId, scenario, config) { +) : NativeAuthBaseStateV2(correlationId, scenario, config) { private val TAG: String = StrongAuthVerificationRequiredStateV2::class.java.simpleName private constructor(parcel: Parcel) : this( - continuationToken = parcel.readString() ?: "", correlationId = parcel.readString() ?: "UNSET", scenario = NativeAuthFlowScenarioV2.valueOf(parcel.readString() ?: NativeAuthFlowScenarioV2.UNKNOWN.name), // Also drains the continuationState field written by the base class, so the read order diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceJavaTest.java b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceJavaTest.java index 78fad599b5..a45268b2c2 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceJavaTest.java +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceJavaTest.java @@ -117,7 +117,6 @@ public void signUpV2RejectsBlankUsername() throws ExecutionException, Interrupte @Test public void signInAfterResetPasswordReturnsNotImplemented() throws ExecutionException, InterruptedException, TimeoutException { final SignInAfterResetPasswordStateV2 state = new SignInAfterResetPasswordStateV2( - "continuation-token", "correlation-id", NativeAuthFlowScenarioV2.RESET_PASSWORD, new NativeAuthPublicClientApplicationConfiguration(), @@ -142,7 +141,6 @@ public void onError(@NonNull BaseException exception) { @Test public void mfaVerificationRequiredResendChallengeReturnsInvalidState() throws ExecutionException, InterruptedException, TimeoutException { final MFAVerificationRequiredStateV2 state = new MFAVerificationRequiredStateV2( - "continuation-token", "correlation-id", NativeAuthFlowScenarioV2.SIGN_IN, new NativeAuthPublicClientApplicationConfiguration(), diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceKotlinTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceKotlinTest.kt index 0e99166630..b664531d38 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceKotlinTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2InterfaceKotlinTest.kt @@ -23,6 +23,8 @@ package com.microsoft.identity.nativeauth.v2 import android.content.Context +import android.os.Bundle +import android.os.Parcel import androidx.test.core.app.ApplicationProvider import com.microsoft.identity.client.PublicClientApplication import com.microsoft.identity.client.AuthenticationResultAdapter @@ -79,6 +81,7 @@ import com.microsoft.identity.nativeauth.statemachine.states.AttributesRequiredS import com.microsoft.identity.nativeauth.statemachine.states.CodeRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.MFARequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.MFAVerificationRequiredStateV2 +import com.microsoft.identity.nativeauth.statemachine.states.NativeAuthBaseStateV2 import com.microsoft.identity.nativeauth.statemachine.states.NewPasswordRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.PasswordRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.ResetPasswordMethodRequiredStateV2 @@ -106,7 +109,6 @@ import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse import org.junit.Assert.assertTrue import org.junit.Assert.assertSame -import org.junit.Assert.assertNull import org.junit.Assert.fail import org.junit.Before import org.junit.Test @@ -169,7 +171,6 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { assertEquals(NativeAuthFlowScenarioV2.SIGN_UP, result.scenario) assertEquals(6, result.codeLength) assertEquals("a***@example.com", result.sentTo) - assertNull(result.nextState.continuationToken) } @Test @@ -193,7 +194,6 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { assertEquals(6, result.codeLength) assertEquals("a***@example.com", result.sentTo) assertEquals("email", result.channel) - assertNull(result.nextState.continuationToken) assertEquals(correlationId, result.nextState.correlationId) } @@ -587,7 +587,6 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { @Test fun codeOperationsWithoutContinuationStateReturnInvalidState() = runTest { val state = CodeRequiredStateV2( - null, correlationId, NativeAuthFlowScenarioV2.RESET_PASSWORD, NativeAuthPublicClientApplicationConfiguration() @@ -752,7 +751,6 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { // Null-continuation fallback returns before any command parameters are created. val notImplementedPassword = "Password!".toCharArray() val notImplemented = NewPasswordRequiredStateV2( - "continuation-token", correlationId, NativeAuthFlowScenarioV2.RESET_PASSWORD, NativeAuthPublicClientApplicationConfiguration() @@ -890,34 +888,32 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { val attributes = com.microsoft.identity.nativeauth.UserAttributes.Builder().city("city").build() val authMethod = com.microsoft.identity.nativeauth.AuthMethod("id", "oob", null, "email") - val codeRequired = CodeRequiredStateV2("continuation-token", "correlation-id", scenario, config) + val codeRequired = CodeRequiredStateV2("correlation-id", scenario, config) assertInvalidState(codeRequired.submitCode("1234")) assertInvalidState(codeRequired.resendCode()) - assertInvalidState(PasswordRequiredStateV2("continuation-token", "correlation-id", scenario, config).submitPassword("password".toCharArray())) - assertNotImplemented(NewPasswordRequiredStateV2("continuation-token", "correlation-id", scenario, config).submitNewPassword("password".toCharArray())) - assertNotImplemented(SignInAfterResetPasswordStateV2("continuation-token", "correlation-id", scenario, config).signIn()) - assertInvalidState(AttributesRequiredStateV2("continuation-token", "correlation-id", scenario, config).submitAttributes(attributes)) - assertInvalidState(AttributesInvalidStateV2("continuation-token", "correlation-id", scenario, config).submitAttributes(attributes)) - assertInvalidState(MFARequiredStateV2("continuation-token", "correlation-id", scenario, config).selectAuthMethod(authMethod)) + assertInvalidState(PasswordRequiredStateV2("correlation-id", scenario, config).submitPassword("password".toCharArray())) + assertNotImplemented(NewPasswordRequiredStateV2("correlation-id", scenario, config).submitNewPassword("password".toCharArray())) + assertNotImplemented(SignInAfterResetPasswordStateV2("correlation-id", scenario, config).signIn()) + assertInvalidState(AttributesRequiredStateV2("correlation-id", scenario, config).submitAttributes(attributes)) + assertInvalidState(AttributesInvalidStateV2("correlation-id", scenario, config).submitAttributes(attributes)) + assertInvalidState(MFARequiredStateV2("correlation-id", scenario, config).selectAuthMethod(authMethod)) assertInvalidState( ResetPasswordMethodRequiredStateV2( - "continuation-token", "correlation-id", NativeAuthFlowScenarioV2.RESET_PASSWORD, config ).selectAuthMethod(authMethod) ) - assertInvalidState(MFAVerificationRequiredStateV2("continuation-token", "correlation-id", scenario, config).submitChallenge("challenge")) - assertInvalidState(MFAVerificationRequiredStateV2("continuation-token", "correlation-id", scenario, config).resendChallenge()) - assertNotImplemented(StrongAuthRegistrationRequiredStateV2("continuation-token", "correlation-id", scenario, config).selectAuthMethod(authMethod)) - assertNotImplemented(StrongAuthVerificationRequiredStateV2("continuation-token", "correlation-id", scenario, config).submitChallenge("challenge")) + assertInvalidState(MFAVerificationRequiredStateV2("correlation-id", scenario, config).submitChallenge("challenge")) + assertInvalidState(MFAVerificationRequiredStateV2("correlation-id", scenario, config).resendChallenge()) + assertNotImplemented(StrongAuthRegistrationRequiredStateV2("correlation-id", scenario, config).selectAuthMethod(authMethod)) + assertNotImplemented(StrongAuthVerificationRequiredStateV2("correlation-id", scenario, config).submitChallenge("challenge")) } @Test fun signInAfterResetPasswordRequiredIsResult() { val state = SignInAfterResetPasswordStateV2( - continuationToken = "continuation-token", correlationId = "correlation-id", scenario = NativeAuthFlowScenarioV2.RESET_PASSWORD, config = NativeAuthPublicClientApplicationConfiguration() @@ -976,6 +972,71 @@ class NativeAuthV2InterfaceKotlinTest : PublicClientApplicationAbstractTest() { NativeAuthResultV2.CodeRequired).nextState } + @Test + fun resetPasswordContinuesAfterRestoringStatesAndReinitializingConfiguration() = runTest { + val codeState = restoreSavedStateAndReinitializeConfiguration(codeRequiredState()) + enqueueResult( + NativeAuthV2CommandResult.NewPasswordRequired(correlationId, createContinuationState()), + NativeAuthV2ResetPasswordSubmitCodeCommand::class + ) + val passwordState = restoreSavedStateAndReinitializeConfiguration( + (codeState.submitCode("123456") as NativeAuthResultV2.NewPasswordRequired).nextState + ) + enqueueResult( + NativeAuthV2CommandResult.SignInAfterResetPasswordRequired( + correlationId, + createContinuationState() + ), + NativeAuthV2SubmitNewPasswordCommand::class + ) + val signInState = restoreSavedStateAndReinitializeConfiguration( + (passwordState.submitNewPassword("Password!".toCharArray()) as + NativeAuthResultV2.SignInAfterResetPasswordRequired).nextState + ) + val localResult = mockk() + val authenticationResult = mockk() + every { authenticationResult.account } returns mockk() + mockkStatic(AuthenticationResultAdapter::class) + try { + every { AuthenticationResultAdapter.adapt(localResult) } returns authenticationResult + enqueueResult( + NativeAuthV2CommandResult.Complete(correlationId, localResult, null, null), + NativeAuthV2SignInAfterResetPasswordCommand::class + ) + val result = signInState.signIn() as NativeAuthResultV2.Complete + assertEquals(correlationId, result.resultValue.correlationId) + assertEquals(NativeAuthFlowScenarioV2.RESET_PASSWORD, result.scenario) + } finally { + unmockkStatic(AuthenticationResultAdapter::class) + } + } + + @Suppress("DEPRECATION") + private inline fun restoreSavedStateAndReinitializeConfiguration(state: T): T { + val parcel = Parcel.obtain() + val restoredParcel = Parcel.obtain() + try { + val savedState = Bundle().apply { putParcelable("state", state) } + savedState.writeToParcel(parcel, 0) + val bytes = parcel.marshall() + restoredParcel.unmarshall(bytes, 0, bytes.size) + restoredParcel.setDataPosition(0) + val restoredState = Bundle.CREATOR.createFromParcel(restoredParcel).apply { + classLoader = T::class.java.classLoader + } + val restored = requireNotNull(restoredState.getParcelable("state")) + // The existing configuration serialization excludes superclass fields, Context, and + // the token cache. Reinitialize those separately; the opaque continuation stays restored. + restored.config.mergeConfiguration(state.config) + restored.config.appContext = state.config.appContext + restored.config.oAuth2TokenCache = state.config.oAuth2TokenCache + return restored + } finally { + parcel.recycle() + restoredParcel.recycle() + } + } + private suspend fun signInAfterResetState(): SignInAfterResetPasswordStateV2 { val codeState = codeRequiredState() enqueueResult( diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ResultsTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ResultsTest.kt index ce09c2eaad..81c1192c83 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ResultsTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2ResultsTest.kt @@ -54,7 +54,6 @@ import org.robolectric.RobolectricTestRunner @RunWith(RobolectricTestRunner::class) class NativeAuthV2ResultsTest { - private val continuationToken = "continuation-token" private val correlationId = "correlation-id" private val scenario = NativeAuthFlowScenarioV2.SIGN_IN private val config = NativeAuthPublicClientApplicationConfiguration() @@ -81,7 +80,7 @@ class NativeAuthV2ResultsTest { @Test fun testCodeRequiredExposesExpectedValues() { val result = NativeAuthResultV2.CodeRequired( - nextState = CodeRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = CodeRequiredStateV2(correlationId, scenario, config), scenario = scenario, codeLength = 6, sentTo = "user@email.com", @@ -96,7 +95,7 @@ class NativeAuthV2ResultsTest { @Test fun testPasswordRequiredExposesExpectedValues() { val result = NativeAuthResultV2.PasswordRequired( - nextState = PasswordRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = PasswordRequiredStateV2(correlationId, scenario, config), scenario = scenario ) assertEquals(scenario, result.scenario) @@ -105,7 +104,7 @@ class NativeAuthV2ResultsTest { @Test fun testNewPasswordRequiredExposesExpectedValues() { val result = NativeAuthResultV2.NewPasswordRequired( - nextState = NewPasswordRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = NewPasswordRequiredStateV2(correlationId, scenario, config), scenario = scenario ) assertEquals(scenario, result.scenario) @@ -115,7 +114,7 @@ class NativeAuthV2ResultsTest { fun testAttributesRequiredExposesExpectedValues() { val requiredAttributes = mutableListOf(RequiredUserAttribute("city", "string", true, null)) val result = NativeAuthResultV2.AttributesRequired( - nextState = AttributesRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = AttributesRequiredStateV2(correlationId, scenario, config), scenario = scenario, requiredAttributes = requiredAttributes ) @@ -136,7 +135,7 @@ class NativeAuthV2ResultsTest { fun testAttributesInvalidExposesExpectedValues() { val invalidAttributes = mutableListOf("city") val result = NativeAuthResultV2.AttributesInvalid( - nextState = AttributesInvalidStateV2(continuationToken, correlationId, scenario, config), + nextState = AttributesInvalidStateV2(correlationId, scenario, config), scenario = scenario, invalidAttributes = invalidAttributes ) @@ -157,7 +156,6 @@ class NativeAuthV2ResultsTest { fun testMFARequiredExposesExpectedValues() { val authMethods = mutableListOf(AuthMethod("id", "oob", "user@email.com", "email")) val nextState = MFARequiredStateV2( - continuationToken, correlationId, scenario, config, @@ -186,7 +184,6 @@ class NativeAuthV2ResultsTest { val authMethods = mutableListOf(AuthMethod("sms-1", "sms", "+X XXX XXX 34", "sms")) val nextState = ResetPasswordMethodRequiredStateV2( - continuationToken, correlationId, NativeAuthFlowScenarioV2.RESET_PASSWORD, config, @@ -212,7 +209,7 @@ class NativeAuthV2ResultsTest { @Test fun testMFAVerificationRequiredExposesExpectedValues() { val result = NativeAuthResultV2.MFAVerificationRequired( - nextState = MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = MFAVerificationRequiredStateV2(correlationId, scenario, config), scenario = scenario, codeLength = 8, sentTo = "user@email.com", @@ -228,7 +225,7 @@ class NativeAuthV2ResultsTest { fun testStrongAuthRegistrationRequiredExposesExpectedValues() { val authMethods = listOf(AuthMethod("id", "oob", "user@email.com", "email")) val result = NativeAuthResultV2.StrongAuthRegistrationRequired( - nextState = StrongAuthRegistrationRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = StrongAuthRegistrationRequiredStateV2(correlationId, scenario, config), scenario = scenario, authMethods = authMethods ) @@ -239,7 +236,7 @@ class NativeAuthV2ResultsTest { @Test fun testStrongAuthVerificationRequiredExposesExpectedValues() { val result = NativeAuthResultV2.StrongAuthVerificationRequired( - nextState = StrongAuthVerificationRequiredStateV2(continuationToken, correlationId, scenario, config), + nextState = StrongAuthVerificationRequiredStateV2(correlationId, scenario, config), scenario = scenario, codeLength = 4, sentTo = "user@email.com", diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt index ee61fdb481..56881d2b3f 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignInTest.kt @@ -195,7 +195,6 @@ class NativeAuthV2SignInTest : PublicClientApplicationAbstractTest() { assertEquals(8, result.codeLength) assertEquals("u***@contoso.com", result.sentTo) assertEquals("email", result.channel) - assertNull(result.nextState.continuationToken) assertEquals(correlationId, result.nextState.correlationId) } @@ -1150,7 +1149,6 @@ class NativeAuthV2SignInTest : PublicClientApplicationAbstractTest() { val restoredVerification = parcelRoundTrip(verificationState, MFAVerificationRequiredStateV2.CREATOR) listOf(restoredPassword, restoredMfa, restoredVerification).forEach { restored -> - assertNull(restored.continuationToken) assertEquals(correlationId, restored.correlationId) assertEquals(NativeAuthFlowScenarioV2.SIGN_IN, restored.scenario) assertEquals(listOf("scope"), restored.continuationState?.scopesForTokenRequest()) @@ -1244,12 +1242,16 @@ class NativeAuthV2SignInTest : PublicClientApplicationAbstractTest() { creator: android.os.Parcelable.Creator ): T { val parcel = Parcel.obtain() + val restoredParcel = Parcel.obtain() try { state.writeToParcel(parcel, 0) - parcel.setDataPosition(0) - return creator.createFromParcel(parcel) + val bytes = parcel.marshall() + restoredParcel.unmarshall(bytes, 0, bytes.size) + restoredParcel.setDataPosition(0) + return creator.createFromParcel(restoredParcel) } finally { parcel.recycle() + restoredParcel.recycle() } } diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignUpTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignUpTest.kt index b7de95282c..21152f8c5d 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignUpTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2SignUpTest.kt @@ -92,7 +92,6 @@ import kotlinx.coroutines.test.runTest import org.junit.After import org.junit.Assert.assertEquals import org.junit.Assert.assertFalse -import org.junit.Assert.assertNull import org.junit.Assert.assertNotSame import org.junit.Assert.assertTrue import org.junit.Assert.fail @@ -203,7 +202,6 @@ class NativeAuthV2SignUpTest : PublicClientApplicationAbstractTest() { assertEquals(NativeAuthFlowScenarioV2.SIGN_UP, result.scenario) assertEquals(6, result.codeLength) assertEquals("u***@contoso.com", result.sentTo) - assertNull(result.nextState.continuationToken) } @Test diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt index e2e8a94834..c5a0fe57f4 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt @@ -39,6 +39,7 @@ import com.microsoft.identity.nativeauth.statemachine.NativeAuthFlowScenarioV2 import com.microsoft.identity.nativeauth.statemachine.results.NativeAuthResultV2 import com.microsoft.identity.nativeauth.statemachine.states.AttributesInvalidStateV2 import com.microsoft.identity.nativeauth.statemachine.states.AttributesRequiredStateV2 +import com.microsoft.identity.nativeauth.statemachine.states.BaseState import com.microsoft.identity.nativeauth.statemachine.states.CodeRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.MFARequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.MFAVerificationRequiredStateV2 @@ -47,11 +48,13 @@ import com.microsoft.identity.nativeauth.statemachine.states.NewPasswordRequired import com.microsoft.identity.nativeauth.statemachine.states.PasswordRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.ResetPasswordMethodRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.SignInAfterResetPasswordStateV2 +import com.microsoft.identity.nativeauth.statemachine.states.SignInAfterSignUpStateV2 +import com.microsoft.identity.nativeauth.statemachine.states.SignInCodeRequiredState import com.microsoft.identity.nativeauth.statemachine.states.StrongAuthRegistrationRequiredStateV2 import com.microsoft.identity.nativeauth.statemachine.states.StrongAuthVerificationRequiredStateV2 import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertSame -import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Assert.fail import org.junit.Test @@ -67,10 +70,69 @@ import java.util.concurrent.TimeUnit @RunWith(RobolectricTestRunner::class) class NativeAuthV2StatesTest { - private val continuationToken = "continuation-token" private val correlationId = "correlation-id" private val scenario = NativeAuthFlowScenarioV2.SIGN_IN - private val config = NativeAuthPublicClientApplicationConfiguration() + private val config = NativeAuthPublicClientApplicationConfiguration().apply { + dc = "parcel-dc" + setChallengeTypes(listOf("password", "oob")) + } + + @Test + fun v2StatesDoNotInheritTheV1ContinuationTokenContract() { + assertFalse(BaseState::class.java.isAssignableFrom(NativeAuthBaseStateV2::class.java)) + var type: Class<*>? = NativeAuthBaseStateV2::class.java + while (type != null) { + assertFalse(type.declaredFields.any { it.name == "continuationToken" }) + assertFalse(type.declaredMethods.any { it.name.startsWith("getContinuationToken") }) + type = type.superclass + } + } + + @Test + fun v2ParcelStartsWithCorrelationIdRatherThanARawTokenPlaceholder() { + val state = CodeRequiredStateV2( + createContinuationState(), + NativeAuthFlowScenarioV2.RESET_PASSWORD, + config + ) + val parcel = Parcel.obtain() + try { + state.writeToParcel(parcel, 0) + parcel.setDataPosition(0) + assertEquals(correlationId, parcel.readString()) + assertEquals(NativeAuthFlowScenarioV2.RESET_PASSWORD.name, parcel.readString()) + } finally { + parcel.recycle() + } + } + + @Test + fun v1StateRetainsRawTokenContractAndParcelPrefix() { + val state = SignInCodeRequiredState( + "v1-continuation-token", + correlationId, + "user@example.com", + listOf("scope"), + null, + config + ) + val baseState: BaseState = state + assertEquals("v1-continuation-token", baseState.continuationToken) + assertEquals(correlationId, baseState.correlationId) + val parcel = Parcel.obtain() + val restoredParcel = Parcel.obtain() + try { + state.writeToParcel(parcel, 0) + val bytes = parcel.marshall() + restoredParcel.unmarshall(bytes, 0, bytes.size) + restoredParcel.setDataPosition(0) + assertEquals("v1-continuation-token", restoredParcel.readString()) + assertEquals(correlationId, restoredParcel.readString()) + } finally { + parcel.recycle() + restoredParcel.recycle() + } + } private fun assertParcelRoundTrip( state: T, @@ -78,49 +140,54 @@ class NativeAuthV2StatesTest { ): T { assertEquals(0, state.describeContents()) val parcel = Parcel.obtain() + val restoredParcel = Parcel.obtain() try { state.writeToParcel(parcel, 0) - parcel.setDataPosition(0) - val restored = creator.createFromParcel(parcel) - assertEquals(state.continuationToken, restored.continuationToken) + val bytes = parcel.marshall() + restoredParcel.unmarshall(bytes, 0, bytes.size) + restoredParcel.setDataPosition(0) + val restored = creator.createFromParcel(restoredParcel) assertEquals(state.correlationId, restored.correlationId) assertEquals(state.scenario, restored.scenario) + assertEquals(state.config.dc, restored.config.dc) + assertEquals(state.config.getChallengeTypes(), restored.config.getChallengeTypes()) + assertEquals(restoredParcel.dataSize(), restoredParcel.dataPosition()) assertEquals(1, creator.newArray(1).size) return restored } finally { parcel.recycle() + restoredParcel.recycle() } } @Test fun testStatesAreParcelable() { assertParcelRoundTrip( - CodeRequiredStateV2(continuationToken, correlationId, scenario, config), + CodeRequiredStateV2(correlationId, scenario, config), CodeRequiredStateV2.CREATOR ) assertParcelRoundTrip( - PasswordRequiredStateV2(continuationToken, correlationId, scenario, config), + PasswordRequiredStateV2(correlationId, scenario, config), PasswordRequiredStateV2.CREATOR ) assertParcelRoundTrip( - NewPasswordRequiredStateV2(continuationToken, correlationId, scenario, config), + NewPasswordRequiredStateV2(correlationId, scenario, config), NewPasswordRequiredStateV2.CREATOR ) assertParcelRoundTrip( - AttributesRequiredStateV2(continuationToken, correlationId, scenario, config), + AttributesRequiredStateV2(correlationId, scenario, config), AttributesRequiredStateV2.CREATOR ) assertParcelRoundTrip( - AttributesInvalidStateV2(continuationToken, correlationId, scenario, config), + AttributesInvalidStateV2(correlationId, scenario, config), AttributesInvalidStateV2.CREATOR ) assertParcelRoundTrip( - MFARequiredStateV2(continuationToken, correlationId, scenario, config), + MFARequiredStateV2(correlationId, scenario, config), MFARequiredStateV2.CREATOR ) val restoredResetPasswordMethodState = assertParcelRoundTrip( ResetPasswordMethodRequiredStateV2( - continuationToken, correlationId, NativeAuthFlowScenarioV2.RESET_PASSWORD, config, @@ -130,21 +197,29 @@ class NativeAuthV2StatesTest { ) assertEquals("sms-1", restoredResetPasswordMethodState.authMethods.single().id) assertParcelRoundTrip( - MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config), + MFAVerificationRequiredStateV2(correlationId, scenario, config), MFAVerificationRequiredStateV2.CREATOR ) assertParcelRoundTrip( - StrongAuthRegistrationRequiredStateV2(continuationToken, correlationId, scenario, config), + StrongAuthRegistrationRequiredStateV2(correlationId, scenario, config), StrongAuthRegistrationRequiredStateV2.CREATOR ) assertParcelRoundTrip( - StrongAuthVerificationRequiredStateV2(continuationToken, correlationId, scenario, config), + StrongAuthVerificationRequiredStateV2(correlationId, scenario, config), StrongAuthVerificationRequiredStateV2.CREATOR ) + assertParcelRoundTrip( + SignInAfterResetPasswordStateV2(correlationId, scenario, config), + SignInAfterResetPasswordStateV2.CREATOR + ) + assertParcelRoundTrip( + SignInAfterSignUpStateV2(correlationId, scenario, config), + SignInAfterSignUpStateV2.CREATOR + ) } @Test - fun testContinuationStatesParcelOpaqueStateWithoutInventingContinuationToken() { + fun testContinuationStatesParcelOpaqueState() { val continuationState = createContinuationState() val restoredCodeState = assertParcelRoundTrip( @@ -160,11 +235,51 @@ class NativeAuthV2StatesTest { SignInAfterResetPasswordStateV2.CREATOR ) - listOf(restoredCodeState, restoredPasswordState, restoredSignInState).forEach { restored -> - assertNull(restored.continuationToken) + val restoredStates = listOf( + restoredCodeState, + restoredPasswordState, + restoredSignInState, + assertParcelRoundTrip( + PasswordRequiredStateV2(continuationState, scenario, config), + PasswordRequiredStateV2.CREATOR + ), + assertParcelRoundTrip( + AttributesRequiredStateV2(continuationState, NativeAuthFlowScenarioV2.SIGN_UP, config), + AttributesRequiredStateV2.CREATOR + ), + assertParcelRoundTrip( + AttributesInvalidStateV2(continuationState, NativeAuthFlowScenarioV2.SIGN_UP, config), + AttributesInvalidStateV2.CREATOR + ), + assertParcelRoundTrip( + SignInAfterSignUpStateV2(continuationState, NativeAuthFlowScenarioV2.SIGN_UP, config), + SignInAfterSignUpStateV2.CREATOR + ), + assertParcelRoundTrip( + MFAVerificationRequiredStateV2(continuationState, scenario, config), + MFAVerificationRequiredStateV2.CREATOR + ) + ) + restoredStates.forEach { restored -> assertEquals(correlationId, restored.correlationId) assertEquals(listOf("scope"), restored.continuationState?.scopesForTokenRequest()) + assertEquals("NativeAuthV2ContinuationState()", restored.continuationState.toString()) + } + + val methods = listOf(AuthMethod("sms-1", "sms", "+X XXX XXX 34", "sms")) + val restoredMfa = assertParcelRoundTrip( + MFARequiredStateV2(continuationState, methods, scenario, config), + MFARequiredStateV2.CREATOR + ) + val restoredResetPasswordMethods = assertParcelRoundTrip( + ResetPasswordMethodRequiredStateV2(continuationState, methods, config), + ResetPasswordMethodRequiredStateV2.CREATOR + ) + listOf(restoredMfa, restoredResetPasswordMethods).forEach { restored -> + assertEquals(listOf("scope"), restored.continuationState?.scopesForTokenRequest()) } + assertEquals(methods, restoredMfa.authMethods) + assertEquals(methods, restoredResetPasswordMethods.authMethods) } private fun createContinuationState(): NativeAuthV2ContinuationState = @@ -210,7 +325,7 @@ class NativeAuthV2StatesTest { @Test fun testCodeRequiredStateCallbacksReturnInvalidState() { assertCallbackInvalidState { future -> - CodeRequiredStateV2(continuationToken, correlationId, scenario, config).submitCode( + CodeRequiredStateV2(correlationId, scenario, config).submitCode( "1234", object : CodeRequiredStateV2.SubmitCodeCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -219,7 +334,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackInvalidState { future -> - CodeRequiredStateV2(continuationToken, correlationId, scenario, config).resendCode( + CodeRequiredStateV2(correlationId, scenario, config).resendCode( object : CodeRequiredStateV2.ResendCodeCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) override fun onError(exception: BaseException) = future.setException(exception) @@ -231,7 +346,7 @@ class NativeAuthV2StatesTest { @Test fun testPasswordRequiredStateCallbackReturnsInvalidState() { assertCallbackInvalidState { future -> - PasswordRequiredStateV2(continuationToken, correlationId, scenario, config).submitPassword( + PasswordRequiredStateV2(correlationId, scenario, config).submitPassword( "password".toCharArray(), object : PasswordRequiredStateV2.SubmitPasswordCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -244,7 +359,7 @@ class NativeAuthV2StatesTest { @Test fun testNewPasswordRequiredStateCallbackReturnsNotImplemented() { assertCallbackNotImplemented { future -> - NewPasswordRequiredStateV2(continuationToken, correlationId, scenario, config).submitNewPassword( + NewPasswordRequiredStateV2(correlationId, scenario, config).submitNewPassword( "password".toCharArray(), object : NewPasswordRequiredStateV2.SubmitNewPasswordCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -258,7 +373,7 @@ class NativeAuthV2StatesTest { fun testAttributesRequiredStateCallbackReturnsInvalidState() { val attributes = UserAttributes.Builder().city("city").build() assertCallbackInvalidState { future -> - AttributesRequiredStateV2(continuationToken, correlationId, scenario, config).submitAttributes( + AttributesRequiredStateV2(correlationId, scenario, config).submitAttributes( attributes, object : AttributesRequiredStateV2.SubmitAttributesCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -272,7 +387,7 @@ class NativeAuthV2StatesTest { fun testAttributesInvalidStateCallbackReturnsInvalidState() { val attributes = UserAttributes.Builder().city("city").build() assertCallbackInvalidState { future -> - AttributesInvalidStateV2(continuationToken, correlationId, scenario, config).submitAttributes( + AttributesInvalidStateV2(correlationId, scenario, config).submitAttributes( attributes, object : AttributesInvalidStateV2.SubmitAttributesCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -286,7 +401,7 @@ class NativeAuthV2StatesTest { fun testMFARequiredStateCallbackReturnsInvalidState() { val authMethod = AuthMethod("id", "oob", null, "email") assertCallbackInvalidState { future -> - MFARequiredStateV2(continuationToken, correlationId, scenario, config).selectAuthMethod( + MFARequiredStateV2(correlationId, scenario, config).selectAuthMethod( authMethod, callback = object : MFARequiredStateV2.SelectAuthMethodCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -300,7 +415,6 @@ class NativeAuthV2StatesTest { fun testMFARequiredStateDefensivelyCopiesAuthMethods() { val source = mutableListOf(AuthMethod("id", "oob", null, "email")) val state = MFARequiredStateV2( - continuationToken, correlationId, scenario, config, @@ -320,7 +434,7 @@ class NativeAuthV2StatesTest { @Test fun testMFAVerificationRequiredStateCallbackReturnsInvalidState() { assertCallbackInvalidState { future -> - MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).submitChallenge( + MFAVerificationRequiredStateV2(correlationId, scenario, config).submitChallenge( "challenge", object : MFAVerificationRequiredStateV2.SubmitChallengeCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -329,7 +443,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackInvalidState { future -> - MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).resendChallenge( + MFAVerificationRequiredStateV2(correlationId, scenario, config).resendChallenge( object : MFAVerificationRequiredStateV2.ResendChallengeCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) override fun onError(exception: BaseException) = future.setException(exception) @@ -342,7 +456,7 @@ class NativeAuthV2StatesTest { fun testStrongAuthRegistrationRequiredStateCallbackReturnsNotImplemented() { val authMethod = AuthMethod("id", "oob", null, "email") assertCallbackNotImplemented { future -> - StrongAuthRegistrationRequiredStateV2(continuationToken, correlationId, scenario, config).selectAuthMethod( + StrongAuthRegistrationRequiredStateV2(correlationId, scenario, config).selectAuthMethod( authMethod, callback = object : StrongAuthRegistrationRequiredStateV2.SelectAuthMethodCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -355,7 +469,7 @@ class NativeAuthV2StatesTest { @Test fun testStrongAuthVerificationRequiredStateCallbackReturnsNotImplemented() { assertCallbackNotImplemented { future -> - StrongAuthVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).submitChallenge( + StrongAuthVerificationRequiredStateV2(correlationId, scenario, config).submitChallenge( "challenge", object : StrongAuthVerificationRequiredStateV2.SubmitChallengeCallback { override fun onResult(result: NativeAuthResultV2) = future.setResult(result) @@ -368,7 +482,7 @@ class NativeAuthV2StatesTest { @Test fun testStateCallbacksRouteExceptionsToOnError() { assertCallbackRoutesToOnError { future, thrown -> - CodeRequiredStateV2(continuationToken, correlationId, scenario, config).submitCode( + CodeRequiredStateV2(correlationId, scenario, config).submitCode( "1234", object : CodeRequiredStateV2.SubmitCodeCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -377,7 +491,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - CodeRequiredStateV2(continuationToken, correlationId, scenario, config).resendCode( + CodeRequiredStateV2(correlationId, scenario, config).resendCode( object : CodeRequiredStateV2.ResendCodeCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown override fun onError(exception: BaseException) = future.setException(exception) @@ -385,7 +499,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - PasswordRequiredStateV2(continuationToken, correlationId, scenario, config).submitPassword( + PasswordRequiredStateV2(correlationId, scenario, config).submitPassword( "password".toCharArray(), object : PasswordRequiredStateV2.SubmitPasswordCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -394,7 +508,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - NewPasswordRequiredStateV2(continuationToken, correlationId, scenario, config).submitNewPassword( + NewPasswordRequiredStateV2(correlationId, scenario, config).submitNewPassword( "password".toCharArray(), object : NewPasswordRequiredStateV2.SubmitNewPasswordCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -404,7 +518,7 @@ class NativeAuthV2StatesTest { } val attributes = UserAttributes.Builder().city("city").build() assertCallbackRoutesToOnError { future, thrown -> - AttributesRequiredStateV2(continuationToken, correlationId, scenario, config).submitAttributes( + AttributesRequiredStateV2(correlationId, scenario, config).submitAttributes( attributes, object : AttributesRequiredStateV2.SubmitAttributesCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -413,7 +527,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - AttributesInvalidStateV2(continuationToken, correlationId, scenario, config).submitAttributes( + AttributesInvalidStateV2(correlationId, scenario, config).submitAttributes( attributes, object : AttributesInvalidStateV2.SubmitAttributesCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -423,7 +537,7 @@ class NativeAuthV2StatesTest { } val authMethod = AuthMethod("id", "oob", null, "email") assertCallbackRoutesToOnError { future, thrown -> - MFARequiredStateV2(continuationToken, correlationId, scenario, config).selectAuthMethod( + MFARequiredStateV2(correlationId, scenario, config).selectAuthMethod( authMethod, null, object : MFARequiredStateV2.SelectAuthMethodCallback { @@ -434,7 +548,6 @@ class NativeAuthV2StatesTest { } assertCallbackRoutesToOnError { future, thrown -> ResetPasswordMethodRequiredStateV2( - continuationToken, correlationId, NativeAuthFlowScenarioV2.RESET_PASSWORD, config @@ -447,7 +560,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).submitChallenge( + MFAVerificationRequiredStateV2(correlationId, scenario, config).submitChallenge( "challenge", object : MFAVerificationRequiredStateV2.SubmitChallengeCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown @@ -456,7 +569,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - MFAVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).resendChallenge( + MFAVerificationRequiredStateV2(correlationId, scenario, config).resendChallenge( object : MFAVerificationRequiredStateV2.ResendChallengeCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown override fun onError(exception: BaseException) = future.setException(exception) @@ -464,7 +577,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - StrongAuthRegistrationRequiredStateV2(continuationToken, correlationId, scenario, config).selectAuthMethod( + StrongAuthRegistrationRequiredStateV2(correlationId, scenario, config).selectAuthMethod( authMethod, null, object : StrongAuthRegistrationRequiredStateV2.SelectAuthMethodCallback { @@ -474,7 +587,7 @@ class NativeAuthV2StatesTest { ) } assertCallbackRoutesToOnError { future, thrown -> - StrongAuthVerificationRequiredStateV2(continuationToken, correlationId, scenario, config).submitChallenge( + StrongAuthVerificationRequiredStateV2(correlationId, scenario, config).submitChallenge( "challenge", object : StrongAuthVerificationRequiredStateV2.SubmitChallengeCallback { override fun onResult(result: NativeAuthResultV2): Unit = throw thrown From c8d95e7dcbecde8ae11a52de40a52285ab3b7db2 Mon Sep 17 00:00:00 2001 From: Disha Janardhan Date: Tue, 6 Oct 2026 14:23:59 -0700 Subject: [PATCH 2/3] Refactor continuation state string assertions Update assertion for continuation state string to check for redacted content and ensure scope is not included. Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --- .../identity/nativeauth/v2/NativeAuthV2StatesTest.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt index c5a0fe57f4..ca39719f1a 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt @@ -263,7 +263,9 @@ class NativeAuthV2StatesTest { restoredStates.forEach { restored -> assertEquals(correlationId, restored.correlationId) assertEquals(listOf("scope"), restored.continuationState?.scopesForTokenRequest()) - assertEquals("NativeAuthV2ContinuationState()", restored.continuationState.toString()) + val continuationStateString = restored.continuationState.toString() + assertTrue(continuationStateString.contains("")) + assertFalse(continuationStateString.contains("scope")) } val methods = listOf(AuthMethod("sms-1", "sms", "+X XXX XXX 34", "sms")) From 2905a5bb49c60311295e79e062f61369ef6d8333 Mon Sep 17 00:00:00 2001 From: Disha Janardhan Date: Wed, 7 Oct 2026 23:05:50 -0700 Subject: [PATCH 3/3] Simplify Native Auth V2 state hierarchy Leave V1 BaseState unchanged and let NativeAuthBaseStateV2 own its correlation metadata without a shared superclass. Add a hierarchy regression assertion. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../nativeauth/statemachine/states/BaseStates.kt | 13 +++---------- .../statemachine/states/NativeAuthBaseStateV2.kt | 4 ++-- .../nativeauth/v2/NativeAuthV2StatesTest.kt | 6 ++++++ 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt index ae58d7c43d..74fac9ce01 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/BaseStates.kt @@ -26,16 +26,9 @@ package com.microsoft.identity.nativeauth.statemachine.states interface State /** - * Common metadata for Native Auth states, independent of the continuation protocol. - */ -abstract class NativeAuthState internal constructor() { - internal abstract val correlationId: String -} - -/** - * Base class for V1 Native Auth states carrying a raw continuation token. + * BaseState is the base class for various states in the Native Auth state machine. */ abstract class BaseState( internal open val continuationToken: String?, - internal override val correlationId: String -) : NativeAuthState() + internal open val correlationId: String +) diff --git a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt index 32cf7652c2..a90f98af2a 100644 --- a/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt +++ b/msal/src/main/java/com/microsoft/identity/nativeauth/statemachine/states/NativeAuthBaseStateV2.kt @@ -46,11 +46,11 @@ import com.microsoft.identity.nativeauth.utils.serializable * @property continuationState Opaque DTO carrying mid-flow state; null in legacy/test paths and for states that don't drive a server call. */ abstract class NativeAuthBaseStateV2 internal constructor( - override val correlationId: String, + internal val correlationId: String, internal val scenario: NativeAuthFlowScenarioV2, internal val config: NativeAuthPublicClientApplicationConfiguration, internal val continuationState: NativeAuthV2ContinuationState? = null -) : NativeAuthState(), State, Parcelable { +) : State, Parcelable { private val TAG: String = NativeAuthBaseStateV2::class.java.simpleName diff --git a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt index ca39719f1a..a95bfa4e34 100644 --- a/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt +++ b/msal/src/test/java/com/microsoft/identity/nativeauth/v2/NativeAuthV2StatesTest.kt @@ -88,6 +88,12 @@ class NativeAuthV2StatesTest { } } + @Test + fun v1AndV2BaseStatesHaveNoSharedSuperclass() { + assertEquals(Any::class.java, BaseState::class.java.superclass) + assertEquals(Any::class.java, NativeAuthBaseStateV2::class.java.superclass) + } + @Test fun v2ParcelStartsWithCorrelationIdRatherThanARawTokenPlaceholder() { val state = CodeRequiredStateV2(