Skip to content

Commit

Permalink
Modify public api to match JS SDK
Browse files Browse the repository at this point in the history
  • Loading branch information
Ian Bird committed Apr 5, 2024
1 parent 8b17816 commit 1f945d4
Show file tree
Hide file tree
Showing 7 changed files with 88 additions and 50 deletions.
5 changes: 0 additions & 5 deletions dev-app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,6 @@
<!-- Optional override for the default API URL. -->
<meta-data android:name="uid2_api_url" android:value="https://operator-integ.uidapi.com"/>

<!-- Metadata Required for Client Side Integration -->
<!-- This information will be provided when you register your app with the UID2 operator. -->
<meta-data android:name="uid2_api_public_key" android:value="UID2-X-L-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtXJdTSZAYHvoRDWiehMHoWF1BNPuqLs5w2ZHiAZ1IJc7O4/z0ojPTB0V+KYX/wxQK0hxx6kxCvHj335eI/ZQsQ=="/>
<meta-data android:name="uid2_api_subscription_id" android:value="4WvryDGbR5"/>

<!-- Metadata Required for Server Side Integration -->
<!-- This information is only consumed by the DevApp (not the SDK) to simulate a server side integration. -->
<meta-data android:name="uid2_api_key" android:value=""/>
Expand Down
18 changes: 16 additions & 2 deletions dev-app/src/main/java/com/uid2/dev/ui/MainScreenViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,12 @@ class MainScreenViewModel(
try {
if (action.clientSide) {
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(IdentityRequest.Email(action.address), onGenerateResult)
manager.generateIdentity(
IdentityRequest.Email(action.address),
SUBSCRIPTION_ID,
PUBLIC_KEY,
onGenerateResult,
)
} else {
// We're going to generate the identity as if we've obtained it via a backend service.
api.generateIdentity(action.address, EMAIL)?.let {
Expand All @@ -113,7 +118,12 @@ class MainScreenViewModel(
try {
if (action.clientSide) {
// Generate the identity via Client Side Integration (client side token generation).
manager.generateIdentity(IdentityRequest.Phone(action.number), onGenerateResult)
manager.generateIdentity(
IdentityRequest.Phone(action.number),
SUBSCRIPTION_ID,
PUBLIC_KEY,
onGenerateResult,
)
} else {
// We're going to generate the identity as if we've obtained it via a backend service.
api.generateIdentity(action.number, PHONE)?.let {
Expand All @@ -138,6 +148,10 @@ class MainScreenViewModel(

private companion object {
const val TAG = "MainScreenViewModel"

@Suppress("ktlint:standard:max-line-length")
const val SUBSCRIPTION_ID = "UID2-X-L-MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEtXJdTSZAYHvoRDWiehMHoWF1BNPuqLs5w2ZHiAZ1IJc7O4/z0ojPTB0V+KYX/wxQK0hxx6kxCvHj335eI/ZQsQ=="
const val PUBLIC_KEY = "4WvryDGbR5"
}
}

Expand Down
20 changes: 7 additions & 13 deletions sdk/src/main/java/com/uid2/UID2Client.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.uid2

import com.uid2.data.IdentityRequest
import com.uid2.data.IdentityStatus
import com.uid2.data.toPayload
import com.uid2.extensions.encodeBase64
import com.uid2.network.DataEnvelope
Expand All @@ -27,8 +26,6 @@ import java.net.URL
*/
internal class UID2Client(
private val apiUrl: String,
private val apiPublicKey: String?,
private val apiSubscriptionId: String?,
private val session: NetworkSession,
private val packageName: String,
private val dataEnvelope: DataEnvelope = DataEnvelope,
Expand All @@ -55,24 +52,21 @@ internal class UID2Client(
PayloadDecryptException::class,
InvalidPayloadException::class,
)
suspend fun generateIdentity(identityRequest: IdentityRequest): ResponsePackage = withContext(ioDispatcher) {
suspend fun generateIdentity(
identityRequest: IdentityRequest,
subscriptionId: String,
publicKey: String,
): ResponsePackage = withContext(ioDispatcher) {
logger.i(TAG) { "Generating Identity" }

// If the SDK hasn't been configured with the required client side integration parameters, then it's not
// possible for us to generate the requested (new) identity.
if (apiPublicKey == null || apiSubscriptionId == null) {
logger.e(TAG) { "SDK not configured correctly (Key: $apiPublicKey, SubscriptionID: $apiSubscriptionId)" }
return@withContext ResponsePackage(null, IdentityStatus.INVALID, "SDK misconfigured")
}

// Check to make sure we have a valid endpoint to hit.
val url = apiGenerateUrl ?: run {
logger.e(TAG) { "Error determining identity generation API" }
throw InvalidApiUrlException()
}

// Generate the required Server and Client keys.
val serverPublicKey = keyUtils.generateServerPublicKey(apiPublicKey)
val serverPublicKey = keyUtils.generateServerPublicKey(publicKey)
val clientKeyPair = keyUtils.generateKeyPair()
if (serverPublicKey == null || clientKeyPair == null) {
logger.e(TAG) { "Error generating server and client keys" }
Expand Down Expand Up @@ -106,7 +100,7 @@ internal class UID2Client(
"iv" to iv.encodeBase64(),
"public_key" to clientKeyPair.public.encoded.encodeBase64(),
"timestamp" to now.toString(),
"subscription_id" to apiSubscriptionId,
"subscription_id" to subscriptionId,
"app_name" to packageName,
),
),
Expand Down
18 changes: 7 additions & 11 deletions sdk/src/main/java/com/uid2/UID2Manager.kt
Original file line number Diff line number Diff line change
Expand Up @@ -218,13 +218,19 @@ public class UID2Manager internal constructor(
* Once set, assuming it's valid, it will be monitored so that we automatically refresh the token(s) when required.
* This will also be persisted locally, so that when the application re-launches, we reload this Identity.
*
* @param identityRequest The identify for which the [UID2Identity] is required for.
* @param subscriptionId The subscription id that was obtained when configuring your account.
* @param publicKey The public key that was obtained when configuring your account.
*
* @throws InputValidationException Thrown if the given [IdentityRequest] is not valid. For a
* [IdentityRequest.Phone] we expect the given number to conform to the ITU E.164 Standard
* (https://en.wikipedia.org/wiki/E.164).
*/
@Throws(InputValidationException::class)
public fun generateIdentity(
identityRequest: IdentityRequest,
subscriptionId: String,
publicKey: String,
onResult: (GenerateIdentityResult) -> Unit,
): Unit = afterInitialized {
// Normalize any given input to validate it.
Expand All @@ -237,7 +243,7 @@ public class UID2Manager internal constructor(
scope.launch {
try {
// Attempt to generate the new identity.
val identity = client.generateIdentity(request)
val identity = client.generateIdentity(request, subscriptionId, publicKey)

// Cancel any in-flight refresh job that could be processing a previously set identity.
refreshJob?.cancel()
Expand Down Expand Up @@ -516,10 +522,6 @@ public class UID2Manager internal constructor(
private const val UID2_API_URL_KEY = "uid2_api_url"
private const val UID2_API_URL_DEFAULT = "https://prod.uidapi.com"

// The metadata keys that are used to provide access to required parameters for Client Side Integration.
private const val UID2_API_PUBLIC_KEY = "uid2_api_public_key"
private const val UID2_API_SUBSCRIPTION_ID = "uid2_api_subscription_id"

private const val PACKAGE_NAME_DEFAULT = "unknown"

private const val PACKAGE_NOT_AVAILABLE = "Identity not available"
Expand All @@ -540,8 +542,6 @@ public class UID2Manager internal constructor(
private const val EXPIRATION_CHECK_TOLERANCE_MS = 50

private var api: String = UID2_API_URL_DEFAULT
private var apiPublicKey: String? = null
private var apiSubscriptionId: String? = null
private var packageName: String = PACKAGE_NAME_DEFAULT
private var networkSession: NetworkSession = DefaultNetworkSession()
private var storageManager: StorageManager? = null
Expand Down Expand Up @@ -573,8 +573,6 @@ public class UID2Manager internal constructor(
val metadata = context.getMetadata()

this.api = metadata?.getString(UID2_API_URL_KEY, UID2_API_URL_DEFAULT) ?: UID2_API_URL_DEFAULT
this.apiPublicKey = metadata?.getString(UID2_API_PUBLIC_KEY, null)
this.apiSubscriptionId = metadata?.getString(UID2_API_SUBSCRIPTION_ID, null)
this.packageName = context.packageName
this.networkSession = networkSession
this.storageManager = StorageManager.getInstance(context.applicationContext)
Expand All @@ -600,8 +598,6 @@ public class UID2Manager internal constructor(
return instance ?: UID2Manager(
UID2Client(
apiUrl = api,
apiPublicKey = apiPublicKey,
apiSubscriptionId = apiSubscriptionId,
session = networkSession,
packageName = packageName,
logger = logger,
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/main/java/com/uid2/network/RefreshResponse.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import org.json.JSONObject
* This class defines the expected response from the Identity API when refreshing. The results could include a new
* (refreshed) Identity, or represent a failure/error.
*
* https://github.com/IABTechLab/uid2docs/blob/main/api/v2/endpoints/post-token-refresh.md#decrypted-json-response-format
* https://unifiedid.com/docs/endpoints/post-token-refresh
*/
internal data class RefreshResponse(
val body: UID2Identity?,
Expand Down
57 changes: 43 additions & 14 deletions sdk/src/test/java/com/uid2/UID2ClientTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,16 @@ class UID2ClientTest {
private val logger: Logger = mock()

private val url = "https://test.dev"
private val apiPublicKey = "an api key"
private val apiSubscriptionId = "subscription id"
private val refreshToken = "RefreshToken"
private val refreshKey = "RefreshKey"

private val keyPair: KeyPair = mock()
private val keyPairPublic: PublicKey = mock()
private val keyPairPublicEncoded = ByteArray(12)

private val SUBSCRIPTION_ID = "subscription_id"
private val PUBLIC_KEY = "public_key"

@Before
fun before() {
// By default, don't encrypt the data. Just convert it directly to a ByteArray.
Expand All @@ -77,7 +78,11 @@ class UID2ClientTest {
@Test
fun `test generate with invalid api url`() = runTest(testDispatcher) {
testInvalidClientApi { client ->
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}

Expand All @@ -91,7 +96,11 @@ class UID2ClientTest {
// Verify the expected CryptoException is thrown.
assertThrows(CryptoException::class.java) {
runTest(testDispatcher) {
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}
}
Expand All @@ -106,7 +115,11 @@ class UID2ClientTest {
// Verify the expected CryptoException is thrown.
assertThrows(CryptoException::class.java) {
runTest(testDispatcher) {
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}
}
Expand All @@ -121,7 +134,11 @@ class UID2ClientTest {
// Verify the expected CryptoException is thrown.
assertThrows(CryptoException::class.java) {
runTest(testDispatcher) {
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}
}
Expand All @@ -136,15 +153,23 @@ class UID2ClientTest {
// Verify the expected CryptoException is thrown.
assertThrows(CryptoException::class.java) {
runTest(testDispatcher) {
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}
}

@Test
fun `test generate with network failure`() = runTest(testDispatcher) {
testNetworkFailure { client ->
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}

Expand All @@ -159,7 +184,11 @@ class UID2ClientTest {
// Verify the expected CryptoException is thrown.
assertThrows(PayloadDecryptException::class.java) {
runTest(testDispatcher) {
client.generateIdentity(IdentityRequest.Email("[email protected]"))
client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
}
}
}
Expand All @@ -174,7 +203,11 @@ class UID2ClientTest {
)
whenever(networkSession.loadData(any(), any())).thenReturn(NetworkResponse(200, "some data"))

val response = client.generateIdentity(IdentityRequest.Email("[email protected]"))
val response = client.generateIdentity(
IdentityRequest.Email("[email protected]"),
SUBSCRIPTION_ID,
PUBLIC_KEY,
)
assertNotNull(response)

// Verify that the returned package has an identity that matches what we included in the body of the response.
Expand Down Expand Up @@ -288,8 +321,6 @@ class UID2ClientTest {
private fun testInvalidClientApi(callback: suspend (client: UID2Client) -> Unit) {
val client = UID2Client(
"this is not a url",
apiPublicKey,
apiSubscriptionId,
networkSession,
packageName,
dataEnvelope,
Expand Down Expand Up @@ -347,8 +378,6 @@ class UID2ClientTest {

private fun withClient() = UID2Client(
url,
apiPublicKey,
apiSubscriptionId,
networkSession,
packageName,
dataEnvelope,
Expand Down
18 changes: 14 additions & 4 deletions sdk/src/test/java/com/uid2/UID2ManagerTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -142,18 +142,23 @@ class UID2ManagerTest {

@Test
fun `generates identity for different requests`() = runTest(testDispatcher) {
val subscriptionId = "sub"
val publicKey = "pub"

listOf(
IdentityRequest.Email("[email protected]"),
IdentityRequest.EmailHash("a-hash"),
IdentityRequest.Phone("+00000000000"),
IdentityRequest.PhoneHash("another-hash"),
).forEach { request ->
val generated = withRandomIdentity()
whenever(client.generateIdentity(request)).thenReturn(ResponsePackage(generated, ESTABLISHED, ""))
whenever(client.generateIdentity(request, subscriptionId, publicKey)).thenReturn(
ResponsePackage(generated, ESTABLISHED, ""),
)

// Request a new identity should be generated.
var result: GenerateIdentityResult? = null
manager.generateIdentity(request) { result = it }
manager.generateIdentity(request, subscriptionId, publicKey) { result = it }
testDispatcher.scheduler.advanceUntilIdle()

// Verify that the identity is updated from the one provided via the Client.
Expand All @@ -166,15 +171,20 @@ class UID2ManagerTest {

@Test
fun `existing identity untouched if generation fails`() = runTest(testDispatcher) {
val subscriptionId = "sub"
val publicKey = "pub"

val request = IdentityRequest.Email("[email protected]")
whenever(client.generateIdentity(request)).thenThrow(PayloadDecryptException::class.java)
whenever(client.generateIdentity(request, subscriptionId, publicKey)).thenThrow(
PayloadDecryptException::class.java,
)

// Verify that the manager has a known (existing) identity.
assertEquals(manager.currentIdentity, initialIdentity)

// Request a new identity is generated, knowing that this will fail.
var result: GenerateIdentityResult? = null
manager.generateIdentity(request) { result = it }
manager.generateIdentity(request, subscriptionId, publicKey) { result = it }
testDispatcher.scheduler.advanceUntilIdle()

// Verify that after the failure, the existing identity is still present.
Expand Down

0 comments on commit 1f945d4

Please sign in to comment.