Skip to content

Commit

Permalink
Added checking "state" before continue authentication. Added handling…
Browse files Browse the repository at this point in the history
… errors from the auth screen.| #716
  • Loading branch information
DenBond7 committed Aug 6, 2020
1 parent f240fe7 commit 40571ca
Show file tree
Hide file tree
Showing 7 changed files with 124 additions and 31 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@ import okhttp3.HttpUrl.Companion.toHttpUrl
*/
class OAuth2Helper {
companion object {
const val QUERY_PARAMETER_STATE = "state"
const val QUERY_PARAMETER_CODE = "code"
const val QUERY_PARAMETER_ERROR = "error"
const val QUERY_PARAMETER_ERROR_DESCRIPTION = "error_description"
const val OAUTH2_GRANT_TYPE = "authorization_code"
const val OAUTH2_GRANT_TYPE_REFRESH_TOKEN = "refresh_token"

/**************** Microsoft ****************/
/**
Expand All @@ -29,22 +34,23 @@ class OAuth2Helper {
*
* https://outlook.office.com/SMTP.Send - Allows the app to be able to send emails from the user’s mailbox using the SMTP AUTH client submission protocol.
*/
const val SCOPE_MICROSOFT_OAUTH2 = "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"
const val SCOPE_MICROSOFT_OAUTH2_FOR_MAIL = "offline_access https://outlook.office.com/IMAP.AccessAsUser.All https://outlook.office.com/SMTP.Send"
const val SCOPE_MICROSOFT_OAUTH2_FOR_PROFILE = "openid profile email"

const val MICROSOFT_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
const val MICROSOFT_OAUTH2_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token"
const val MICROSOFT_REDIRECT_URI = "msauth://com.flowcrypt.email.denys/04gM%2BEAfhnq4ALbhOX8jG5oRuow%3D"
const val MICROSOFT_AZURE_APP_ID = "3be51534-5f76-4970-9a34-40ef197aa018"

fun getMicrosoftOAuth2Intent(requestCode: String): Intent {
fun getMicrosoftOAuth2Intent(state: String): Intent {
val authorizeUrl = MICROSOFT_OAUTH2_AUTHORIZE_URL.toHttpUrl()
.newBuilder()
.addQueryParameter("client_id", MICROSOFT_AZURE_APP_ID)
.addQueryParameter("response_type", "code")
.addQueryParameter("redirect_uri", MICROSOFT_REDIRECT_URI)
.addQueryParameter("response_mode", "query")
.addQueryParameter("scope", SCOPE_MICROSOFT_OAUTH2)
.addQueryParameter("state", requestCode)
.addQueryParameter("scope", "$SCOPE_MICROSOFT_OAUTH2_FOR_PROFILE $SCOPE_MICROSOFT_OAUTH2_FOR_MAIL")
.addQueryParameter(QUERY_PARAMETER_STATE, state)
.build()

return Intent(Intent.ACTION_VIEW).apply { data = Uri.parse(authorizeUrl.toUrl().toString()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ interface ApiRepository : BaseApiRepository {
* @param context Interface to global information about an application environment.
* @param authorizeCode A code which will be used to retrieve an access token.
*/
suspend fun getMicrosoftOAuth2Token(requestCode: Long = 0L, context: Context, authorizeCode: String): Result<MicrosoftOAuth2TokenResponse>
suspend fun getMicrosoftOAuth2Token(requestCode: Long = 0L, context: Context, authorizeCode: String, scopes: String): Result<MicrosoftOAuth2TokenResponse>

suspend fun getMicrosoftAccountInfo(requestCode: Long = 0L, context: Context, bearerToken: String): Result<MicrosoftAccountResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,19 @@ interface ApiService {
@POST(OAuth2Helper.MICROSOFT_OAUTH2_TOKEN_URL)
suspend fun getMicrosoftOAuth2Token(
@Field("code") code: String,
@Field("scope") scope: String,
@Field("client_id") clientId: String = OAuth2Helper.MICROSOFT_AZURE_APP_ID,
@Field("scope") scope: String = OAuth2Helper.SCOPE_MICROSOFT_OAUTH2,
@Field("redirect_uri") redirect_uri: String = OAuth2Helper.MICROSOFT_REDIRECT_URI,
@Field("grant_type") grant_type: String = OAuth2Helper.OAUTH2_GRANT_TYPE): Response<MicrosoftOAuth2TokenResponse>

@FormUrlEncoded
@POST(OAuth2Helper.MICROSOFT_OAUTH2_TOKEN_URL)
suspend fun refreshMicrosoftOAuth2Token(
@Field("refresh_token") code: String,
@Field("scope") scope: String,
@Field("client_id") clientId: String = OAuth2Helper.MICROSOFT_AZURE_APP_ID,
@Field("grant_type") grant_type: String = OAuth2Helper.OAUTH2_GRANT_TYPE_REFRESH_TOKEN): Response<MicrosoftOAuth2TokenResponse>


@GET("https://graph.microsoft.com/v1.0/me/")
suspend fun getMicrosoftAccountInfo(@Header("Authorization") bearerToken: String): Response<MicrosoftAccountResponse>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,10 +95,11 @@ class FlowcryptApiRepository : ApiRepository {
}

override suspend fun getMicrosoftOAuth2Token(requestCode: Long, context: Context,
authorizeCode: String): Result<MicrosoftOAuth2TokenResponse> =
authorizeCode: String, scopes: String):
Result<MicrosoftOAuth2TokenResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
getResult { apiService.getMicrosoftOAuth2Token(authorizeCode) }
getResult { apiService.getMicrosoftOAuth2Token(authorizeCode, scopes) }
}

override suspend fun getMicrosoftAccountInfo(requestCode: Long, context: Context, bearerToken: String): Result<MicrosoftAccountResponse> =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ import android.app.Application
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
import com.flowcrypt.email.api.email.EmailProviderSettingsHelper
import com.flowcrypt.email.api.email.OAuth2Helper
import com.flowcrypt.email.api.email.model.AuthCredentials
import com.flowcrypt.email.api.retrofit.ApiRepository
import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository
import com.flowcrypt.email.api.retrofit.response.base.Result
import kotlinx.coroutines.launch


/**
* @author Denis Bondarenko
* Date: 7/15/20
Expand All @@ -28,20 +30,21 @@ class OAuth2AuthCredentialsViewModel(application: Application) : BaseAndroidView
fun getMicrosoftOAuth2Token(requestCode: Long = 0L, authorizeCode: String) {
viewModelScope.launch {
microsoftOAuth2TokenLiveData.postValue(Result.loading())
val microsoftOAuth2TokenResponseResult = apiRepository.getMicrosoftOAuth2Token(
val microsoftOAuth2TokenResponseResultForProfile = apiRepository.getMicrosoftOAuth2Token(
requestCode = requestCode,
context = getApplication(),
authorizeCode = authorizeCode
authorizeCode = authorizeCode,
scopes = OAuth2Helper.SCOPE_MICROSOFT_OAUTH2_FOR_PROFILE
)

if (microsoftOAuth2TokenResponseResult.status != Result.Status.SUCCESS) {
when (microsoftOAuth2TokenResponseResult.status) {
if (microsoftOAuth2TokenResponseResultForProfile.status != Result.Status.SUCCESS) {
when (microsoftOAuth2TokenResponseResultForProfile.status) {
Result.Status.ERROR -> {
microsoftOAuth2TokenLiveData.postValue(Result.exception(IllegalStateException()))
}

Result.Status.EXCEPTION -> {
microsoftOAuth2TokenLiveData.postValue(Result.exception(microsoftOAuth2TokenResponseResult.exception
microsoftOAuth2TokenLiveData.postValue(Result.exception(microsoftOAuth2TokenResponseResultForProfile.exception
?: RuntimeException()))
}

Expand All @@ -51,21 +54,57 @@ class OAuth2AuthCredentialsViewModel(application: Application) : BaseAndroidView
return@launch
}

val token = microsoftOAuth2TokenResponseResult.data?.accessToken

if (token == null) {
val tokenForProfile = microsoftOAuth2TokenResponseResultForProfile.data?.accessToken
if (tokenForProfile == null) {
microsoftOAuth2TokenLiveData.postValue(Result.exception(NullPointerException("token is null")))
return@launch
}

/*val microsoftAccount = apiRepository.getMicrosoftAccountInfo(
val microsoftAccount = apiRepository.getMicrosoftAccountInfo(
requestCode = requestCode,
context = getApplication(),
bearerToken = token
)*/
bearerToken = tokenForProfile
)

val userEmailAddress = microsoftAccount.data?.userPrincipalName
if (userEmailAddress == null) {
microsoftOAuth2TokenLiveData.postValue(Result.exception(NullPointerException("User email is null")))
return@launch
}

val microsoftOAuth2TokenResponseResultForEmail = apiRepository.getMicrosoftOAuth2Token(
requestCode = requestCode,
context = getApplication(),
authorizeCode = authorizeCode,
scopes = OAuth2Helper.SCOPE_MICROSOFT_OAUTH2_FOR_MAIL
)

if (microsoftOAuth2TokenResponseResultForEmail.status != Result.Status.SUCCESS) {
when (microsoftOAuth2TokenResponseResultForEmail.status) {
Result.Status.ERROR -> {
microsoftOAuth2TokenLiveData.postValue(Result.exception(IllegalStateException()))
}

Result.Status.EXCEPTION -> {
microsoftOAuth2TokenLiveData.postValue(Result.exception(microsoftOAuth2TokenResponseResultForEmail.exception
?: RuntimeException()))
}

else -> {
}
}
return@launch
}

val tokenForEmail = microsoftOAuth2TokenResponseResultForEmail.data?.accessToken

if (tokenForEmail == null) {
microsoftOAuth2TokenLiveData.postValue(Result.exception(NullPointerException("token is null")))
return@launch
}

val recommendAuthCredentials = EmailProviderSettingsHelper.getBaseSettings(
"[email protected]", token)?.copy(useOAuth2 = true)
microsoftAccount.data.userPrincipalName, tokenForEmail)?.copy(useOAuth2 = true)

microsoftOAuth2TokenLiveData.postValue(Result.success(recommendAuthCredentials!!))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,10 @@ import com.sun.mail.util.MailConnectException
import kotlinx.android.synthetic.main.fragment_screenshot_editor.*
import kotlinx.coroutines.launch
import java.net.SocketTimeoutException
import java.util.*
import java.util.regex.Pattern
import javax.mail.AuthenticationFailedException
import kotlin.collections.ArrayList

/**
* @author Denis Bondarenko
Expand Down Expand Up @@ -96,6 +98,7 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,

private var isImapSpinnerRestored: Boolean = false
private var isSmtpSpinnerRestored: Boolean = false
private var uuidForOAuth: String? = null

private val privateKeysViewModel: PrivateKeysViewModel by viewModels()
private val oAuth2AuthCredentialsViewModel: OAuth2AuthCredentialsViewModel by viewModels()
Expand Down Expand Up @@ -128,6 +131,11 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

savedInstanceState?.let {
uuidForOAuth = it.getString(KEY_UUID_FOR_OAUTH)
}

subscribeToCheckAccountSettings()
subscribeToAuthorizeAndSearchBackups()

Expand All @@ -139,11 +147,6 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
}
}

override fun onPause() {
super.onPause()
saveTempCreds()
}

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
initViews(view)
Expand All @@ -153,6 +156,16 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
setupOAuth2AuthCredentialsViewModel()
}

override fun onPause() {
super.onPause()
saveTempCreds()
}

override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putString(KEY_UUID_FOR_OAUTH, uuidForOAuth)
}

override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
when (requestCode) {
REQUEST_CODE_ADD_NEW_ACCOUNT -> when (resultCode) {
Expand Down Expand Up @@ -244,8 +257,26 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
}

fun handleOAuth2Intent(intent: Intent?) {
oAuth2AuthCredentialsViewModel.getMicrosoftOAuth2Token(authorizeCode = intent?.data?.getQueryParameter("code")
?: "")
val state = intent?.data?.getQueryParameter(OAuth2Helper.QUERY_PARAMETER_STATE)
val code = intent?.data?.getQueryParameter(OAuth2Helper.QUERY_PARAMETER_CODE)
if (uuidForOAuth.equals(state)) {
if (code != null) {
oAuth2AuthCredentialsViewModel.getMicrosoftOAuth2Token(authorizeCode = code)
} else {
val error = intent?.data?.getQueryParameter(OAuth2Helper.QUERY_PARAMETER_ERROR)
val errorDescription = intent?.data?.getQueryParameter(OAuth2Helper
.QUERY_PARAMETER_ERROR_DESCRIPTION)

showInfoDialog(
dialogTitle = getString(R.string.error_with_value, error),
dialogMsg = errorDescription,
useLinkify = true
)
}
} else {
showInfoDialog(dialogTitle = "", dialogMsg = getString(R.string.could_not_varify_response,
getString(R.string.support_email)), useLinkify = true)
}
}

private fun initViews(view: View) {
Expand Down Expand Up @@ -342,10 +373,13 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
}

view.findViewById<View>(R.id.buttonSignInWithOutlook)?.setOnClickListener {
val intent = OAuth2Helper.getMicrosoftOAuth2Intent("")
if (intent.resolveActivity(requireContext().packageManager) != null) {
intent.data?.let {
CustomTabsIntent.Builder().build().launchUrl(requireContext(), it)
uuidForOAuth = UUID.randomUUID().toString()
uuidForOAuth?.let { state ->
val intent = OAuth2Helper.getMicrosoftOAuth2Intent(state)
if (intent.resolveActivity(requireContext().packageManager) != null) {
intent.data?.let {
CustomTabsIntent.Builder().build().launchUrl(requireContext(), it)
}
}
}
}
Expand Down Expand Up @@ -781,6 +815,9 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
}

companion object {
private val KEY_UUID_FOR_OAUTH =
GeneralUtil.generateUniqueExtraKey("KEY_UUID_FOR_OAUTH", AddOtherAccountFragment::class.java)

private const val REQUEST_CODE_ADD_NEW_ACCOUNT = 10
private const val REQUEST_CODE_CHECK_PRIVATE_KEYS_FROM_EMAIL = 11
private const val REQUEST_CODE_RETRY_SETTINGS_CHECKING = 12
Expand Down
2 changes: 2 additions & 0 deletions FlowCrypt/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -466,4 +466,6 @@
<string name="connect_your_email_account_using_oauth_2_0">Connect your email account\n using OAuth 2.0</string>
<string name="continue_with_outlook_hotmail">Continue with Outlook, Hotmail</string>
<string name="loading_account_details">Loading the account details &#8230;</string>
<string name="could_not_varify_response">Sorry, we couldn\'t verify the response. Please try again or write to us at %1$s</string>
<string name="error_with_value">Error: %1$s</string>
</resources>

0 comments on commit 40571ca

Please sign in to comment.