Skip to content

Commit

Permalink
Release Api-Client 3.0.0
Browse files Browse the repository at this point in the history
  • Loading branch information
iProovBot committed Jan 10, 2024
1 parent 110a6e6 commit dba9162
Show file tree
Hide file tree
Showing 11 changed files with 113 additions and 81 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# iProov Android API Client v2.0.1
# iProov Android API Client v3.0.0

## 📖 Table of contents

Expand Down Expand Up @@ -83,7 +83,7 @@ private val uiScope = CoroutineScope(Dispatchers.Main + uiSupervisorJob)

val apiClient = ApiClientRetrofit(
context = this,
baseUrl = "https://eu.rp.secure.iproov.me/api/v2/",
baseUrl = "{{ your base url }}",
logLevel = HttpLoggingInterceptor.Level.BODY,
apiKey = "{{ your API key }}",
secret = "{{ your API secret }}")
Expand Down Expand Up @@ -111,6 +111,7 @@ src/main/assets/ directory that looks like this:

~~~
{
"base_url": <YOUR_URL>,
"api_key": <YOUR_CLIENT_ID>,
"secret": <YOUR_SECRET>
}
Expand Down
2 changes: 1 addition & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ plugins {

subprojects {
group="com.iproov.android-api-client"
version = "2.0.1"
version = "3.0.0"
}

allprojects {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,7 @@ class DemoActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_demo)

demoKotlinRetrofitApiCalls()

demoKotlinFuelApiCalls()
}

