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

Prepared Retrofit realization for sending encrypted messages through … #1619

Merged
merged 3 commits into from
Dec 21, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions FlowCrypt/src/main/java/com/flowcrypt/email/Constants.kt
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class Constants {
* The MIME type of PGP keys.
*/
const val MIME_TYPE_PGP_KEY = "application/pgp-keys"
const val MIME_TYPE_JSON = "application/json"
const val MIME_TYPE_BINARY_DATA = "application/octet-stream"
const val MIME_TYPE_RFC822 = "message/rfc822"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@ import android.content.Context
import com.flowcrypt.email.api.retrofit.base.BaseApiRepository
import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel
import com.flowcrypt.email.api.retrofit.request.model.LoginModel
import com.flowcrypt.email.api.retrofit.request.model.MessageUploadRequest
import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel
import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse
import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse
import com.flowcrypt.email.api.retrofit.response.api.LoginResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageReplyTokenResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageUploadResponse
import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse
import com.flowcrypt.email.api.retrofit.response.attester.PubResponse
import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse
Expand All @@ -34,12 +37,12 @@ interface ApiRepository : BaseApiRepository {
/**
* @param context Interface to global information about an application environment.
* @param loginModel An instance of [LoginModel].
* @param tokenId OIDC token.
* @param idToken OIDC token.
*/
suspend fun login(
context: Context,
loginModel: LoginModel,
tokenId: String
idToken: String
): Result<LoginResponse>

/**
Expand Down Expand Up @@ -121,10 +124,10 @@ interface ApiRepository : BaseApiRepository {
*
* @param context Interface to global information about an application environment.
* @param ekmUrl key_manager_url from [OrgRules].
* @param tokenId OIDC token.
* @param idToken OIDC token.
*/
suspend fun getPrivateKeysViaEkm(
context: Context, ekmUrl: String, tokenId: String
context: Context, ekmUrl: String, idToken: String
): Result<EkmPrivateKeysResponse>

/**
Expand All @@ -134,4 +137,34 @@ interface ApiRepository : BaseApiRepository {
* @param domain A company domain.
*/
suspend fun checkFes(context: Context, domain: String): Result<FesServerResponse>

/**
* Grab a reply token before uploading a password protected message
*
* @param context Interface to global information about an application environment.
* @param domain A company domain.
* @param domain OIDC token.
*/
suspend fun getReplyTokenForPasswordProtectedMsg(
context: Context,
domain: String,
idToken: String
): Result<MessageReplyTokenResponse>

/**
* Upload a password protected message to a web portal
*
* @param context Interface to global information about an application environment.
* @param domain A company domain.
* @param idToken OIDC token.
* @param messageUploadRequest an instance of [MessageUploadRequest]
* @param msg an encrypted message that will be sent
*/
suspend fun uploadPasswordProtectedMsgToWebPortal(
context: Context,
domain: String,
idToken: String,
messageUploadRequest: MessageUploadRequest,
msg: String
): Result<MessageUploadResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,15 @@ import com.flowcrypt.email.api.retrofit.response.api.DomainOrgRulesResponse
import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse
import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse
import com.flowcrypt.email.api.retrofit.response.api.LoginResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageReplyTokenResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageUploadResponse
import com.flowcrypt.email.api.retrofit.response.api.PostHelpFeedbackResponse
import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse
import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse
import com.flowcrypt.email.api.retrofit.response.oauth2.MicrosoftOAuth2TokenResponse
import com.google.gson.JsonObject
import okhttp3.MultipartBody
import okhttp3.RequestBody
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.Response
Expand All @@ -29,7 +33,9 @@ import retrofit2.http.Field
import retrofit2.http.FormUrlEncoded
import retrofit2.http.GET
import retrofit2.http.Header
import retrofit2.http.Multipart
import retrofit2.http.POST
import retrofit2.http.Part
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Streaming
Expand Down Expand Up @@ -143,7 +149,7 @@ interface ApiService {
* @param body POJO model for requests
*/
@POST(BuildConfig.API_URL + "account/login")
suspend fun postLogin(@Body body: LoginModel, @Header("Authorization") tokenId: String):
suspend fun postLogin(@Body body: LoginModel, @Header("Authorization") idToken: String):
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd rename here and in other methods of this class idToken into authorization, because it actually contains not only token itself but the whole content of the header.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree 👍

Response<LoginResponse>

/**
Expand Down Expand Up @@ -200,7 +206,7 @@ interface ApiService {
@GET
suspend fun getPrivateKeysViaEkm(
@Url ekmUrl: String,
@Header("Authorization") tokenId: String
@Header("Authorization") idToken: String
): Response<EkmPrivateKeysResponse>

/**
Expand All @@ -214,4 +220,25 @@ interface ApiService {
*/
@GET()
suspend fun isAvailable(@Url url: String): Response<ResponseBody>

/**
* This method grabs a reply token before uploading a password protected message
*/
@POST("https://fes.{domain}/api/v1/message/new-reply-token")
suspend fun getReplyTokenForPasswordProtectedMsg(
@Path("domain") domain: String,
@Header("Authorization") idToken: String
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above - rename

): Response<MessageReplyTokenResponse>

/**
* This method uploads a password protected message to a web portal
*/
@Multipart
@POST("https://fes.{domain}/api/v1/message")
suspend fun uploadPasswordProtectedMsgToWebPortal(
@Path("domain") domain: String,
@Header("Authorization") idToken: String,
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above - rename

@Part("details") details: RequestBody,
@Part content: MultipartBody.Part
): Response<MessageUploadResponse>
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,17 @@
package com.flowcrypt.email.api.retrofit

import android.content.Context
import com.flowcrypt.email.Constants
import com.flowcrypt.email.R
import com.flowcrypt.email.api.retrofit.request.model.InitialLegacySubmitModel
import com.flowcrypt.email.api.retrofit.request.model.LoginModel
import com.flowcrypt.email.api.retrofit.request.model.MessageUploadRequest
import com.flowcrypt.email.api.retrofit.request.model.TestWelcomeModel
import com.flowcrypt.email.api.retrofit.response.api.EkmPrivateKeysResponse
import com.flowcrypt.email.api.retrofit.response.api.FesServerResponse
import com.flowcrypt.email.api.retrofit.response.api.LoginResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageReplyTokenResponse
import com.flowcrypt.email.api.retrofit.response.api.MessageUploadResponse
import com.flowcrypt.email.api.retrofit.response.attester.InitialLegacySubmitResponse
import com.flowcrypt.email.api.retrofit.response.attester.PubResponse
import com.flowcrypt.email.api.retrofit.response.attester.TestWelcomeResponse
Expand All @@ -24,10 +28,14 @@ import com.flowcrypt.email.api.wkd.WkdClient
import com.flowcrypt.email.extensions.kotlin.isValidEmail
import com.flowcrypt.email.extensions.kotlin.isValidLocalhostEmail
import com.flowcrypt.email.extensions.org.bouncycastle.openpgp.armor
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import okhttp3.OkHttpClient
import okhttp3.RequestBody.Companion.toRequestBody
import okhttp3.ResponseBody.Companion.toResponseBody
import org.pgpainless.algorithm.EncryptionPurpose
import org.pgpainless.key.info.KeyRingInfo
Expand All @@ -49,14 +57,14 @@ class FlowcryptApiRepository : ApiRepository {
override suspend fun login(
context: Context,
loginModel: LoginModel,
tokenId: String
idToken: String
): Result<LoginResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
getResult(
context = context,
expectedResultClass = LoginResponse::class.java
) { apiService.postLogin(loginModel, "Bearer $tokenId") }
) { apiService.postLogin(loginModel, "Bearer $idToken") }
}

override suspend fun getDomainOrgRules(
Expand Down Expand Up @@ -205,15 +213,15 @@ class FlowcryptApiRepository : ApiRepository {
override suspend fun getPrivateKeysViaEkm(
context: Context,
ekmUrl: String,
tokenId: String
idToken: String
): Result<EkmPrivateKeysResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
val url = if (ekmUrl.endsWith("/")) ekmUrl else "$ekmUrl/"
getResult(
context = context,
expectedResultClass = EkmPrivateKeysResponse::class.java
) { apiService.getPrivateKeysViaEkm("${url}v1/keys/private", "Bearer $tokenId") }
) { apiService.getPrivateKeysViaEkm("${url}v1/keys/private", "Bearer $idToken") }
}

override suspend fun checkFes(context: Context, domain: String): Result<FesServerResponse> =
Expand All @@ -238,4 +246,48 @@ class FlowcryptApiRepository : ApiRepository {
expectedResultClass = FesServerResponse::class.java
) { apiService.checkFes(domain) }
}

override suspend fun getReplyTokenForPasswordProtectedMsg(
context: Context,
domain: String,
idToken: String
): Result<MessageReplyTokenResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
getResult(
context = context,
expectedResultClass = MessageReplyTokenResponse::class.java
) { apiService.getReplyTokenForPasswordProtectedMsg(domain, "Bearer $idToken") }
}

