From 4ec944fe6b26081dcc65d01ef72bb6555239a8de Mon Sep 17 00:00:00 2001 From: shah Date: Thu, 20 Jun 2024 11:57:52 +0530 Subject: [PATCH 01/14] feat: removed API key usage ALMS-175 --- README.md | 11 +- .../auth/ApiKeyCredentialsProvider.kt | 59 -------- .../amazon/location/auth/AuthHelper.kt | 12 -- .../auth/LocationCredentialsProvider.kt | 42 +----- .../amazon/location/auth/utils/Constants.kt | 1 - .../auth/ApiKeyCredentialsProviderTest.kt | 80 ----------- .../amazon/location/auth/AuthHelperTest.kt | 8 -- .../auth/LocationCredentialsProviderTest.kt | 129 +++++------------- .../amazon/location/auth/utils/Constants.kt | 1 - 9 files changed, 39 insertions(+), 304 deletions(-) delete mode 100644 library/src/main/java/software/amazon/location/auth/ApiKeyCredentialsProvider.kt delete mode 100644 library/src/test/java/software/amazon/location/auth/ApiKeyCredentialsProviderTest.kt diff --git a/README.md b/README.md index 002a309..6460c1d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Amazon Location Service Mobile Authentication SDK for Android -These utilities help you authenticate when making [Amazon Location Service](https://aws.amazon.com/location/) API calls from your Android applications. This specifically helps when using [Amazon Cognito](https://docs.aws.amazon.com/location/latest/developerguide/authenticating-using-cognito.html) or [API keys](https://docs.aws.amazon.com/location/latest/developerguide/using-apikeys.html) as the authentication method. +These utilities help you authenticate when making [Amazon Location Service](https://aws.amazon.com/location/) API calls from your Android applications. This specifically helps when using [Amazon Cognito](https://docs.aws.amazon.com/location/latest/developerguide/authenticating-using-cognito.html) as the authentication method. ## Installation @@ -33,15 +33,6 @@ import okhttp3.OkHttpClient You can create an AuthHelper and use it with the AWS Kotlin SDK: -``` -// Create an authentication helper instance using an Amazon Location API Key -private fun exampleAPIKeyLogin() { - var authHelper = AuthHelper(applicationContext) - var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithApiKey("My-Amazon-Location-API-Key") - var locationClient = locationCredentialsProvider?.getLocationClient() -} -``` - ``` // Create an authentication helper using credentials from Cognito private fun exampleCognitoLogin() { diff --git a/library/src/main/java/software/amazon/location/auth/ApiKeyCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/ApiKeyCredentialsProvider.kt deleted file mode 100644 index f5ccfb7..0000000 --- a/library/src/main/java/software/amazon/location/auth/ApiKeyCredentialsProvider.kt +++ /dev/null @@ -1,59 +0,0 @@ -package software.amazon.location.auth - -import android.content.Context -import software.amazon.location.auth.utils.Constants.API_KEY - -/** - * Provides API key credentials for accessing services and manages their storage. - */ -class ApiKeyCredentialsProvider { - private var securePreferences: EncryptedSharedPreferences? = null - - /** - * Initializes the provider and saves the provided API key. - * @param context The application context. - * @param apiKey The API key to save. - */ - constructor(context: Context, apiKey: String) { - initialize(context) - saveCredentials(apiKey) - } - - /** - * Initializes the provider and retrieves the API key from the cache. - * @param context The application context. - * @throws Exception If no credentials are found in the cache. - */ - constructor(context: Context) { - initialize(context) - val apiKey = getCachedCredentials() - if (apiKey === null) throw Exception("No credentials found") - } - - private fun initialize(context: Context) { - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences?.initEncryptedSharedPreferences() - } - - private fun saveCredentials(apiKey: String) { - if (securePreferences === null) throw Exception("Not initialized") - securePreferences!!.put(API_KEY, apiKey) - } - - /** - * Retrieves the cached API key credentials. - * @return The API key or null if not found. - * @throws Exception If the AWSKeyValueStore is not initialized. - */ - fun getCachedCredentials(): String? { - if (securePreferences === null) throw Exception("Not initialized") - return securePreferences!!.get(API_KEY) - } - - /** - * Clears the stored credentials. - */ - fun clearCredentials() { - securePreferences?.clear() - } -} \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index 39265bc..a5c5f61 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -71,16 +71,4 @@ class AuthHelper(private val context: Context) { locationCredentialsProvider // Return the generated locationCredentialsProvider } } - - /** - * Authenticates using an API key. - * @param apiKey The API key for authentication. - * @return A LocationCredentialsProvider instance. - */ - fun authenticateWithApiKey( - apiKey: String, - ): LocationCredentialsProvider = LocationCredentialsProvider( - context, - apiKey, - ) } diff --git a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt index 2bdb221..c32ee81 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -11,7 +11,6 @@ import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.epochMilliseconds import software.amazon.location.auth.utils.AwsRegions -import software.amazon.location.auth.utils.Constants.API_KEY import software.amazon.location.auth.utils.Constants.IDENTITY_POOL_ID import software.amazon.location.auth.utils.Constants.METHOD import software.amazon.location.auth.utils.Constants.REGION @@ -19,12 +18,11 @@ import software.amazon.location.auth.utils.Constants.REGION const val PREFS_NAME = "software.amazon.location.auth" /** - * Provides credentials for accessing location-based services through Cognito or API key authentication. + * Provides credentials for accessing location-based services through Cognito authentication. */ class LocationCredentialsProvider { private var context: Context private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null - private var apiKeyProvider: ApiKeyCredentialsProvider? = null private var securePreferences: EncryptedSharedPreferences private var locationClient: LocationClient? = null private var cognitoIdentityClient: CognitoIdentityClient? = null @@ -44,24 +42,10 @@ class LocationCredentialsProvider { securePreferences.put(REGION, region.regionName) } - /** - * Initializes with an API key. - * @param context The application context. - * @param apiKey The API key for authentication. - */ - constructor(context: Context, apiKey: String) { - this.context = context - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences.initEncryptedSharedPreferences() - securePreferences.put(METHOD, "apiKey") - securePreferences.put(API_KEY, apiKey) - apiKeyProvider = ApiKeyCredentialsProvider(context, apiKey) - } - /** * Initializes with cached credentials. * @param context The application context. - * @throws Exception If API key credentials are not found. + * @throws Exception If credentials are not found. */ constructor(context: Context) { this.context = context @@ -76,13 +60,6 @@ class LocationCredentialsProvider { if (identityPoolId === null || region === null) throw Exception("No credentials found") cognitoCredentialsProvider = CognitoCredentialsProvider(context) } - - "apiKey" -> { - val apiKey = securePreferences.get(API_KEY) - if (apiKey === null) throw Exception("No credentials found") - apiKeyProvider = ApiKeyCredentialsProvider(context, apiKey) - } - else -> { throw Exception("No credentials found") } @@ -225,7 +202,6 @@ class LocationCredentialsProvider { * @param region The AWS region for the CognitoIdentityClient. * @return A new instance of CognitoIdentityClient. */ - fun generateCognitoIdentityClient(region: String): CognitoIdentityClient { return CognitoIdentityClient { this.region = region } } @@ -252,19 +228,9 @@ class LocationCredentialsProvider { return cognitoCredentialsProvider?.getCachedCredentials() } - /** - * Retrieves the API key credentials provider. - * @return The ApiKeyCredentialsProvider instance. - * @throws Exception If the API key provider is not initialized. - */ - fun getApiKeyProvider(): ApiKeyCredentialsProvider { - if (apiKeyProvider === null) throw Exception("Api key provider not initialized") - return apiKeyProvider!! - } - /** * Refreshes the Cognito credentials. - * @throws Exception If the Cognito provider is not initialized or if called for API key authentication. + * @throws Exception If the Cognito provider is not initialized. */ suspend fun refresh() { if (cognitoCredentialsProvider === null) throw Exception("Refresh is only supported for Cognito credentials. Make sure to use the cognito constructor.") @@ -275,7 +241,7 @@ class LocationCredentialsProvider { /** * Clears the Cognito credentials. - * @throws Exception If the Cognito provider is not initialized or if called for API key authentication. + * @throws Exception If the Cognito provider is not initialized. */ fun clear() { if (cognitoCredentialsProvider === null) throw Exception("Clear is only supported for Cognito credentials. Make sure to use the cognito constructor.") diff --git a/library/src/main/java/software/amazon/location/auth/utils/Constants.kt b/library/src/main/java/software/amazon/location/auth/utils/Constants.kt index d22fb39..1ce1eaa 100644 --- a/library/src/main/java/software/amazon/location/auth/utils/Constants.kt +++ b/library/src/main/java/software/amazon/location/auth/utils/Constants.kt @@ -17,6 +17,5 @@ object Constants { const val REGION= "region" const val METHOD= "method" const val IDENTITY_POOL_ID= "identityPoolId" - const val API_KEY = "apiKey" const val DEFAULT_ENCODING = "UTF-8" } \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/ApiKeyCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/ApiKeyCredentialsProviderTest.kt deleted file mode 100644 index 676ed0b..0000000 --- a/library/src/test/java/software/amazon/location/auth/ApiKeyCredentialsProviderTest.kt +++ /dev/null @@ -1,80 +0,0 @@ -package software.amazon.location.auth - -import android.content.Context -import io.mockk.every -import io.mockk.just -import io.mockk.mockk -import io.mockk.mockkConstructor -import io.mockk.runs -import io.mockk.verify -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import org.junit.Before -import org.junit.Test - -private const val TEST_API_KEY = "dummyApiKey" - -class ApiKeyCredentialsProviderTest { - - private lateinit var context: Context - @Before - fun setUp() { - context = mockk(relaxed = true) - - mockkConstructor(EncryptedSharedPreferences::class) - - every { anyConstructed().initEncryptedSharedPreferences() } just runs - every { anyConstructed().put(any(), any()) } just runs - every { anyConstructed().get("region") } returns "us-east-1" - every { anyConstructed().clear() } just runs - } - - @Test - fun `constructor with apiKey saves credentials`() { - every { anyConstructed().put("apiKey", TEST_API_KEY) } just runs - - ApiKeyCredentialsProvider(context, TEST_API_KEY) - - verify { anyConstructed().put("apiKey", TEST_API_KEY) } - } - - @Test - fun `constructor without apiKey throws when no credentials found`() { - every { anyConstructed().get("apiKey") } returns null - - assertFailsWith { - ApiKeyCredentialsProvider(context) - } - } - - @Test - fun `getCachedCredentials returns apiKey when found`() { - val apiKey = "testApiKey" - every { anyConstructed().get("apiKey") } returns apiKey - - val provider = ApiKeyCredentialsProvider(context, apiKey) - val cachedApiKey = provider.getCachedCredentials() - - assertEquals(apiKey, cachedApiKey) - } - - @Test - fun `getCachedCredentials throws when not initialized`() { - val provider = ApiKeyCredentialsProvider(context, "testApiKey") - - every { anyConstructed().get("apiKey") } throws Exception("Not initialized") - - assertFailsWith { - provider.getCachedCredentials() - } - } - - @Test - fun `clearCredentials clears the stored credentials`() { - val provider = ApiKeyCredentialsProvider(context, "testApiKey") - - provider.clearCredentials() - - verify { anyConstructed().clear() } - } -} \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt index 203a9d8..ac7ea87 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -17,8 +17,6 @@ import software.amazon.location.auth.utils.Constants import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID -private const val TEST_API_KEY = "dummyApiKey" - class AuthHelperTest { private lateinit var context: Context @@ -71,10 +69,4 @@ class AuthHelperTest { assertNotNull(provider) } } - - @Test - fun `authenticateWithApiKey creates LocationCredentialsProvider`() { - val provider = authHelper.authenticateWithApiKey(TEST_API_KEY) - assertNotNull(provider) - } } \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt index b289b74..a6308bf 100644 --- a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt @@ -23,15 +23,11 @@ import junit.framework.TestCase.assertFalse import junit.framework.TestCase.assertNotNull import kotlin.test.assertFailsWith import kotlin.test.assertTrue -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import org.junit.Before import org.junit.Test import software.amazon.location.auth.utils.AwsRegions import software.amazon.location.auth.utils.Constants.ACCESS_KEY_ID -import software.amazon.location.auth.utils.Constants.API_KEY import software.amazon.location.auth.utils.Constants.EXPIRATION import software.amazon.location.auth.utils.Constants.IDENTITY_POOL_ID import software.amazon.location.auth.utils.Constants.METHOD @@ -40,16 +36,12 @@ import software.amazon.location.auth.utils.Constants.SECRET_KEY import software.amazon.location.auth.utils.Constants.SESSION_TOKEN import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID -private const val TEST_API_KEY = "dummyApiKey" - class LocationCredentialsProviderTest { - private lateinit var context: Context private lateinit var locationClient: LocationClient private lateinit var cognitoIdentityClient: CognitoIdentityClient private lateinit var cognitoCredentialsProvider: CognitoCredentialsProvider private lateinit var credentialsProvider: CredentialsProvider - private val coroutineScope = CoroutineScope(Dispatchers.Default) @Before fun setUp() { @@ -64,27 +56,18 @@ class LocationCredentialsProviderTest { every { anyConstructed().initEncryptedSharedPreferences() } just runs every { anyConstructed().generateCognitoIdentityClient("us-east-1") } returns cognitoIdentityClient - every { anyConstructed().generateLocationClient("us-east-1", any()) } returns locationClient + every { + anyConstructed().generateLocationClient( + "us-east-1", + any(), + ) + } returns locationClient every { anyConstructed().put(any(), any()) } just runs every { anyConstructed().get(REGION) } returns "us-east-1" every { anyConstructed().clear() } just runs every { anyConstructed().remove(any()) } just runs } - @Test - fun `constructor with Cognito initializes correctly`() { - every { anyConstructed().get(METHOD) } returns "api" - val provider = - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - assertNotNull(provider) - } - - @Test - fun `constructor with API key initializes correctly`() { - val provider = LocationCredentialsProvider(context, TEST_API_KEY) - assertNotNull(provider) - } - @Test fun `constructor with cached credentials for Cognito initializes correctly`() { every { anyConstructed().get(METHOD) } returns "cognito" @@ -97,14 +80,6 @@ class LocationCredentialsProviderTest { assertNotNull(provider) } - @Test - fun `constructor with cached credentials for API key initializes correctly`() { - every { anyConstructed().get(METHOD) } returns "apiKey" - every { anyConstructed().get(API_KEY) } returns TEST_API_KEY - val provider = LocationCredentialsProvider(context) - assertNotNull(provider) - } - @Test fun `getCredentialsProvider returns cognito provider successfully`() { every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID @@ -120,22 +95,17 @@ class LocationCredentialsProviderTest { } } - @Test - fun `getApiKeyProvider returns api key provider successfully`() { - val provider = LocationCredentialsProvider(context, TEST_API_KEY) - assertNotNull(provider.getApiKeyProvider()) - } - @Test fun `isCredentialsValid returns true when credentials are valid`() { val expirationTime = Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future - val mockCredentials = Credentials.invoke { - expiration = expirationTime - secretKey = "test" - accessKeyId = "test" - sessionToken = "test" - } + val mockCredentials = + Credentials.invoke { + expiration = expirationTime + secretKey = "test" + accessKeyId = "test" + sessionToken = "test" + } every { anyConstructed().getCachedCredentials() } returns mockCredentials every { anyConstructed().get(METHOD) } returns "cognito" every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" @@ -157,9 +127,10 @@ class LocationCredentialsProviderTest { fun `isCredentialsValid returns false when credentials are expired`() { val expirationTime = Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds - 10000) // 10 seconds in the past - val mockCredentials = mockk { - every { expiration } returns expirationTime - } + val mockCredentials = + mockk { + every { expiration } returns expirationTime + } every { anyConstructed().getCachedCredentials() } returns mockCredentials every { anyConstructed().get(METHOD) } returns "cognito" every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" @@ -213,23 +184,26 @@ class LocationCredentialsProviderTest { every { anyConstructed().get(EXPIRATION) } returns "11111" every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID val identityId = "test-identity-id" - val credentials = Credentials { - accessKeyId = "test-access-key" - secretKey = "test-secret-key" - sessionToken = "test-session-token" - } + val credentials = + Credentials { + accessKeyId = "test-access-key" + secretKey = "test-secret-key" + sessionToken = "test-session-token" + } every { anyConstructed().get(METHOD) } returns "cognito" every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID val provider = LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - coEvery { cognitoIdentityClient.getId(any()) } returns GetIdResponse { - this.identityId = identityId - } - - coEvery { cognitoIdentityClient.getCredentialsForIdentity(any()) } returns GetCredentialsForIdentityResponse { - this.credentials = credentials - } + coEvery { cognitoIdentityClient.getId(any()) } returns + GetIdResponse { + this.identityId = identityId + } + + coEvery { cognitoIdentityClient.getCredentialsForIdentity(any()) } returns + GetCredentialsForIdentityResponse { + this.credentials = credentials + } runBlocking { provider.verifyAndRefreshCredentials() val locationClient = provider.getLocationClient() @@ -244,13 +218,6 @@ class LocationCredentialsProviderTest { assertFailsWith { LocationCredentialsProvider(context) } } - @Test - fun `constructor with cached API key credentials throws exception on missing data`() { - every { anyConstructed().get(METHOD) } returns "apiKey" - every { anyConstructed().get(API_KEY) } returns null // Simulate missing data - assertFailsWith { LocationCredentialsProvider(context) } - } - @Test fun `verify SecurePreferences interactions for cognito initialization`() { LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) @@ -258,42 +225,14 @@ class LocationCredentialsProviderTest { verify(exactly = 1) { anyConstructed().put( IDENTITY_POOL_ID, - TEST_IDENTITY_POOL_ID + TEST_IDENTITY_POOL_ID, ) } verify(exactly = 1) { anyConstructed().put( REGION, - AwsRegions.US_EAST_1.regionName + AwsRegions.US_EAST_1.regionName, ) } } - - @Test - fun `getCredentialsProvider throws if Cognito provider not initialized`() { - val provider = LocationCredentialsProvider(context, "apiKey") - assertFailsWith { provider.getCredentialsProvider() } - } - - @Test - fun `getApiKeyProvider throws if API key provider not initialized`() { - val provider = - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - assertFailsWith { provider.getApiKeyProvider() } - } - - @Test - fun `refresh throws if Cognito provider not initialized`() { - val provider = LocationCredentialsProvider(context, "apiKey") - coroutineScope.launch { - provider.verifyAndRefreshCredentials() - assertFailsWith { provider.refresh() } - } - } - - @Test - fun `clear throws if Cognito provider not initialized`() { - val provider = LocationCredentialsProvider(context, "apiKey") - assertFailsWith { provider.clear() } - } } diff --git a/library/src/test/java/software/amazon/location/auth/utils/Constants.kt b/library/src/test/java/software/amazon/location/auth/utils/Constants.kt index 9ca42fe..ce29376 100644 --- a/library/src/test/java/software/amazon/location/auth/utils/Constants.kt +++ b/library/src/test/java/software/amazon/location/auth/utils/Constants.kt @@ -7,7 +7,6 @@ object Constants { const val EXPIRATION = "expiration" const val METHOD = "method" const val IDENTITY_POOL_ID = "identityPoolId" - const val API_KEY = "apiKey" const val REGION = "region" const val HEADER_HOST = "Host" const val HEADER_X_AMZ_SECURITY_TOKEN = "x-amz-security-token" From 298041c24ff990cbc6cde09b3f56c4ba0abe7244 Mon Sep 17 00:00:00 2001 From: shah Date: Fri, 28 Jun 2024 13:37:48 +0530 Subject: [PATCH 02/14] feat: Add support for custom credential providers in Auth SDK ALMS-205 --- .../amazon/location/auth/AuthHelper.kt | 16 ++++ .../auth/LocationCredentialsProvider.kt | 90 +++++++++++++------ .../amazon/location/auth/AuthHelperTest.kt | 46 +++++++--- .../auth/CustomCredentialsProviderTest.kt | 71 +++++++++++++++ .../amazon/location/auth/utils/Constants.kt | 2 +- 5 files changed, 184 insertions(+), 41 deletions(-) create mode 100644 library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index a5c5f61..ccbf2d8 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -1,6 +1,7 @@ package software.amazon.location.auth import android.content.Context +import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import software.amazon.location.auth.utils.AwsRegions @@ -71,4 +72,19 @@ class AuthHelper(private val context: Context) { locationCredentialsProvider // Return the generated locationCredentialsProvider } } + + suspend fun authenticateWithCredentialsProvider( + identityPoolId: String, + credentialsProvider: CredentialsProvider? + ): LocationCredentialsProvider { + return withContext(Dispatchers.IO) { + val locationCredentialsProvider = LocationCredentialsProvider( + context, + identityPoolId, + AwsRegions.fromName(identityPoolId.split(":")[0]), + ) + locationCredentialsProvider.verifyAndRefreshCredentials(credentialsProvider) + locationCredentialsProvider + } + } } diff --git a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt index c32ee81..77f08e2 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -23,7 +23,7 @@ const val PREFS_NAME = "software.amazon.location.auth" class LocationCredentialsProvider { private var context: Context private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null - private var securePreferences: EncryptedSharedPreferences + private var securePreferences: EncryptedSharedPreferences? = null private var locationClient: LocationClient? = null private var cognitoIdentityClient: CognitoIdentityClient? = null @@ -35,11 +35,10 @@ class LocationCredentialsProvider { */ constructor(context: Context, identityPoolId: String, region: AwsRegions) { this.context = context - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences.initEncryptedSharedPreferences() - securePreferences.put(METHOD, "cognito") - securePreferences.put(IDENTITY_POOL_ID, identityPoolId) - securePreferences.put(REGION, region.regionName) + initPreference(context) + securePreferences?.put(METHOD, "cognito") + securePreferences?.put(IDENTITY_POOL_ID, identityPoolId) + securePreferences?.put(REGION, region.regionName) } /** @@ -49,14 +48,13 @@ class LocationCredentialsProvider { */ constructor(context: Context) { this.context = context - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences.initEncryptedSharedPreferences() - val method = securePreferences.get(METHOD) + initPreference(context) + val method = securePreferences?.get(METHOD) if (method === null) throw Exception("No credentials found") when (method) { "cognito" -> { - val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) - val region = securePreferences.get(REGION) + val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) + val region = securePreferences?.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") cognitoCredentialsProvider = CognitoCredentialsProvider(context) } @@ -66,6 +64,11 @@ class LocationCredentialsProvider { } } + fun initPreference(context: Context) { + securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) + securePreferences?.initEncryptedSharedPreferences() + } + /** * Checks AWS credentials availability and validity. * @@ -75,25 +78,59 @@ class LocationCredentialsProvider { * * @throws Exception if the identity pool ID or region is not found, or if credential generation fails. */ - suspend fun verifyAndRefreshCredentials() { - val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) - val region = securePreferences.get(REGION) + suspend fun verifyAndRefreshCredentials(credentialsProvider: CredentialsProvider? = null) { + val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) + val region = securePreferences?.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") - val isCredentialsAvailable = try { - cognitoCredentialsProvider = CognitoCredentialsProvider(context) - true - } catch (e: Exception) { - false - } - if (!isCredentialsAvailable) { - generateCredentials(region, identityPoolId) - } else { - if (!isCredentialsValid()) { + if (credentialsProvider == null) { + val isCredentialsAvailable = try { + cognitoCredentialsProvider = CognitoCredentialsProvider(context) + true + } catch (e: Exception) { + false + } + if (!isCredentialsAvailable) { generateCredentials(region, identityPoolId) + } else { + if (!isCredentialsValid()) { + generateCredentials(region, identityPoolId) + } } + } else { + initializeCognitoCredentialsProvider(credentialsProvider, region, identityPoolId) } } + /** + * Initializes the Cognito credentials provider using the provided credentials provider, region, and identity pool ID. + * + * This method generates a Cognito identity client if not already present, retrieves an identity ID using the + * provided identity pool ID, and initializes the `CognitoCredentialsProvider` with the resolved credentials. + * + * @param credentialsProvider The provider for AWS credentials. + * @param region The AWS region where the identity pool is located. + * @param identityPoolId The ID of the Cognito identity pool. + * @throws Exception if the identity ID cannot be retrieved. + */ + private suspend fun initializeCognitoCredentialsProvider(credentialsProvider: CredentialsProvider, region: String, identityPoolId: String) { + if (cognitoIdentityClient == null) { + cognitoIdentityClient = generateCognitoIdentityClient(region) + } + val getIdResponse = cognitoIdentityClient?.getId(GetIdRequest { this.identityPoolId = identityPoolId }) + val identityId = getIdResponse?.identityId ?: throw Exception("Failed to get identity ID") + val credentials = credentialsProvider.resolve() + cognitoCredentialsProvider = CognitoCredentialsProvider( + context, + identityId, + aws.sdk.kotlin.services.cognitoidentity.model.Credentials { + accessKeyId = credentials.accessKeyId + secretKey = credentials.secretAccessKey + sessionToken = credentials.sessionToken + expiration = credentials.expiration + } + ) + } + /** * Retrieves the LocationClient instance with configured AWS credentials. @@ -105,8 +142,8 @@ class LocationCredentialsProvider { * @throws Exception if the AWS region is not found in secure preferences. */ fun getLocationClient(): LocationClient? { - val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) - val region = securePreferences.get(REGION) + val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) + val region = securePreferences?.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") if (locationClient == null) { val credentialsProvider = createCredentialsProvider() @@ -151,6 +188,7 @@ class LocationCredentialsProvider { accessKeyId = getCredentialsProvider()?.accessKeyId!!, secretAccessKey = getCredentialsProvider()?.secretKey!!, sessionToken = getCredentialsProvider()?.sessionToken, + expiration = getCredentialsProvider()?.expiration ) ) } diff --git a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt index ac7ea87..13ec447 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -1,6 +1,8 @@ package software.amazon.location.auth import android.content.Context +import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient +import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -9,11 +11,10 @@ import io.mockk.runs import org.junit.Before import org.junit.Test import kotlin.test.assertNotNull -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch +import kotlinx.coroutines.runBlocking import software.amazon.location.auth.utils.AwsRegions import software.amazon.location.auth.utils.Constants +import software.amazon.location.auth.utils.Constants.IDENTITY_POOL_ID import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID @@ -21,25 +22,34 @@ class AuthHelperTest { private lateinit var context: Context private lateinit var authHelper: AuthHelper - private val coroutineScope = CoroutineScope(Dispatchers.Default) + private lateinit var credentialsProvider: CredentialsProvider + private lateinit var cognitoIdentityClient: CognitoIdentityClient @Before fun setUp() { context = mockk(relaxed = true) authHelper = AuthHelper(context) + credentialsProvider = mockk(relaxed = true) + cognitoIdentityClient = mockk(relaxed = true) mockkConstructor(EncryptedSharedPreferences::class) - mockkConstructor(EncryptedSharedPreferences::class) + mockkConstructor(LocationCredentialsProvider::class) - every { anyConstructed().initEncryptedSharedPreferences() } just runs every { anyConstructed().put(any(), any()) } just runs every { anyConstructed().clear() } just runs every { anyConstructed().remove(any()) } just runs every { anyConstructed().get("region") } returns "us-east-1" - every { anyConstructed().get("identityPoolId") } returns TEST_IDENTITY_POOL_ID + every { anyConstructed().get(Constants.ACCESS_KEY_ID) } returns "test" + every { anyConstructed().get(Constants.SECRET_KEY) } returns "test" + every { anyConstructed().get(Constants.SESSION_TOKEN) } returns "test" + every { anyConstructed().get(Constants.EXPIRATION) } returns "11111" + every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID + every { anyConstructed().generateCognitoIdentityClient("us-east-1") } returns cognitoIdentityClient + every { anyConstructed().initPreference(context) } just runs + every { anyConstructed().initEncryptedSharedPreferences() } just runs } @Test fun `authenticateWithCognitoIdentityPool with identityPoolId creates LocationCredentialsProvider`() { - coroutineScope.launch { + runBlocking { val provider = authHelper.authenticateWithCognitoIdentityPool(TEST_IDENTITY_POOL_ID) assertNotNull(provider) } @@ -47,11 +57,7 @@ class AuthHelperTest { @Test fun `authenticateWithCognitoIdentityPool with identityPoolId and string region creates LocationCredentialsProvider`() { - every { anyConstructed().get(Constants.ACCESS_KEY_ID) } returns "test" - every { anyConstructed().get(Constants.SECRET_KEY) } returns "test" - every { anyConstructed().get(Constants.SESSION_TOKEN) } returns "test" - every { anyConstructed().get(Constants.EXPIRATION) } returns "11111" - coroutineScope.launch { + runBlocking { val provider = authHelper.authenticateWithCognitoIdentityPool(TEST_IDENTITY_POOL_ID, "us-east-1") assertNotNull(provider) @@ -60,7 +66,7 @@ class AuthHelperTest { @Test fun `authenticateWithCognitoIdentityPool with identityPoolId and Regions enum creates LocationCredentialsProvider`() { - coroutineScope.launch { + runBlocking { val provider = authHelper.authenticateWithCognitoIdentityPool( TEST_IDENTITY_POOL_ID, @@ -69,4 +75,16 @@ class AuthHelperTest { assertNotNull(provider) } } + + @Test + fun `authenticateWithCredentialsProvider with identityPoolId`() { + runBlocking { + val provider = + authHelper.authenticateWithCredentialsProvider( + TEST_IDENTITY_POOL_ID, + credentialsProvider + ) + assertNotNull(provider) + } + } } \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt new file mode 100644 index 0000000..f694af8 --- /dev/null +++ b/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt @@ -0,0 +1,71 @@ +package software.amazon.location.auth + +import android.content.Context +import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient +import aws.sdk.kotlin.services.location.LocationClient +import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.runs +import junit.framework.TestCase.assertNotNull +import kotlinx.coroutines.runBlocking +import org.junit.Before +import org.junit.Test +import software.amazon.location.auth.utils.AwsRegions +import software.amazon.location.auth.utils.Constants.ACCESS_KEY_ID +import software.amazon.location.auth.utils.Constants.EXPIRATION +import software.amazon.location.auth.utils.Constants.IDENTITY_POOL_ID +import software.amazon.location.auth.utils.Constants.REGION +import software.amazon.location.auth.utils.Constants.SECRET_KEY +import software.amazon.location.auth.utils.Constants.SESSION_TOKEN +import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID + +class CustomCredentialsProviderTest { + private lateinit var context: Context + private lateinit var locationClient: LocationClient + private lateinit var cognitoIdentityClient: CognitoIdentityClient + private lateinit var cognitoCredentialsProvider: CognitoCredentialsProvider + private lateinit var credentialsProvider: CredentialsProvider + + @Before + fun setUp() { + context = mockk(relaxed = true) + locationClient = mockk(relaxed = true) + cognitoIdentityClient = mockk(relaxed = true) + cognitoCredentialsProvider = mockk(relaxed = true) + credentialsProvider = mockk(relaxed = true) + mockkConstructor(EncryptedSharedPreferences::class) + mockkConstructor(CognitoCredentialsProvider::class) + mockkConstructor(LocationCredentialsProvider::class) + every { anyConstructed().initEncryptedSharedPreferences() } just runs + + every { anyConstructed().generateCognitoIdentityClient("us-east-1") } returns cognitoIdentityClient + every { + anyConstructed().generateLocationClient( + "us-east-1", + any(), + ) + } returns locationClient + every { anyConstructed().put(any(), any()) } just runs + every { anyConstructed().get(REGION) } returns "us-east-1" + every { anyConstructed().clear() } just runs + every { anyConstructed().remove(any()) } just runs + } + + @Test + fun `getCredentialsProvider returns cognito provider successfully with custom credential`() { + every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID + every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" + every { anyConstructed().get(SECRET_KEY) } returns "test" + every { anyConstructed().get(SESSION_TOKEN) } returns "test" + every { anyConstructed().get(EXPIRATION) } returns "11111" + val provider = + LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) + runBlocking { + provider.verifyAndRefreshCredentials(credentialsProvider) + assertNotNull(provider.getCredentialsProvider()) + } + } +} diff --git a/library/src/test/java/software/amazon/location/auth/utils/Constants.kt b/library/src/test/java/software/amazon/location/auth/utils/Constants.kt index ce29376..972f202 100644 --- a/library/src/test/java/software/amazon/location/auth/utils/Constants.kt +++ b/library/src/test/java/software/amazon/location/auth/utils/Constants.kt @@ -12,7 +12,7 @@ object Constants { const val HEADER_X_AMZ_SECURITY_TOKEN = "x-amz-security-token" const val HEADER_AUTHORIZATION = "Authorization" const val TEST_REGION = "us-west-2" - const val TEST_URL = "https://service.amazonaws.com" + const val TEST_URL = "https://service.amazonaws.com/?test=query" const val TEST_URL1 = "https://example.com" const val TEST_IDENTITY_POOL_ID = "us-east-1:dummyIdentityPoolId" } \ No newline at end of file From 98a2edb583cc12a95f82f6861dadd891c4ba61fa Mon Sep 17 00:00:00 2001 From: shah Date: Fri, 28 Jun 2024 13:42:20 +0530 Subject: [PATCH 03/14] feat: code optimize ALMS-205 --- .../main/java/software/amazon/location/auth/AuthHelper.kt | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index ccbf2d8..e1143c1 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -73,6 +73,12 @@ class AuthHelper(private val context: Context) { } } + /** + * Authenticates using a Cognito Identity Pool ID and a specified CredentialsProvider. + * @param identityPoolId The identity pool id. + * @param credentialsProvider The CredentialsProvider from AWS kotlin SDK. + * @return A LocationCredentialsProvider object. + */ suspend fun authenticateWithCredentialsProvider( identityPoolId: String, credentialsProvider: CredentialsProvider? From a71e17fed3bfeb88c9d39bab21d2dab0561d08ea Mon Sep 17 00:00:00 2001 From: shah Date: Fri, 28 Jun 2024 20:22:11 +0530 Subject: [PATCH 04/14] feat: Add support for custom credential providers in Auth SDK ALMS-205 --- .../amazon/location/auth/AuthHelper.kt | 13 +-- .../auth/LocationCredentialsProvider.kt | 106 +++++++++++------- .../amazon/location/auth/AuthHelperTest.kt | 5 +- .../auth/CustomCredentialsProviderTest.kt | 18 ++- .../auth/LocationCredentialsProviderTest.kt | 12 ++ 5 files changed, 103 insertions(+), 51 deletions(-) diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index e1143c1..b856ff4 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -74,22 +74,21 @@ class AuthHelper(private val context: Context) { } /** - * Authenticates using a Cognito Identity Pool ID and a specified CredentialsProvider. - * @param identityPoolId The identity pool id. + * Authenticates using a region and a specified CredentialsProvider. + * @param region The AWS region as a string. * @param credentialsProvider The CredentialsProvider from AWS kotlin SDK. * @return A LocationCredentialsProvider object. */ suspend fun authenticateWithCredentialsProvider( - identityPoolId: String, - credentialsProvider: CredentialsProvider? + region: String, + credentialsProvider: CredentialsProvider ): LocationCredentialsProvider { return withContext(Dispatchers.IO) { val locationCredentialsProvider = LocationCredentialsProvider( context, - identityPoolId, - AwsRegions.fromName(identityPoolId.split(":")[0]), + AwsRegions.fromName(region), ) - locationCredentialsProvider.verifyAndRefreshCredentials(credentialsProvider) + locationCredentialsProvider.initializeLocationClient(credentialsProvider) locationCredentialsProvider } } diff --git a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt index 77f08e2..a35fd8e 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -21,6 +21,8 @@ const val PREFS_NAME = "software.amazon.location.auth" * Provides credentials for accessing location-based services through Cognito authentication. */ class LocationCredentialsProvider { + private var credentialsProvider: CredentialsProvider? = null + private var customCredentials: aws.sdk.kotlin.services.cognitoidentity.model.Credentials? = null private var context: Context private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null private var securePreferences: EncryptedSharedPreferences? = null @@ -41,6 +43,19 @@ class LocationCredentialsProvider { securePreferences?.put(REGION, region.regionName) } + /** + * Initializes with region. + * @param context The application context. + * @param region The region for Cognito authentication. + */ + constructor(context: Context, region: AwsRegions) { + this.context = context + initPreference(context) + securePreferences?.put(METHOD, "custom") + securePreferences?.put(REGION, region.regionName) + } + + /** * Initializes with cached credentials. * @param context The application context. @@ -78,26 +93,22 @@ class LocationCredentialsProvider { * * @throws Exception if the identity pool ID or region is not found, or if credential generation fails. */ - suspend fun verifyAndRefreshCredentials(credentialsProvider: CredentialsProvider? = null) { + suspend fun verifyAndRefreshCredentials() { val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) val region = securePreferences?.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") - if (credentialsProvider == null) { - val isCredentialsAvailable = try { - cognitoCredentialsProvider = CognitoCredentialsProvider(context) - true - } catch (e: Exception) { - false - } - if (!isCredentialsAvailable) { + val isCredentialsAvailable = try { + cognitoCredentialsProvider = CognitoCredentialsProvider(context) + true + } catch (e: Exception) { + false + } + if (!isCredentialsAvailable) { + generateCredentials(region, identityPoolId) + } else { + if (!isCredentialsValid()) { generateCredentials(region, identityPoolId) - } else { - if (!isCredentialsValid()) { - generateCredentials(region, identityPoolId) - } } - } else { - initializeCognitoCredentialsProvider(credentialsProvider, region, identityPoolId) } } @@ -108,27 +119,24 @@ class LocationCredentialsProvider { * provided identity pool ID, and initializes the `CognitoCredentialsProvider` with the resolved credentials. * * @param credentialsProvider The provider for AWS credentials. - * @param region The AWS region where the identity pool is located. - * @param identityPoolId The ID of the Cognito identity pool. - * @throws Exception if the identity ID cannot be retrieved. */ - private suspend fun initializeCognitoCredentialsProvider(credentialsProvider: CredentialsProvider, region: String, identityPoolId: String) { - if (cognitoIdentityClient == null) { - cognitoIdentityClient = generateCognitoIdentityClient(region) - } - val getIdResponse = cognitoIdentityClient?.getId(GetIdRequest { this.identityPoolId = identityPoolId }) - val identityId = getIdResponse?.identityId ?: throw Exception("Failed to get identity ID") + suspend fun initializeLocationClient(credentialsProvider: CredentialsProvider) { + val region = securePreferences?.get(REGION) + if (region === null) throw Exception("No credentials found") + + this.credentialsProvider = credentialsProvider + setCustomCredentials(credentialsProvider, region) + } + + private suspend fun setCustomCredentials(credentialsProvider: CredentialsProvider, region: String) { val credentials = credentialsProvider.resolve() - cognitoCredentialsProvider = CognitoCredentialsProvider( - context, - identityId, - aws.sdk.kotlin.services.cognitoidentity.model.Credentials { - accessKeyId = credentials.accessKeyId - secretKey = credentials.secretAccessKey - sessionToken = credentials.sessionToken - expiration = credentials.expiration - } - ) + customCredentials = aws.sdk.kotlin.services.cognitoidentity.model.Credentials.invoke { + accessKeyId = credentials.accessKeyId + secretKey = credentials.secretAccessKey + sessionToken = credentials.sessionToken + expiration = credentials.expiration + } + locationClient = generateLocationClient(region, credentialsProvider) } @@ -142,9 +150,8 @@ class LocationCredentialsProvider { * @throws Exception if the AWS region is not found in secure preferences. */ fun getLocationClient(): LocationClient? { - val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) val region = securePreferences?.get(REGION) - if (identityPoolId === null || region === null) throw Exception("No credentials found") + if (region === null) throw Exception("No credentials found") if (locationClient == null) { val credentialsProvider = createCredentialsProvider() locationClient = generateLocationClient(region, credentialsProvider) @@ -250,9 +257,12 @@ class LocationCredentialsProvider { * @return True if the credentials are valid (i.e., not expired), false otherwise. */ fun isCredentialsValid(): Boolean { - val credentials = cognitoCredentialsProvider?.getCachedCredentials() + val expirationTimeMillis: Long = if (cognitoCredentialsProvider == null && customCredentials != null) { + customCredentials?.expiration?.epochMilliseconds ?: throw Exception("Failed to get credentials") + } else { + getCredentialsProvider()?.expiration?.epochMilliseconds ?: throw Exception("Failed to get credentials") + } val currentTimeMillis = Instant.now().epochMilliseconds - val expirationTimeMillis = credentials?.expiration?.epochMilliseconds ?: throw Exception("Failed to get credentials") return currentTimeMillis < expirationTimeMillis } @@ -262,6 +272,10 @@ class LocationCredentialsProvider { * @throws Exception If the Cognito provider is not initialized. */ fun getCredentialsProvider(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials? { + val method = securePreferences?.get(METHOD) + if (method == "custom" && customCredentials != null) { + return customCredentials + } if (cognitoCredentialsProvider === null) throw Exception("Cognito credentials not initialized") return cognitoCredentialsProvider?.getCachedCredentials() } @@ -271,10 +285,18 @@ class LocationCredentialsProvider { * @throws Exception If the Cognito provider is not initialized. */ suspend fun refresh() { - if (cognitoCredentialsProvider === null) throw Exception("Refresh is only supported for Cognito credentials. Make sure to use the cognito constructor.") - locationClient = null - cognitoCredentialsProvider?.clearCredentials() - verifyAndRefreshCredentials() + val region = securePreferences?.get(REGION) + if (region === null) throw Exception("No credentials found") + + val method = securePreferences?.get(METHOD) + if (method == "custom" && customCredentials != null) { + credentialsProvider?.let { setCustomCredentials(it, region) } + } else { + if (cognitoCredentialsProvider === null) throw Exception("Refresh is only supported for Cognito credentials. Make sure to use the cognito constructor.") + locationClient = null + cognitoCredentialsProvider?.clearCredentials() + verifyAndRefreshCredentials() + } } /** diff --git a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt index 13ec447..e302d62 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -3,6 +3,7 @@ package software.amazon.location.auth import android.content.Context import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -43,6 +44,8 @@ class AuthHelperTest { every { anyConstructed().get(Constants.EXPIRATION) } returns "11111" every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID every { anyConstructed().generateCognitoIdentityClient("us-east-1") } returns cognitoIdentityClient + coEvery { anyConstructed().initializeLocationClient(any()) } just runs + coEvery { anyConstructed().isCredentialsValid() } returns true every { anyConstructed().initPreference(context) } just runs every { anyConstructed().initEncryptedSharedPreferences() } just runs } @@ -81,7 +84,7 @@ class AuthHelperTest { runBlocking { val provider = authHelper.authenticateWithCredentialsProvider( - TEST_IDENTITY_POOL_ID, + "us-east-1", credentialsProvider ) assertNotNull(provider) diff --git a/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt index f694af8..b68c275 100644 --- a/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt @@ -2,8 +2,12 @@ package software.amazon.location.auth import android.content.Context import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient +import aws.sdk.kotlin.services.cognitoidentity.model.Credentials import aws.sdk.kotlin.services.location.LocationClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import aws.smithy.kotlin.runtime.time.Instant +import aws.smithy.kotlin.runtime.time.epochMilliseconds +import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds import io.mockk.every import io.mockk.just import io.mockk.mockk @@ -17,6 +21,7 @@ import software.amazon.location.auth.utils.AwsRegions import software.amazon.location.auth.utils.Constants.ACCESS_KEY_ID import software.amazon.location.auth.utils.Constants.EXPIRATION import software.amazon.location.auth.utils.Constants.IDENTITY_POOL_ID +import software.amazon.location.auth.utils.Constants.METHOD import software.amazon.location.auth.utils.Constants.REGION import software.amazon.location.auth.utils.Constants.SECRET_KEY import software.amazon.location.auth.utils.Constants.SESSION_TOKEN @@ -56,6 +61,17 @@ class CustomCredentialsProviderTest { @Test fun `getCredentialsProvider returns cognito provider successfully with custom credential`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future + val mockCredentials = + Credentials.invoke { + expiration = expirationTime + secretKey = "test" + accessKeyId = "test" + sessionToken = "test" + } + every { anyConstructed().getCachedCredentials() } returns mockCredentials + every { anyConstructed().get(METHOD) } returns "" every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" every { anyConstructed().get(SECRET_KEY) } returns "test" @@ -64,7 +80,7 @@ class CustomCredentialsProviderTest { val provider = LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) runBlocking { - provider.verifyAndRefreshCredentials(credentialsProvider) + provider.verifyAndRefreshCredentials() assertNotNull(provider.getCredentialsProvider()) } } diff --git a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt index a6308bf..40a06b6 100644 --- a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt @@ -82,6 +82,17 @@ class LocationCredentialsProviderTest { @Test fun `getCredentialsProvider returns cognito provider successfully`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future + val mockCredentials = + Credentials.invoke { + expiration = expirationTime + secretKey = "test" + accessKeyId = "test" + sessionToken = "test" + } + every { anyConstructed().getCachedCredentials() } returns mockCredentials + every { anyConstructed().get(METHOD) } returns "" every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" every { anyConstructed().get(SECRET_KEY) } returns "test" @@ -107,6 +118,7 @@ class LocationCredentialsProviderTest { sessionToken = "test" } every { anyConstructed().getCachedCredentials() } returns mockCredentials + every { anyConstructed().get(METHOD) } returns "" every { anyConstructed().get(METHOD) } returns "cognito" every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" every { anyConstructed().get(SECRET_KEY) } returns "test" From 031ff58aae5ffa87d470aa5d57578c1b90c360e1 Mon Sep 17 00:00:00 2001 From: shah Date: Fri, 28 Jun 2024 20:26:44 +0530 Subject: [PATCH 05/14] feat: Add support for custom credential providers in Auth SDK ALMS-205 --- .../amazon/location/auth/LocationCredentialsProvider.kt | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt index a35fd8e..8548fb5 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -113,7 +113,7 @@ class LocationCredentialsProvider { } /** - * Initializes the Cognito credentials provider using the provided credentials provider, region, and identity pool ID. + * Initializes the Location client with custom CredentialsProvider * * This method generates a Cognito identity client if not already present, retrieves an identity ID using the * provided identity pool ID, and initializes the `CognitoCredentialsProvider` with the resolved credentials. @@ -128,6 +128,12 @@ class LocationCredentialsProvider { setCustomCredentials(credentialsProvider, region) } + /** + * Sets custom credentials using the provided credentials provider and region. + * + * @param credentialsProvider The provider for AWS credentials, which is used to resolve AWS access credentials. + * @param region The AWS region to be used for initializing the location client. + */ private suspend fun setCustomCredentials(credentialsProvider: CredentialsProvider, region: String) { val credentials = credentialsProvider.resolve() customCredentials = aws.sdk.kotlin.services.cognitoidentity.model.Credentials.invoke { From 9dd9aa4e2eacc9700a00f5cde2e47c406c9abfb5 Mon Sep 17 00:00:00 2001 From: shah Date: Mon, 1 Jul 2024 20:14:52 +0530 Subject: [PATCH 06/14] feat: readme file updated ALMS-226 --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 6460c1d..7ca658d 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,14 @@ These utilities help you authenticate when making [Amazon Location Service](http ## Installation -This authentication SDK works with the overall AWS SDK. Both SDKs are published to Maven Central. +This authentication SDK works with the overall AWS Kotlin SDK. Both SDKs are published to Maven Central. Check the [latest version](https://mvnrepository.com/artifact/software.amazon.location/auth) of auth SDK on Maven Central. Add the following lines to the dependencies section of your build.gradle file in Android Studio: ``` -implementation("software.amazon.location:auth:0.0.2") +implementation("software.amazon.location:auth:0.2.4") implementation("aws.sdk.kotlin:location:1.2.21") implementation("org.maplibre.gl:android-sdk:11.0.0-pre5") implementation("com.squareup.okhttp3:okhttp:4.12.0") @@ -34,12 +34,21 @@ import okhttp3.OkHttpClient You can create an AuthHelper and use it with the AWS Kotlin SDK: ``` -// Create an authentication helper using credentials from Cognito +// Create a credentail provider using Identity Pool Id with AuthHelper private fun exampleCognitoLogin() { var authHelper = AuthHelper(applicationContext) var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCognitoIdentityPool("My-Cognito-Identity-Pool-Id") var locationClient = locationCredentialsProvider?.getLocationClient() } + +OR + +// Create a credentail provider using custom credential provider with AuthHelper +private fun exampleCustomCredentialLogin() { + var authHelper = AuthHelper(applicationContext) + var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", "MY-CUSTOM-CREDENTIAL-PROVIDER") + var locationClient = locationCredentialsProvider?.getLocationClient() +} ``` You can use the LocationCredentialsProvider to load the maplibre map. Here is an example of that: @@ -49,7 +58,7 @@ HttpRequestUtil.setOkHttpClient( .addInterceptor( AwsSignerInterceptor( "geo", - "My-aws-region", + "MY-AWS-REGION", locationCredentialsProvider ) ) From 44779286ee61f8ed8e8b71b8f3fe8c90c18e0be1 Mon Sep 17 00:00:00 2001 From: shah Date: Tue, 2 Jul 2024 16:24:09 +0530 Subject: [PATCH 07/14] feat: github action added for unit test ALMS-211 --- .github/workflows/test-android.yml | 21 +++++++++++++++ .../auth/LocationCredentialsProviderTest.kt | 26 +++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/test-android.yml diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml new file mode 100644 index 0000000..1857907 --- /dev/null +++ b/.github/workflows/test-android.yml @@ -0,0 +1,21 @@ +name: Run Unit Tests for Android +on: + workflow_dispatch: + pull_request: + branches: [ main ] +jobs: + test-android: + name: Test Android + runs-on: macos-13 + steps: + - uses: actions/checkout@v3 + + - name: Setup Java + uses: actions/setup-java@v3 + with: + distribution: 'zulu' + java-version: '11' + + - name: Run Unit Tests + run: | + ./gradlew testDebugUnitTest \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt index 40a06b6..4527bee 100644 --- a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt @@ -106,6 +106,32 @@ class LocationCredentialsProviderTest { } } + @Test + fun `initializeLocationClient`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future + val mockCredentials = + Credentials.invoke { + expiration = expirationTime + secretKey = "test" + accessKeyId = "test" + sessionToken = "test" + } + every { anyConstructed().getCachedCredentials() } returns mockCredentials + every { anyConstructed().get(METHOD) } returns "" + every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID + every { anyConstructed().get(ACCESS_KEY_ID) } returns "test" + every { anyConstructed().get(SECRET_KEY) } returns "test" + every { anyConstructed().get(SESSION_TOKEN) } returns "test" + every { anyConstructed().get(EXPIRATION) } returns "11111" + val provider = + LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) + runBlocking { + provider.initializeLocationClient(credentialsProvider) + assertNotNull(provider.getLocationClient()) + } + } + @Test fun `isCredentialsValid returns true when credentials are valid`() { val expirationTime = From 949eac13d48d5aaf3a3f3acce4d022e5eb6073d2 Mon Sep 17 00:00:00 2001 From: shah Date: Tue, 2 Jul 2024 16:34:34 +0530 Subject: [PATCH 08/14] feat: code optimize ALMS-211 --- .github/workflows/test-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 1857907..a99d92f 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -14,7 +14,7 @@ jobs: uses: actions/setup-java@v3 with: distribution: 'zulu' - java-version: '11' + java-version: '17' - name: Run Unit Tests run: | From e38fb3f59b0f73ca54a8431f670e9fc87e4b349f Mon Sep 17 00:00:00 2001 From: shah Date: Tue, 2 Jul 2024 17:21:25 +0530 Subject: [PATCH 09/14] feat: test result upload ALMS-211 --- .github/workflows/test-android.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index a99d92f..6daa9cb 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -18,4 +18,12 @@ jobs: - name: Run Unit Tests run: | - ./gradlew testDebugUnitTest \ No newline at end of file + ./gradlew testDebugUnitTest + + - name: Upload test results + uses: actions/upload-artifact@v2 + if: always() + with: + name: test-results + path: app/build/reports/androidTests/connected/ + retention-days: 1 \ No newline at end of file From 2aedf39d94634a943d6bebdc484a251818193dfa Mon Sep 17 00:00:00 2001 From: shah Date: Tue, 2 Jul 2024 17:25:41 +0530 Subject: [PATCH 10/14] feat: test result upload ALMS-211 --- .github/workflows/test-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 6daa9cb..15489c2 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -25,5 +25,5 @@ jobs: if: always() with: name: test-results - path: app/build/reports/androidTests/connected/ + path: library/build/reports/tests/testDebugUnitTest/ retention-days: 1 \ No newline at end of file From 000aa7f880b99ba9537c2683d90c255c90b61f8b Mon Sep 17 00:00:00 2001 From: shah Date: Wed, 3 Jul 2024 19:23:15 +0530 Subject: [PATCH 11/14] feat: changed to ubuntu-latest ALMS-211 --- .github/workflows/test-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-android.yml b/.github/workflows/test-android.yml index 15489c2..bbc842c 100644 --- a/.github/workflows/test-android.yml +++ b/.github/workflows/test-android.yml @@ -6,7 +6,7 @@ on: jobs: test-android: name: Test Android - runs-on: macos-13 + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 From f8f696983cd18ee3f0467b3db9dc4f496f5ab13d Mon Sep 17 00:00:00 2001 From: shah Date: Thu, 4 Jul 2024 15:21:00 +0530 Subject: [PATCH 12/14] feat: code optimize --- .../amazon/location/auth/AuthHelper.kt | 24 +++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index b856ff4..0c5c299 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -75,9 +75,29 @@ class AuthHelper(private val context: Context) { /** * Authenticates using a region and a specified CredentialsProvider. + * + * For example, to get credentials from AWS Kotlin SDK: + * 1. Use `CognitoIdentityClient` to call `.getId` to get the identity ID: + * ``` kotlin + * val identityId = cognitoIdentityClient.getId(GetIdRequest { this.identityPoolId = identityPoolId }).identityId + * ``` + * + * 2. Use `CognitoIdentityClient` to call `.getCredentialsForIdentity` with the identity ID to get the credentials: + * ``` kotlin + * val credentials = cognitoIdentityClient.getCredentialsForIdentity(GetCredentialsForIdentityRequest { this.identityId = identityId }).credentials + * ``` + * + * + * To create a `StaticCredentialsProvider` as a `CredentialsProvider` from the obtained credentials: + * 1. Use the credentials obtained in the previous steps: + * ``` kotlin + * val staticCredentialsProvider = StaticCredentialsProvider(credentials) + * ``` + * + * * @param region The AWS region as a string. - * @param credentialsProvider The CredentialsProvider from AWS kotlin SDK. - * @return A LocationCredentialsProvider object. + * @param credentialsProvider The `CredentialsProvider` from AWS Kotlin SDK (`aws.smithy.kotlin.runtime.auth.awscredentials`). + * @return A `LocationCredentialsProvider` object. */ suspend fun authenticateWithCredentialsProvider( region: String, From c00a3dbfb04616673af56ee5056cd18baecd8a69 Mon Sep 17 00:00:00 2001 From: shah Date: Thu, 4 Jul 2024 15:36:17 +0530 Subject: [PATCH 13/14] feat: code optimization --- .../java/software/amazon/location/auth/AuthHelper.kt | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt index 0c5c299..3aeb83f 100644 --- a/library/src/main/java/software/amazon/location/auth/AuthHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/AuthHelper.kt @@ -91,10 +91,16 @@ class AuthHelper(private val context: Context) { * To create a `StaticCredentialsProvider` as a `CredentialsProvider` from the obtained credentials: * 1. Use the credentials obtained in the previous steps: * ``` kotlin - * val staticCredentialsProvider = StaticCredentialsProvider(credentials) + * val staticCredentialsProvider = StaticCredentialsProvider( + * aws.smithy.kotlin.runtime.auth.awscredentials.Credentials.invoke( + * accessKeyId = credentials.accessKeyId, + * secretAccessKey = credentials.secretKey, + * sessionToken = credentials.sessionToken, + * expiration = credentials.expiration + * ) + * ) * ``` * - * * @param region The AWS region as a string. * @param credentialsProvider The `CredentialsProvider` from AWS Kotlin SDK (`aws.smithy.kotlin.runtime.auth.awscredentials`). * @return A `LocationCredentialsProvider` object. From 1bbca8a041d222b27470426fe292ad7b788e52bd Mon Sep 17 00:00:00 2001 From: shah Date: Fri, 5 Jul 2024 15:39:35 +0530 Subject: [PATCH 14/14] feat: PR comment addressed --- README.md | 2 +- .../auth/LocationCredentialsProvider.kt | 43 +++++++++---------- .../amazon/location/auth/AuthHelperTest.kt | 4 +- 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 7ca658d..b73915a 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ OR // Create a credentail provider using custom credential provider with AuthHelper private fun exampleCustomCredentialLogin() { var authHelper = AuthHelper(applicationContext) - var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", "MY-CUSTOM-CREDENTIAL-PROVIDER") + var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", MY-CUSTOM-CREDENTIAL-PROVIDER) var locationClient = locationCredentialsProvider?.getLocationClient() } ``` diff --git a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt index 8548fb5..1ee7006 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -25,7 +25,7 @@ class LocationCredentialsProvider { private var customCredentials: aws.sdk.kotlin.services.cognitoidentity.model.Credentials? = null private var context: Context private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null - private var securePreferences: EncryptedSharedPreferences? = null + private var securePreferences: EncryptedSharedPreferences private var locationClient: LocationClient? = null private var cognitoIdentityClient: CognitoIdentityClient? = null @@ -37,10 +37,10 @@ class LocationCredentialsProvider { */ constructor(context: Context, identityPoolId: String, region: AwsRegions) { this.context = context - initPreference(context) - securePreferences?.put(METHOD, "cognito") - securePreferences?.put(IDENTITY_POOL_ID, identityPoolId) - securePreferences?.put(REGION, region.regionName) + securePreferences = initPreference(context) + securePreferences.put(METHOD, "cognito") + securePreferences.put(IDENTITY_POOL_ID, identityPoolId) + securePreferences.put(REGION, region.regionName) } /** @@ -50,9 +50,9 @@ class LocationCredentialsProvider { */ constructor(context: Context, region: AwsRegions) { this.context = context - initPreference(context) - securePreferences?.put(METHOD, "custom") - securePreferences?.put(REGION, region.regionName) + securePreferences = initPreference(context) + securePreferences.put(METHOD, "custom") + securePreferences.put(REGION, region.regionName) } @@ -63,13 +63,13 @@ class LocationCredentialsProvider { */ constructor(context: Context) { this.context = context - initPreference(context) - val method = securePreferences?.get(METHOD) + securePreferences = initPreference(context) + val method = securePreferences.get(METHOD) if (method === null) throw Exception("No credentials found") when (method) { "cognito" -> { - val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) - val region = securePreferences?.get(REGION) + val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) + val region = securePreferences.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") cognitoCredentialsProvider = CognitoCredentialsProvider(context) } @@ -79,9 +79,8 @@ class LocationCredentialsProvider { } } - fun initPreference(context: Context) { - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences?.initEncryptedSharedPreferences() + fun initPreference(context: Context): EncryptedSharedPreferences { + return EncryptedSharedPreferences(context, PREFS_NAME).apply { initEncryptedSharedPreferences() } } /** @@ -94,8 +93,8 @@ class LocationCredentialsProvider { * @throws Exception if the identity pool ID or region is not found, or if credential generation fails. */ suspend fun verifyAndRefreshCredentials() { - val identityPoolId = securePreferences?.get(IDENTITY_POOL_ID) - val region = securePreferences?.get(REGION) + val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) + val region = securePreferences.get(REGION) if (identityPoolId === null || region === null) throw Exception("No credentials found") val isCredentialsAvailable = try { cognitoCredentialsProvider = CognitoCredentialsProvider(context) @@ -121,7 +120,7 @@ class LocationCredentialsProvider { * @param credentialsProvider The provider for AWS credentials. */ suspend fun initializeLocationClient(credentialsProvider: CredentialsProvider) { - val region = securePreferences?.get(REGION) + val region = securePreferences.get(REGION) if (region === null) throw Exception("No credentials found") this.credentialsProvider = credentialsProvider @@ -156,7 +155,7 @@ class LocationCredentialsProvider { * @throws Exception if the AWS region is not found in secure preferences. */ fun getLocationClient(): LocationClient? { - val region = securePreferences?.get(REGION) + val region = securePreferences.get(REGION) if (region === null) throw Exception("No credentials found") if (locationClient == null) { val credentialsProvider = createCredentialsProvider() @@ -278,7 +277,7 @@ class LocationCredentialsProvider { * @throws Exception If the Cognito provider is not initialized. */ fun getCredentialsProvider(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials? { - val method = securePreferences?.get(METHOD) + val method = securePreferences.get(METHOD) if (method == "custom" && customCredentials != null) { return customCredentials } @@ -291,10 +290,10 @@ class LocationCredentialsProvider { * @throws Exception If the Cognito provider is not initialized. */ suspend fun refresh() { - val region = securePreferences?.get(REGION) + val region = securePreferences.get(REGION) if (region === null) throw Exception("No credentials found") - val method = securePreferences?.get(METHOD) + val method = securePreferences.get(METHOD) if (method == "custom" && customCredentials != null) { credentialsProvider?.let { setCustomCredentials(it, region) } } else { diff --git a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt index e302d62..72164e1 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -24,12 +24,14 @@ class AuthHelperTest { private lateinit var context: Context private lateinit var authHelper: AuthHelper private lateinit var credentialsProvider: CredentialsProvider + private lateinit var encryptedSharedPreferences: EncryptedSharedPreferences private lateinit var cognitoIdentityClient: CognitoIdentityClient @Before fun setUp() { context = mockk(relaxed = true) authHelper = AuthHelper(context) credentialsProvider = mockk(relaxed = true) + encryptedSharedPreferences = mockk(relaxed = true) cognitoIdentityClient = mockk(relaxed = true) mockkConstructor(EncryptedSharedPreferences::class) mockkConstructor(LocationCredentialsProvider::class) @@ -46,7 +48,7 @@ class AuthHelperTest { every { anyConstructed().generateCognitoIdentityClient("us-east-1") } returns cognitoIdentityClient coEvery { anyConstructed().initializeLocationClient(any()) } just runs coEvery { anyConstructed().isCredentialsValid() } returns true - every { anyConstructed().initPreference(context) } just runs + every { anyConstructed().initPreference(context) } returns encryptedSharedPreferences every { anyConstructed().initEncryptedSharedPreferences() } just runs }