Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignIn code for Native Auth #1919

Merged
merged 13 commits into from
Nov 9, 2023
Merged

SignIn code for Native Auth #1919

merged 13 commits into from
Nov 9, 2023

Conversation

SaurabhMSFT
Copy link
Contributor

@SaurabhMSFT SaurabhMSFT commented Oct 10, 2023

This PR includes the work for SignIn relates features of Native Auth

Companion PR in MSAL Common repo is AzureAD/microsoft-authentication-library-common-for-android#2207

if (cacheRecords.isNullOrEmpty()) {
return null
}
val account = AccountAdapter.adapt(cacheRecords)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like this is an accountList as opposed to an account, right? So let's name it appropriately

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -250,6 +283,37 @@ public static DeviceCodeFlowCommandParameters createDeviceCodeFlowWithClaimsComm
return commandParameters;
}

public static AcquireTokenNoFixedScopesCommandParameters createAcquireTokenNoFixedScopesCommandParameters(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

javadoc for all public methods

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -51,6 +82,8 @@
import java.util.List;
import java.util.Map;

import androidx.annotation.Nullable;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably run Optimize imports once on all files in the PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

import com.microsoft.identity.client.statemachine.Error
import com.microsoft.identity.client.statemachine.states.State

interface Result {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@@ -82,7 +82,7 @@ class PasswordIncorrectError(
override val errorMessage: String,
override val correlationId: String,
override val errorCodes: List<Int>
) : GeneralError(errorMessage = errorMessage, error = error, correlationId = correlationId, errorCodes = errorCodes)
) : Error(errorMessage = errorMessage, error = error, correlationId = correlationId, errorCodes = errorCodes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are there so many identical classes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can easily write one Error class and then use different Strings or enums to represent different error codes within that Error object

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will discuss it with the team but won't be able to make changes as part of this PR as it will be significant refactoring.

* @param error [com.microsoft.identity.client.statemachine.Error].
*/
class UnexpectedError(override val error: Error) :
Result.ErrorResult(error = error),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mentioned in another place about not having so many unnecessary duplicated classes, and instead one result or error class that can hold different error codes.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will discuss it with the team but won't be able to make changes as part of this PR as it will be significant refactoring.

}
}

interface SignOutCallback : Callback<SignOutResult>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also what's inside this interface? Seems to be empty

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean I don't see any curly braces...so not sure if the below methods are in this interface or on the AccountResult class or perhaps on the parent interface i.e. BaseStates?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

An interface in Kotlin doesn't need a body so thats why it works.


sealed interface State

abstract class BaseState(internal open val flowToken: String?)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added


import com.microsoft.identity.common.java.exception.BaseException