Expand All @@ -45,12 +43,13 @@ class DemoActivity : AppCompatActivity() {
val jsonStr = "secrets.json".jsonFile(this)
val json = JSONObject(jsonStr ?: "{}")

val url: String = json.optString("base_url")
val apiKey: String = json.optString("api_key")
val secret: String = json.optString("secret")

val apiClient = ApiClientRetrofit(
context = this,
baseUrl = "https://eu.rp.secure.iproov.me/api/v2/",
baseUrl = url,
logLevel = okhttp3.logging.HttpLoggingInterceptor.Level.BODY,
apiKey = apiKey,
secret = secret
Expand Down Expand Up @@ -93,12 +92,13 @@ class DemoActivity : AppCompatActivity() {
val jsonStr = "secrets.json".jsonFile(this)
val json = JSONObject(jsonStr ?: "{}")

val url: String = json.optString("base_url")
val apiKey = json.optString("api_key")
val secret = json.optString("secret")

val apiClient = ApiClientFuel(
context = this,
baseUrl = "https://eu.rp.secure.iproov.me/api/v2/",
baseUrl = url,
apiKey = apiKey,
secret = secret
)
Expand All @@ -120,7 +120,7 @@ class DemoActivity : AppCompatActivity() {

val userID2 = "${"fueldemo".datetime()}@example.com"
val tokenInvalidating = withContext(Dispatchers.IO) {
apiClient.getToken(AssuranceType.GENUINE_PRESENCE, com.iproov.androidapiclient.ClaimType.ENROL, userID2)
apiClient.getToken(AssuranceType.GENUINE_PRESENCE, ClaimType.ENROL, userID2)
}
val invalidateResult = apiClient.invalidate(tokenInvalidating, "Test reason")
Log.i("Main", "success (Kotlin Fuel, invalidate) = $tokenInvalidating claimAborted=${invalidateResult.claimAborted} userInformed=${invalidateResult.userInformed}")
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.iproov.androidapiclient

enum class ClaimType {
ENROL, VERIFY
enum class ClaimType(val path: String) {
ENROL("enrol"),
VERIFY("verify")
}

enum class PhotoSource(val code: String) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.iproov.androidapiclient

import android.content.Context
import android.util.Log
import java.io.File
import java.io.IOException
import java.nio.charset.Charset
import java.text.SimpleDateFormat
Expand Down Expand Up @@ -31,11 +29,15 @@ fun String.jsonFile(context: Context): String? =
null
}

fun <K: Any, V: Any> Map<K, V>.merge(to: Map<K, V>?) = run {
fun <K: Any, V: Any?> Map<K, V>.merge(to: Map<K, V>?) = run {
val result = mutableMapOf<K, V>()
this.forEach{ result[it.key] = it.value }
to?.forEach{ result[it.key] = it.value }
result
}

inline val String.endingWithSlash: String
get() = if (endsWith("/")) this else "$this/"

inline val String.saferUrl: String
get() = if (endingWithSlash.endsWith("api/v2/")) endingWithSlash else "${endingWithSlash}api/v2/"
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.iproov.androidapiclient.ClaimType
import com.iproov.androidapiclient.DemonstrationPurposesOnly
import com.iproov.androidapiclient.PhotoSource
import com.iproov.androidapiclient.merge
import com.iproov.androidapiclient.saferUrl
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream
Expand Down Expand Up @@ -46,19 +47,20 @@ class ApiClientFuel(
* Obtain a token, given a ClaimType and userID
*/
@Throws(FuelError::class)
suspend fun getToken(assuranceType: AssuranceType, type: ClaimType, userID: String, options: Map<String, Any>? = null, resource: String = appID): String =
suspend fun getToken(assuranceType: AssuranceType, type: ClaimType, userID: String, riskProfile: String? = null, extras: Map<String, Any>? = null, resource: String = appID): String =

fuelInstance
.post("${baseUrl.safelUrl}claim/${type.toString().toLowerCase()}/token")
.post("${baseUrl.saferUrl}claim/${type.toString().toLowerCase()}/token")
.header("Content-Type" to "application/json")
.body(Gson().toJson(mapOf(
"api_key" to apiKey,
"secret" to secret,
"resource" to resource,
"client" to "android",
"user_id" to userID,
"assurance_type" to assuranceType.backendName
).merge(options)))
"assurance_type" to assuranceType.backendName,
"riskProfile" to riskProfile
).merge(extras)))
.awaitObjectResult(jsonDeserializer())
.let { response ->
when (response) {
Expand All @@ -74,7 +76,7 @@ class ApiClientFuel(
suspend fun enrolPhoto(token: String, image: Bitmap, source: PhotoSource): String =

fuelInstance
.upload("${baseUrl.safelUrl}claim/enrol/image", Method.POST, listOf(
.upload("${baseUrl.saferUrl}claim/enrol/image", Method.POST, listOf(
"api_key" to apiKey,
"secret" to secret,
"rotation" to "0",
Expand All @@ -96,16 +98,16 @@ class ApiClientFuel(
* Validate given a token and userID
*/
@Throws(FuelError::class)
suspend fun validate(token: String, userID: String): ValidationResult =
suspend fun validate(token: String, type: ClaimType, userID: String, riskProfile: String? = null): ValidationResult =

fuelInstance
.post("${baseUrl.safelUrl}claim/verify/validate")
.post("${baseUrl.saferUrl}claim/${type.path}/validate")
.body(Gson().toJson(mapOf(
"api_key" to apiKey,
"secret" to secret,
"user_id" to userID,
"token" to token,
"ip" to "127.0.0.1",
"risk_profile" to riskProfile,
"client" to "android"
)))
.awaitObjectResult(jsonDeserializer())
Expand All @@ -123,10 +125,8 @@ class ApiClientFuel(
suspend fun invalidate(token: String, reason: String): InvalidationResult =

fuelInstance
.post("${baseUrl.safelUrl}claim/$token/invalidate")
.post("${baseUrl.saferUrl}claim/$token/invalidate")
.body(Gson().toJson(mapOf(
// "api_key" to apiKey,
// "secret" to secret,
"reason" to reason
)))
.awaitObjectResult(jsonDeserializer())
Expand All @@ -148,11 +148,11 @@ class ApiClientFuel(
* - Get a verify token for the user ID
*/
@DemonstrationPurposesOnly
suspend fun ApiClientFuel.enrolPhotoAndGetVerifyToken(userID: String, image: Bitmap, assuranceType: AssuranceType, source: PhotoSource, options: Map<String, Any>? = null): String =
suspend fun ApiClientFuel.enrolPhotoAndGetVerifyToken(userID: String, image: Bitmap, assuranceType: AssuranceType, source: PhotoSource, riskProfile: String? = null, options: Map<String, Any>? = null): String =

getToken(assuranceType, ClaimType.ENROL, userID).let { token1 ->
enrolPhoto(token1, image, source)
getToken(assuranceType, ClaimType.VERIFY, userID, options)
getToken(assuranceType, ClaimType.VERIFY, userID, riskProfile, options)
}

fun Bitmap.jpegImageStream(): InputStream =
Expand All @@ -171,8 +171,13 @@ fun JSONObject.toValidationResult(): ValidationResult =
ValidationResult(
this.getBoolean("passed"),
this.getString("token"),
this.getString("type"),
this.getBoolean("frame_available"),
this.getOrNullString("frame")?.base64DecodeBitmap(),
this.optJSONObject("result")?.getOrNullString("reason")
this.getOrNullString("reason"),
this.getOrNullString("risk_profile"),
this.getOrNullString("assurance_type"),
this.getJSONObject("signals")
)

/**
Expand All @@ -185,12 +190,6 @@ fun JSONObject.toInvalidationResult(): InvalidationResult =
this.getBoolean("user_informed")
)

private inline val String.endingWithSlash: String
get() = if (endsWith("/")) this else "$this/"

private inline val String.safelUrl: String
get() = if (endingWithSlash.endsWith("api/v2/")) endingWithSlash else "${endingWithSlash}api/v2/"

/**
* Base64 decode to Bitmap mapping
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.iproov.androidapiclient.kotlinfuel

import android.graphics.Bitmap
import org.json.JSONObject

data class ValidationResult (
val isPassed: Boolean,
val token: String,
val type: String,
val isFrameAvailable: Boolean,
val frame: Bitmap?,
val failureReason: String?
val failureReason: String?,
val riskProfile: String?,
val assuranceType: String?,
val signals: JSONObject?
)
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package com.iproov.androidapiclient.kotlinretrofit

import android.content.Context
import android.graphics.Bitmap
import android.util.Log
import com.google.gson.GsonBuilder
import com.iproov.androidapiclient.AssuranceType
import com.iproov.androidapiclient.ClaimType
import com.iproov.androidapiclient.DemonstrationPurposesOnly
import com.iproov.androidapiclient.PhotoSource
import com.iproov.androidapiclient.saferUrl
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
import retrofit2.Retrofit
Expand Down Expand Up @@ -42,14 +44,14 @@ class ApiClientRetrofit(
.readTimeout(30, TimeUnit.SECONDS)
.build()
)
.baseUrl(baseUrl)
.baseUrl(baseUrl.saferUrl)
.build()
.create(ApiService::class.java)
}

private fun converterFactory() : GsonConverterFactory {
val gson = GsonBuilder()
.registerTypeAdapter(TokenRequest::class.java, TokenRequestSerializer())
.registerTypeAdapter(GetTokenRequest::class.java, TokenRequestSerializer())
.create()

return GsonConverterFactory.create(gson)
Expand All @@ -61,18 +63,16 @@ class ApiClientRetrofit(
/**
* Obtain a token, given a ClaimType and userID
*/
suspend fun getToken(assuranceType: AssuranceType, type: ClaimType, userID: String, options: Map<String, Any>? = null, resource: String = appID): Token =

suspend fun getToken(assuranceType: AssuranceType, type: ClaimType, userID: String, riskProfile: String = "", resource: String = appID): GetTokenResponse =
api.getAccessToken(
type.toString().lowercase(),
TokenRequest(apiKey, secret, resource, userID, assuranceType = assuranceType.backendName, options = options)
GetTokenRequest(apiKey, secret, resource, assuranceType.backendName, userID, riskProfile)
)

/**
* Enrol with a Photo, given a token and a PhotoSource
*/
suspend fun enrolPhoto(token: String, image: Bitmap, source: PhotoSource): Token =

suspend fun enrolPhoto(token: String, image: Bitmap, source: PhotoSource): PhotoEnrolResponse =
api.enrolPhoto(
apiKey.toMultipartRequestBody(),
secret.toMultipartRequestBody(),
Expand All @@ -92,9 +92,17 @@ class ApiClientRetrofit(
/**
* Validate given a token and userID
*/
suspend fun validate(token: String, userID: String): ValidationResult =

api.validate(ValidationRequest(apiKey, secret, userID, token, "127.0.0.1", "android"))
suspend fun validate(token: String, claimType: ClaimType, userID: String, riskProfile: String = ""): ValidationResult =
api.validate(
claimType.path,
ValidationRequest(
apiKey,
secret,
userID,
token,
riskProfile
)
)


/**
Expand All @@ -120,11 +128,11 @@ fun String.toMultipartRequestBody(): RequestBody =
* - Get a verify token for the user ID
*/
@DemonstrationPurposesOnly
suspend fun ApiClientRetrofit.enrolPhotoAndGetVerifyToken(userID: String, image: Bitmap, source: PhotoSource, options: Map<String, Any>? = null): String =
suspend fun ApiClientRetrofit.enrolPhotoAndGetVerifyToken(userID: String, image: Bitmap, source: PhotoSource, riskProfile: String = ""): String =

getToken(AssuranceType.GENUINE_PRESENCE, ClaimType.ENROL, userID).token.let { token1 ->
getToken(AssuranceType.GENUINE_PRESENCE, ClaimType.ENROL, userID, riskProfile).token.let { token1 ->
enrolPhoto(token1, image, source).let {
getToken(AssuranceType.GENUINE_PRESENCE, ClaimType.VERIFY, userID, options).token
getToken(AssuranceType.GENUINE_PRESENCE, ClaimType.VERIFY, userID, riskProfile).token
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
package com.iproov.androidapiclient.kotlinretrofit

import okhttp3.MultipartBody;
import kotlinx.coroutines.Deferred
import okhttp3.RequestBody
import retrofit2.http.*

interface ApiService {

@POST("claim/{claimType}/token")
suspend fun getAccessToken(@Path("claimType") claimType: String, @Body tokenRequest: TokenRequest): Token
suspend fun getAccessToken(@Path("claimType") claimType: String, @Body getTokenRequest: GetTokenRequest): GetTokenResponse

@Multipart
@POST("claim/enrol/image")
Expand All @@ -19,10 +18,10 @@ interface ApiService {
@Part("token") token: RequestBody,
@Part image: MultipartBody.Part,
@Part("source") source: RequestBody
): Token
): PhotoEnrolResponse

@POST("claim/verify/validate")
suspend fun validate(@Body validationRequest: ValidationRequest): ValidationResult
@POST("claim/{claimType}/validate")
suspend fun validate(@Path("claimType") claimType: String, @Body validationRequest: ValidationRequest): ValidationResult

@POST("claim/{token}/invalidate")
suspend fun invalidate(@Path("token") token: String, @Body invalidationRequest: InvalidationRequest): InvalidationResult
Expand Down
Loading

0 comments on commit dba9162

Please sign in to comment.