override suspend fun uploadPasswordProtectedMsgToWebPortal(
context: Context,
domain: String,
idToken: String,
messageUploadRequest: MessageUploadRequest,
msg: String
): Result<MessageUploadResponse> =
withContext(Dispatchers.IO) {
val apiService = ApiHelper.getInstance(context).retrofit.create(ApiService::class.java)
getResult(
context = context,
expectedResultClass = MessageUploadResponse::class.java
) {
val details = GsonBuilder().create().toJson(messageUploadRequest)
.toRequestBody(Constants.MIME_TYPE_JSON.toMediaTypeOrNull())

val content = MultipartBody.Part.createFormData(
"content",
"content",
msg.toByteArray().toRequestBody(Constants.MIME_TYPE_BINARY_DATA.toMediaTypeOrNull())
)

apiService.uploadPasswordProtectedMsgToWebPortal(
domain = domain,
idToken = "Bearer $idToken",
details = details,
content = content
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: DenBond7
*/

package com.flowcrypt.email.api.retrofit.request.model

/**
* @author Denis Bondarenko
* Date: 12/20/21
* Time: 12:19 PM
* E-mail: [email protected]
*/
data class MessageUploadRequest(
val associateReplyToken: String,
val from: String,
val to: List<String>,
val cc: List<String> = emptyList(),
val bcc: List<String> = emptyList()
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: DenBond7
*/

package com.flowcrypt.email.api.retrofit.response.api

import android.os.Parcel
import android.os.Parcelable
import com.flowcrypt.email.api.retrofit.response.base.ApiError
import com.flowcrypt.email.api.retrofit.response.base.ApiResponse
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

/**
* @author Denis Bondarenko
* Date: 12/20/21
* Time: 12:10 PM
* E-mail: [email protected]
*/
data class MessageReplyTokenResponse(
@SerializedName("error")
@Expose override val apiError: ApiError? = null,
@Expose val replyToken: String? = null
) : ApiResponse {
constructor(parcel: Parcel) : this(
parcel.readParcelable(ApiError::class.java.classLoader),
parcel.readString()
)

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(apiError, flags)
parcel.writeString(replyToken)
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<MessageReplyTokenResponse> {
override fun createFromParcel(parcel: Parcel) = MessageReplyTokenResponse(parcel)
override fun newArray(size: Int): Array<MessageReplyTokenResponse?> = arrayOfNulls(size)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: DenBond7
*/

package com.flowcrypt.email.api.retrofit.response.api

import android.os.Parcel
import android.os.Parcelable
import com.flowcrypt.email.api.retrofit.response.base.ApiError
import com.flowcrypt.email.api.retrofit.response.base.ApiResponse
import com.google.gson.annotations.Expose
import com.google.gson.annotations.SerializedName

/**
* @author Denis Bondarenko
* Date: 12/20/21
* Time: 12:34 PM
* E-mail: [email protected]
*/
data class MessageUploadResponse(
@SerializedName("error")
@Expose override val apiError: ApiError? = null,
@Expose val url: String? = null
) : ApiResponse {
constructor(parcel: Parcel) : this(
parcel.readParcelable(ApiError::class.java.classLoader),
parcel.readString()
)

override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeParcelable(apiError, flags)
parcel.writeString(url)
}

override fun describeContents(): Int {
return 0
}

companion object CREATOR : Parcelable.Creator<MessageUploadResponse> {
override fun createFromParcel(parcel: Parcel) = MessageUploadResponse(parcel)
override fun newArray(size: Int): Array<MessageUploadResponse?> = arrayOfNulls(size)
}
}
Loading