interface Callback <T> {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc everywhere

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

@@ -0,0 +1,154 @@
package com.microsoft.identity.client.nativeauth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LICENSE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

@@ -0,0 +1,47 @@
package com.microsoft.identity.client.nativeauth
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing LICENSE

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added

Copy link
Contributor

@shahzaibj shahzaibj left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing LICENSEs, Missing Javadocs, duplicated code around classes that hold Error and result objects

@@ -266,7 +266,7 @@ class SignInPasswordRequiredState(
* @param callback [com.microsoft.identity.client.statemachine.states.SignInPasswordRequiredState.SubmitPasswordCallback] to receive the result on.
* @return The results of the submit password action.
*/
fun submitPassword(password: String, callback: SubmitPasswordCallback) {
fun submitPassword(password: CharArray, callback: SubmitPasswordCallback) {
LogSession.logMethodCall(TAG, "${TAG}.submitPassword")
NativeAuthPublicClientApplication.pcaScope.launch {
try {
Copy link
Contributor

@fadidurah fadidurah Nov 4, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure exactly where we need to add StringUtil.overwriteWithZero(params.password)

Every place password is passed as param? If so, then we need it in this method as well i think

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe right after we get the result

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This password is coming from the calling application and it must be their responsibility to clear the password char array. As a rule, I am clearing password char arrays in the object we are creating.

null
)
throw MsalClientException(
MsalClientException.INVALID_PARAMETER,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is INVALID_PARAMETER the right error in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most suitable error from existing codes.

null
)
throw MsalClientException(
MsalClientException.INVALID_PARAMETER,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is INVALID_PARAMETER the right error in this case?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the most suitable error from existing codes.

Comment on lines 424 to 435
val doesAccountExist = checkForPersistedAccount().get()
if (doesAccountExist) {
Logger.error(
TAG,
"An account is already signed in.",
null
)
throw MsalClientException(
MsalClientException.INVALID_PARAMETER,
"An account is already signed in."
)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw this exact code block in another method - seems like an opportunity for code sharing

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved it to a common function.


package com.microsoft.identity.client.exception;

public class MsalUserNotFoundException extends MsalException {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps you can simply leverage MsalServiceException with a new error code for user_not_found?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file has been removed.

*
* @param callback [com.microsoft.identity.client.statemachine.states.AccountResult.SignOutCallback] to receive the result on.
*/
fun signOut(callback: SignOutCallback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So signOut is a method on a base state..why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does this mean that you can sign out from any given state? What if the user was never even signed in to begin with?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If the user does not have a signed in account then we will throw MsalClientException.NO_CURRENT_ACCOUNT error.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, to me it sounds like that signOut should only be accessible from a signedInState... is there a reason it has to stay on a base state?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shahzaibj this method (as well as getAccessToken(), and other methods) are only available in the AccountResult state, which is practically the same as the SignInState you mention. They're not part of BaseState, this is just an interface.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason we've called it AccountResult rather than SignedInResult is because "account" makes more sense to us in this context: on an account object you can retrieve tokens, sign out (the act of signing out an account), etc. This was aligned with our PM.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the AccountResult to its own file so its clear that BaseStates interface doesn't have the signOut and getAccessToken method.

import kotlinx.coroutines.withContext
import java.io.Serializable

class SignInCodeRequiredState internal constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

) : BaseState(flowToken), State, Serializable {
private val TAG: String = SignInCodeRequiredState::class.java.simpleName

interface SubmitCodeCallback : Callback<SignInSubmitCodeResult>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

class SignInPasswordRequiredState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

}
}

abstract class SignInAfterSignUpBaseState(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

* @throws [MsalClientException] If the the account doesn't exist in the cache.
* @throws [ServiceException] If the refresh token doesn't exist in the cache/is expired, or the refreshing fails.
*/
fun getAccessToken(forceRefresh: Boolean = false, callback: GetAccessTokenCallback) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can get access token from a state where you never signed in?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same reply as for the other comment: this method is part of AccountResult, which is a result/state that is returned when you're signed in.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have moved the AccountResult to its own file so its clear that BaseStates interface doesn't have the signOut and getAccessToken method.

@SaurabhMSFT SaurabhMSFT merged commit 872c867 into ciam-feature Nov 9, 2023
9 checks passed
@SaurabhMSFT SaurabhMSFT deleted the saugautam/2642912 branch November 9, 2023 18:50
SaurabhMSFT added a commit to AzureAD/microsoft-authentication-library-common-for-android that referenced this pull request Nov 9, 2023
This PR includes the work for SignIn relates features of Native Auth. 
Mockito-kotlin has been added as dependency for test projects in common
and testutils project. Powermock has been added as a dependency to
testutils project.

Companion PR in MSAL repo is
AzureAD/microsoft-authentication-library-for-android#1919
SaurabhMSFT added a commit that referenced this pull request Dec 6, 2023
This PR is to merge the SignIn, SIgnup and SSPR code into dev branch.
This code was previously reviewed in #1897, #1919, #1949 and #1950 .

A technical overview of Native Auth is available at
https://dev.azure.com/IdentityDivision/DevEx/_git/AuthLibrariesApiReview?path=/%5BAndroid%5D%20Native%20authentication/technical_overview.md&version=GBsammy/native-authentication&_a=preview

A companion PR in common repo is
AzureAD/microsoft-authentication-library-common-for-android#2233

---------

Co-authored-by: melissaahn <[email protected]>
Co-authored-by: iamgusain <[email protected]>
Co-authored-by: Dome Pongmongkol <[email protected]>
Co-authored-by: Sowmya Malayanur <[email protected]>
Co-authored-by: Mohit <[email protected]>
Co-authored-by: tanmaymanolkar1 <[email protected]>
Co-authored-by: amoyixi <[email protected]>
Co-authored-by: yixilin <[email protected]>
Co-authored-by: fadidurah <[email protected]>
Co-authored-by: Dean Xu <[email protected]>
Co-authored-by: Neha Goel <[email protected]>
Co-authored-by: Dome Pongmongkol <[email protected]>
Co-authored-by: pedro romero vargas <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants