Skip to content

Commit

Permalink
Added an ability to handle errors when we fetch access_token.| #716
Browse files Browse the repository at this point in the history
  • Loading branch information
DenBond7 committed Aug 14, 2020
1 parent d9c956c commit 36cf32b
Show file tree
Hide file tree
Showing 9 changed files with 136 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,23 @@ class OAuth2Helper {
const val MICROSOFT_AZURE_APP_ID = "3be51534-5f76-4970-9a34-40ef197aa018"
const val MICROSOFT_OAUTH2_SCHEMA = "msauth"

fun getMicrosoftAuthorizationRequest(): AuthorizationRequest {
fun getMicrosoftAuthorizationRequest(loginHint: String? = null): AuthorizationRequest {
val configuration = AuthorizationServiceConfiguration(Uri.parse(MICROSOFT_OAUTH2_AUTHORIZE_URL), Uri.parse(MICROSOFT_OAUTH2_TOKEN_URL))

return AuthorizationRequest.Builder(
val request = AuthorizationRequest.Builder(
configuration,
MICROSOFT_AZURE_APP_ID,
ResponseTypeValues.CODE,
Uri.parse(MICROSOFT_REDIRECT_URI))
.setResponseMode(AuthorizationRequest.ResponseMode.QUERY)
.setPrompt(AuthorizationRequest.Prompt.SELECT_ACCOUNT)
.setScope("profile email $SCOPE_MICROSOFT_OAUTH2_FOR_MAIL")
.build()

if (loginHint?.isNotEmpty() == true) {
request.setLoginHint(loginHint)
}

return request.build()
}

val SUPPORTED_SCHEMAS = listOf(MICROSOFT_OAUTH2_SCHEMA)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ class FlowcryptApiRepository : ApiRepository {
Result<MicrosoftOAuth2TokenResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
getResult { apiService.getMicrosoftOAuth2Token(authorizeCode, scopes, codeVerifier) }
getResult(
context = context,
expectedResultClass = MicrosoftOAuth2TokenResponse::class.java
) {
apiService.getMicrosoftOAuth2Token(authorizeCode, scopes, codeVerifier)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,16 @@

package com.flowcrypt.email.api.retrofit.base

import android.content.Context
import com.flowcrypt.email.api.retrofit.ApiHelper
import com.flowcrypt.email.api.retrofit.response.base.ApiError
import com.flowcrypt.email.api.retrofit.response.base.ApiResponse
import com.flowcrypt.email.api.retrofit.response.base.Result
import com.flowcrypt.email.util.exception.ApiException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.ResponseBody
import retrofit2.Converter
import retrofit2.Response

/**
Expand All @@ -21,7 +27,10 @@ interface BaseApiRepository {
/**
* Base implementation for the API calls
*/
suspend fun <T> getResult(requestCode: Long = 0L, call: suspend () -> Response<T>): Result<T> {
suspend fun <T> getResult(requestCode: Long = 0L,
context: Context? = null,
expectedResultClass: Class<T>? = null,
call: suspend () -> Response<T>): Result<T> {
return try {
val response = call()
if (response.isSuccessful) {
Expand All @@ -40,11 +49,39 @@ interface BaseApiRepository {
Result.exception(error = ApiException(ApiError(response.code(), response.message())), requestCode = requestCode)
}
} else {
Result.exception(error = ApiException(ApiError(response.code(), response.message())), requestCode = requestCode)
val apiResponseWithError = parseError(context, expectedResultClass, response)
if (apiResponseWithError != null) {
if (apiResponseWithError is ApiResponse) {
return if (apiResponseWithError.apiError != null) {
Result.error(data = apiResponseWithError, requestCode = requestCode)
} else {
Result.exception(error = ApiException(ApiError(response.code(), response.message())), requestCode = requestCode)
}
} else {
Result.exception(error = ApiException(ApiError(response.code(), response.message())), requestCode = requestCode)
}
} else {
Result.exception(error = ApiException(ApiError(response.code(), response.message())), requestCode = requestCode)
}
}
} catch (e: Exception) {
e.printStackTrace()
Result.exception(error = e, requestCode = requestCode)
}
}

private suspend fun <T> parseError(context: Context?, exceptedClass: Class<T>?, response: Response<T>): T? =
withContext(Dispatchers.IO) {
context ?: return@withContext null
exceptedClass ?: return@withContext null
try {
val errorConverter: Converter<ResponseBody, T> = ApiHelper.getInstance(context).retrofit.responseBodyConverter<T>(exceptedClass, arrayOfNulls(0))
response.errorBody()?.let {
return@withContext errorConverter.convert(it)
}
} catch (e: Exception) {
e.printStackTrace()
}
return@withContext null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -24,17 +24,36 @@ data class MicrosoftOAuth2TokenResponse constructor(
@SerializedName("expires_in") @Expose val expiresIn: Long? = null,
@Expose val scope: String? = null,
@SerializedName("refresh_token") @Expose val refreshToken: String? = null,
@SerializedName("id_token") @Expose val idToken: String? = null
@SerializedName("id_token") @Expose val idToken: String? = null,
@Expose val error: String? = null,
@SerializedName("error_description") @Expose val errorDescription: String? = null,
@SerializedName("error_codes") @Expose val errorCodes: IntArray? = null,
@Expose val timestamp: String? = null,
@SerializedName("trace_id") @Expose val traceId: String? = null,
@SerializedName("correlation_id") @Expose val correlationId: String? = null
) : ApiResponse {
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.readValue(Long::class.java.classLoader) as? Long,
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.readString(),
parcel.createIntArray(),
parcel.readString(),
parcel.readString(),
parcel.readString())

override val apiError: ApiError? = null
override val apiError: ApiError?
get() = if (error != null) {
ApiError(
code = errorCodes?.firstOrNull(),
msg = error + "\n" + errorDescription,
internal = error
)
} else null

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(accessToken)
Expand All @@ -43,12 +62,61 @@ data class MicrosoftOAuth2TokenResponse constructor(
parcel.writeString(scope)
parcel.writeString(refreshToken)
parcel.writeString(idToken)
parcel.writeString(error)
parcel.writeString(errorDescription)
parcel.writeIntArray(errorCodes)
parcel.writeString(timestamp)
parcel.writeString(traceId)
parcel.writeString(correlationId)
}

override fun describeContents(): Int {
return 0
}

override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false

other as MicrosoftOAuth2TokenResponse

if (accessToken != other.accessToken) return false
if (tokenType != other.tokenType) return false
if (expiresIn != other.expiresIn) return false
if (scope != other.scope) return false
if (refreshToken != other.refreshToken) return false
if (idToken != other.idToken) return false
if (error != other.error) return false
if (errorDescription != other.errorDescription) return false
if (errorCodes != null) {
if (other.errorCodes == null) return false
if (!errorCodes.contentEquals(other.errorCodes)) return false
} else if (other.errorCodes != null) return false
if (timestamp != other.timestamp) return false
if (traceId != other.traceId) return false
if (correlationId != other.correlationId) return false
if (apiError != other.apiError) return false

return true
}

override fun hashCode(): Int {
var result = accessToken?.hashCode() ?: 0
result = 31 * result + (tokenType?.hashCode() ?: 0)
result = 31 * result + (expiresIn?.hashCode() ?: 0)
result = 31 * result + (scope?.hashCode() ?: 0)
result = 31 * result + (refreshToken?.hashCode() ?: 0)
result = 31 * result + (idToken?.hashCode() ?: 0)
result = 31 * result + (error?.hashCode() ?: 0)
result = 31 * result + (errorDescription?.hashCode() ?: 0)
result = 31 * result + (errorCodes?.contentHashCode() ?: 0)
result = 31 * result + (timestamp?.hashCode() ?: 0)
result = 31 * result + (traceId?.hashCode() ?: 0)
result = 31 * result + (correlationId?.hashCode() ?: 0)
result = 31 * result + (apiError?.hashCode() ?: 0)
return result
}

companion object CREATOR : Parcelable.Creator<MicrosoftOAuth2TokenResponse> {
override fun createFromParcel(parcel: Parcel): MicrosoftOAuth2TokenResponse {
return MicrosoftOAuth2TokenResponse(parcel)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.flowcrypt.email.api.oauth.OAuth2Helper
import com.flowcrypt.email.api.retrofit.ApiRepository
import com.flowcrypt.email.api.retrofit.FlowcryptApiRepository
import com.flowcrypt.email.api.retrofit.response.base.Result
import com.flowcrypt.email.util.exception.ApiException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
Expand Down Expand Up @@ -52,7 +53,7 @@ class OAuth2AuthCredentialsViewModel(application: Application) : BaseAndroidView
if (response.status != Result.Status.SUCCESS) {
when (response.status) {
Result.Status.ERROR -> {
microsoftOAuth2TokenLiveData.postValue(Result.exception(IllegalStateException()))
microsoftOAuth2TokenLiveData.postValue(Result.exception(ApiException(response.data?.apiError)))
}

Result.Status.EXCEPTION -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ class CreatePrivateKeyActivity : BasePassPhraseManagerActivity() {

it.exception?.let { exception ->
if (exception is ApiException) {
showSnackbar(rootView, exception.apiError.msg ?: it.javaClass.simpleName,
showSnackbar(rootView, exception.apiError?.msg ?: it.javaClass.simpleName,
getString(R.string.retry), Snackbar.LENGTH_LONG, View.OnClickListener {
onConfirmPassPhraseSuccess()
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,11 @@ class AddOtherAccountFragment : BaseSingInFragment(), ProgressBehaviour,
useLinkify = true
)
} else {
return@let
showInfoDialog(
dialogTitle = getString(R.string.oauth_error),
dialogMsg = getString(R.string.could_not_verify_response),
useLinkify = true
)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,5 @@ import java.io.IOException
* Time: 18:39
* E-mail: [email protected]
*/
class ApiException(val apiError: ApiError) :
IOException("API error: code = " + apiError.code + ", message = " + apiError.msg)
class ApiException(val apiError: ApiError?) :
IOException("API error: code = " + apiError?.code + ", message = " + apiError?.msg)
1 change: 1 addition & 0 deletions FlowCrypt/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -468,4 +468,5 @@
<string name="loading_account_details">Loading the account details &#8230;</string>
<string name="could_not_verify_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>
<string name="oauth_error">OAuth error</string>
</resources>

0 comments on commit 36cf32b

Please sign in to comment.