diff --git a/README.md b/README.md index a6bc942..1f4360b 100644 --- a/README.md +++ b/README.md @@ -11,17 +11,34 @@ 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.2.5") -implementation("aws.sdk.kotlin:location:1.3.29") -implementation("org.maplibre.gl:android-sdk:11.0.0-pre5") +implementation("software.amazon.location:auth:1.0.0") +implementation("org.maplibre.gl:android-sdk:11.5.2") implementation("com.squareup.okhttp3:okhttp:4.12.0") ``` +For the new standalone Maps / Places / Routes SDKs, add the following lines: +``` +implementation("aws.sdk.kotlin:geomaps:1.3.65") +implementation("aws.sdk.kotlin:geoplaces:1.3.65") +implementation("aws.sdk.kotlin:georoutes:1.3.65") +``` + +For the consolidated Location SDK that includes Geofencing and Tracking, add the following line: +``` +implementation("aws.sdk.kotlin:location:1.3.65") +``` + ## Usage Import the following classes in your code: ``` +// For the standalone Maps / Places / Routes SDKs +import aws.sdk.kotlin.services.geomaps.GeoMapsClient +import aws.sdk.kotlin.services.geoplaces.GeoPlacesClient +import aws.sdk.kotlin.services.georoutes.GeoRoutesClient + +// For the consolidated Location SDK import aws.sdk.kotlin.services.location.LocationClient import software.amazon.location.auth.AuthHelper @@ -36,27 +53,44 @@ You can create an AuthHelper and use it with the AWS Kotlin SDK: ``` // Create a credential provider using Identity Pool Id with AuthHelper private suspend fun exampleCognitoLogin() { - var authHelper = AuthHelper(applicationContext) - var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCognitoIdentityPool("MY-COGNITO-IDENTITY-POOL-ID") - var locationClient = locationCredentialsProvider?.getLocationClient() + val authHelper = AuthHelper.withCognitoIdentityPool("MY-COGNITO-IDENTITY-POOL-ID") + + // Get instances of the standalone clients: + var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig()) + var geoPlacesClient = GeoPlacesClient(authHelper?.getGeoPlacesClientConfig()) + var geoRoutesClient = GeoRoutesClient(authHelper?.getGeoRoutesClientConfig()) + + // Get an instance of the Location client: + var locationClient = LocationClient(authHelper?.getLocationClientConfig()) } OR // Create a credential provider using custom credential provider with AuthHelper private suspend fun exampleCustomCredentialLogin() { - var authHelper = AuthHelper(applicationContext) - var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithCredentialsProvider("MY-AWS-REGION", MY-CUSTOM-CREDENTIAL-PROVIDER) - var locationClient = locationCredentialsProvider?.getLocationClient() -} + var authHelper = AuthHelper.withCredentialsProvider(MY-CUSTOM-CREDENTIAL-PROVIDER, "MY-AWS-REGION") + + // Get instances of the standalone clients: + var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig()) + var geoPlacesClient = GeoPlacesClient(authHelper?.getGeoPlacesClientConfig()) + var geoRoutesClient = GeoRoutesClient(authHelper?.getGeoRoutesClientConfig()) + + // Get an instance of the Location client: + var locationClient = LocationClient(authHelper?.getLocationClientConfig()) OR // Create a credential provider using Api key with AuthHelper private suspend fun exampleApiKeyLogin() { - var authHelper = AuthHelper(applicationContext) - var locationCredentialsProvider : LocationCredentialsProvider = authHelper.authenticateWithApiKey("MY-API-KEY", "MY-AWS-REGION") - var locationClient = locationCredentialsProvider?.getLocationClient() + var authHelper = AuthHelper.withApiKey("MY-API-KEY", "MY-AWS-REGION") + + // Get instances of the standalone clients: + var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig()) + var geoPlacesClient = GeoPlacesClient(authHelper?.getGeoPlacesClientConfig()) + var geoRoutesClient = GeoRoutesClient(authHelper?.getGeoRoutesClientConfig()) + + // Get an instance of the Location client: + var locationClient = LocationClient(authHelper?.getLocationClientConfig()) } ``` You can use the LocationCredentialsProvider to load the maplibre map. Here is an example of that: @@ -76,16 +110,15 @@ HttpRequestUtil.setOkHttpClient( ) ``` -You can use the LocationClient to make calls to Amazon Location Service. Here is an example that searches for places near a specified latitude and longitude: +You can use the created clients to make calls to Amazon Location Service. Here is an example that searches for places near a specified latitude and longitude: ``` -val searchPlaceIndexForPositionRequest = SearchPlaceIndexForPositionRequest { - indexName = "My-Place-Index-Name" - position = listOf(30.405423, -97.718833) +val suggestRequest = SuggestRequest { + biasPosition = listOf(-97.718833, 30.405423) maxResults = MAX_RESULT language = "PREFERRED-LANGUAGE" } -val nearbyPlaces = locationClient.searchPlaceIndexForPosition(request) +val nearbyPlaces = geoPlacesClient.suggest(suggestRequest) ``` ## Security diff --git a/build.gradle.kts b/build.gradle.kts index 26b2a2c..f11a4a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,4 +1,4 @@ plugins { - id("org.jetbrains.kotlin.android") version "1.9.0" apply false - id("com.android.library") version "8.2.2" apply false + id("org.jetbrains.kotlin.android") version "1.9.21" apply false + id("com.android.library") version "8.3.2" apply false } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index d4d7e1f..af912f7 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -2,14 +2,40 @@ # https://docs.gradle.org/current/userguide/platforms.html#sub::toml-dependencies-format [versions] +androidx-junit = "1.2.1" +appcompat = "1.7.0" commons-math3 = "3.6.1" +core-ktx = "1.15.0" +espresso-core = "3.6.1" guava = "32.1.3-jre" +junit = "4.13.2" junit-jupiter-engine = "5.10.0" +kotlin-test = "2.0.21" +location = "1.3.65" +material = "1.12.0" +mockk = "1.13.13" +okhttp = "4.12.0" +security-crypto = "1.1.0-alpha06" [libraries] +appcompat = { module = "androidx.appcompat:appcompat", version.ref = "appcompat" } +cognitoidentity = { module = "aws.sdk.kotlin:cognitoidentity", version.ref = "location" } commons-math3 = { module = "org.apache.commons:commons-math3", version.ref = "commons-math3" } +core-ktx = { module = "androidx.core:core-ktx", version.ref = "core-ktx" } +espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso-core" } +ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-junit" } +geomaps = { module = "aws.sdk.kotlin:geomaps", version.ref = "location" } +geoplaces = { module = "aws.sdk.kotlin:geoplaces", version.ref = "location" } +georoutes = { module = "aws.sdk.kotlin:georoutes", version.ref = "location" } guava = { module = "com.google.guava:guava", version.ref = "guava" } +junit = { module = "junit:junit", version.ref = "junit" } junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine", version.ref = "junit-jupiter-engine" } +kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin-test" } +location = { module = "aws.sdk.kotlin:location", version.ref = "location" } +material = { module = "com.google.android.material:material", version.ref = "material" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" } +security-crypto = { module = "androidx.security:security-crypto", version.ref = "security-crypto" } [plugins] jvm = { id = "org.jetbrains.kotlin.jvm", version = "1.9.20" } diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ed6db1e..1964fdf 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -20,7 +20,7 @@ mavenPublishing { publishToMavenCentral(SonatypeHost.DEFAULT, automaticRelease = true) signAllPublications() - coordinates("software.amazon.location", "auth", "0.2.5") + coordinates("software.amazon.location", "auth", "1.0.0") pom { name.set("Amazon Location Service Mobile Authentication SDK for Android") @@ -83,18 +83,21 @@ android { } dependencies { - implementation("androidx.core:core-ktx:1.13.1") - implementation("androidx.appcompat:appcompat:1.7.0") - implementation("com.google.android.material:material:1.12.0") - implementation("androidx.security:security-crypto:1.1.0-alpha06") - implementation("aws.sdk.kotlin:cognitoidentity:1.3.29") - implementation("aws.sdk.kotlin:location:1.3.29") - implementation("com.squareup.okhttp3:okhttp:4.12.0") + implementation(libs.core.ktx) + implementation(libs.appcompat) + implementation(libs.material) + implementation(libs.security.crypto) + implementation(libs.cognitoidentity) + implementation(libs.location) + implementation(libs.geomaps) + implementation(libs.geoplaces) + implementation(libs.georoutes) + implementation(libs.okhttp) - testImplementation("junit:junit:4.13.2") - testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.20") - testImplementation("io.mockk:mockk:1.13.10") + testImplementation(libs.junit) + testImplementation(libs.kotlin.test) + testImplementation(libs.mockk) - androidTestImplementation("androidx.test.ext:junit:1.2.1") - androidTestImplementation("androidx.test.espresso:espresso-core:3.6.1") + androidTestImplementation(libs.ext.junit) + androidTestImplementation(libs.espresso.core) } 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 387dac2..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?.remove(API_KEY) - } -} \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/ApiKeyInterceptor.kt b/library/src/main/java/software/amazon/location/auth/ApiKeyInterceptor.kt index edf7a5c..1ee326c 100644 --- a/library/src/main/java/software/amazon/location/auth/ApiKeyInterceptor.kt +++ b/library/src/main/java/software/amazon/location/auth/ApiKeyInterceptor.kt @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext @@ -14,11 +17,11 @@ class ApiKeyInterceptor( override suspend fun modifyBeforeSigning(context: ProtocolRequestInterceptorContext): HttpRequest { val req = context.protocolRequest.toBuilder() - if (!context.protocolRequest.url.toString().contains("$QUERY_PARAM_KEY=")) { + return if (!context.protocolRequest.url.toString().contains("$QUERY_PARAM_KEY=")) { req.url(Url.parse(context.protocolRequest.url.toString()+"?$QUERY_PARAM_KEY=$apiKey")) - return req.build() + req.build() } else { - return super.modifyBeforeSigning(context) + super.modifyBeforeSigning(context) } } } 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 b948f8b..3b9f2a6 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,8 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + 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 @@ -9,24 +11,22 @@ import software.amazon.location.auth.utils.AwsRegions /** * Provides methods for authenticating with AWS services using different credentials providers. */ -class AuthHelper(private val context: Context) { +object AuthHelper { /** * Authenticates using a Cognito Identity Pool ID and infers the region. * @param identityPoolId The identity pool id for authentication. * @return A LocationCredentialsProvider object. */ - suspend fun authenticateWithCognitoIdentityPool( + suspend fun withCognitoIdentityPool( identityPoolId: String, ): LocationCredentialsProvider { return withContext(Dispatchers.IO) { val locationCredentialsProvider = LocationCredentialsProvider( - context, identityPoolId, // Get the region from the identity pool id AwsRegions.fromName(identityPoolId.split(":")[0]), ) - locationCredentialsProvider.verifyAndRefreshCredentials() locationCredentialsProvider // Return the generated locationCredentialsProvider } } @@ -37,38 +37,15 @@ class AuthHelper(private val context: Context) { * @param region The AWS region as a string. * @return A LocationCredentialsProvider object. */ - suspend fun authenticateWithCognitoIdentityPool( + suspend fun withCognitoIdentityPool( identityPoolId: String, region: String, ): LocationCredentialsProvider { return withContext(Dispatchers.IO) { val locationCredentialsProvider = LocationCredentialsProvider( - context, identityPoolId, AwsRegions.fromName(region), ) - locationCredentialsProvider.verifyAndRefreshCredentials() - locationCredentialsProvider // Return the generated locationCredentialsProvider - } - } - - /** - * Authenticates using a Cognito Identity Pool ID and a specified region. - * @param identityPoolId The identity pool id. - * @param region The AWS region as a Regions enum. - * @return A LocationCredentialsProvider object. - */ - suspend fun authenticateWithCognitoIdentityPool( - identityPoolId: String, - region: AwsRegions, - ): LocationCredentialsProvider { - return withContext(Dispatchers.IO) { - val locationCredentialsProvider = LocationCredentialsProvider( - context, - identityPoolId, - region, - ) - locationCredentialsProvider.verifyAndRefreshCredentials() locationCredentialsProvider // Return the generated locationCredentialsProvider } } @@ -101,20 +78,19 @@ class AuthHelper(private val context: Context) { * ) * ``` * - * @param region The AWS region as a string. * @param credentialsProvider The `CredentialsProvider` from AWS Kotlin SDK (`aws.smithy.kotlin.runtime.auth.awscredentials`). + * @param region The AWS region as a string. * @return A `LocationCredentialsProvider` object. */ - suspend fun authenticateWithCredentialsProvider( + suspend fun withCredentialsProvider( + credentialsProvider: CredentialsProvider, region: String, - credentialsProvider: CredentialsProvider ): LocationCredentialsProvider { return withContext(Dispatchers.IO) { val locationCredentialsProvider = LocationCredentialsProvider( - context, + credentialsProvider, AwsRegions.fromName(region), ) - locationCredentialsProvider.initializeLocationClient(credentialsProvider) locationCredentialsProvider } } @@ -125,14 +101,12 @@ class AuthHelper(private val context: Context) { * @param region The AWS region as a string. * @return A LocationCredentialsProvider instance. */ - suspend fun authenticateWithApiKey(apiKey: String, region: String): LocationCredentialsProvider { + suspend fun withApiKey(apiKey: String, region: String): LocationCredentialsProvider { return withContext(Dispatchers.IO) { val locationCredentialsProvider = LocationCredentialsProvider( - context, AwsRegions.fromName(region), apiKey, ) - locationCredentialsProvider.initializeLocationClient() locationCredentialsProvider } } diff --git a/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt b/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt index dfc3a70..0fa7d25 100644 --- a/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt +++ b/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt @@ -1,7 +1,9 @@ -package software.amazon.location.auth +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.location.auth -import android.content.Context +import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import java.net.URL import java.nio.charset.StandardCharsets import java.security.MessageDigest @@ -14,44 +16,46 @@ import kotlinx.coroutines.runBlocking import okhttp3.Interceptor import okhttp3.Response import software.amazon.location.auth.utils.Constants -import software.amazon.location.auth.utils.Constants.API_KEY import software.amazon.location.auth.utils.Constants.HEADER_HOST import software.amazon.location.auth.utils.Constants.HEADER_X_AMZ_CONTENT_SHA256 import software.amazon.location.auth.utils.Constants.HEADER_X_AMZ_DATE import software.amazon.location.auth.utils.Constants.HEADER_X_AMZ_SECURITY_TOKEN -import software.amazon.location.auth.utils.Constants.METHOD import software.amazon.location.auth.utils.Constants.QUERY_PARAM_KEY import software.amazon.location.auth.utils.Constants.TIME_PATTERN import software.amazon.location.auth.utils.HASHING_ALGORITHM import software.amazon.location.auth.utils.awsAuthorizationHeader class AwsSignerInterceptor( - private val context: Context, private val serviceName: String, private val region: String, private val credentialsProvider: LocationCredentialsProvider? ) : Interceptor { private val sdfMap = HashMap() - private var securePreferences: EncryptedSharedPreferences?= null override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() - if (!originalRequest.url.host.contains("amazonaws.com") || credentialsProvider?.getCredentialsProvider() == null) { - return chain.proceed(originalRequest) + val credentials: Credentials? + runBlocking { + credentials = credentialsProvider?.getCredentials() } - if (securePreferences == null){ - securePreferences = initPreference(context) + if (!originalRequest.url.host.contains("amazonaws.com") || credentials == null) { + return chain.proceed(originalRequest) } - val method = securePreferences?.get(METHOD) + val method = credentialsProvider?.getMethod() if (method === null) throw Exception("No credentials found") if (method == "apiKey") { val originalHttpUrl = originalRequest.url val hasKey = originalHttpUrl.queryParameter(QUERY_PARAM_KEY) != null val newHttpUrl = if (!hasKey) { - val apiKey = securePreferences?.get(API_KEY) - originalHttpUrl.newBuilder() - .addQueryParameter(QUERY_PARAM_KEY, apiKey) - .build() + val apiKey = credentialsProvider?.getApiKey() + if (!apiKey.isNullOrEmpty()) { + originalHttpUrl.newBuilder() + .addQueryParameter(QUERY_PARAM_KEY, apiKey) + .build() + } + else { + originalHttpUrl + } } else { originalHttpUrl } @@ -61,14 +65,9 @@ class AwsSignerInterceptor( return chain.proceed(newRequest) } else { - runBlocking { - if (!credentialsProvider.isCredentialsValid()) { - credentialsProvider.verifyAndRefreshCredentials() - } - } - val accessKeyId = credentialsProvider.getCredentialsProvider().accessKeyId - val secretKey = credentialsProvider.getCredentialsProvider().secretKey - val sessionToken = credentialsProvider.getCredentialsProvider().sessionToken + val accessKeyId = credentials.accessKeyId + val secretKey = credentials.secretAccessKey + val sessionToken = credentials.sessionToken if (!accessKeyId.isNullOrEmpty() && !secretKey.isNullOrEmpty() && !sessionToken.isNullOrEmpty() && region.isNotEmpty()) { val dateMilli = Date().time val host = extractHostHeader(originalRequest.url.toString()) @@ -101,10 +100,6 @@ class AwsSignerInterceptor( } } - fun initPreference(context: Context): EncryptedSharedPreferences { - return EncryptedSharedPreferences(context, PREFS_NAME).apply { initEncryptedSharedPreferences() } - } - private fun extractHostHeader(urlString: String): String { val url = URL(urlString) return url.host diff --git a/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt b/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt index 439ee5c..98ab40c 100644 --- a/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt @@ -1,102 +1,126 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context -import aws.sdk.kotlin.services.cognitoidentity.model.Credentials +import android.util.Log +import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import aws.smithy.kotlin.runtime.collections.Attributes +import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider +import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient +import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityRequest +import aws.sdk.kotlin.services.cognitoidentity.model.GetIdRequest +import aws.smithy.kotlin.runtime.http.HttpException import aws.smithy.kotlin.runtime.time.Instant import aws.smithy.kotlin.runtime.time.epochMilliseconds -import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds -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_ID -import software.amazon.location.auth.utils.Constants.SECRET_KEY -import software.amazon.location.auth.utils.Constants.SESSION_TOKEN - -/** - * Provides Cognito credentials for accessing services and manages their storage. - */ -class CognitoCredentialsProvider { - private var securePreferences: EncryptedSharedPreferences? = null +// Provide credentials for the given Cognito identity pool. +class CognitoCredentialsProvider /** - * Constructor that initializes the provider with a context and credentials. - * @param context The application context used to initialize the key-value store. - * @param identityId The identityId to be saved in the key-value store. - * @param credentials The credentials to be saved in the key-value store. + * Create a CognitoCredentialsProvider that handles the given identity pool ID. + * The credentials themselves will be lazily fetched on the first resolve() call. + * + * @param identityPoolId The identity pool ID for Cognito. + * @param identityRegion The AWS region where the identity pool is located. */ - constructor(context: Context, identityId: String, credentials: Credentials) { - initialize(context) - saveCredentials(identityId, credentials) - } + ( + private var identityPoolId: String,// Keep track of the region and identity pool ID for use during credential refreshes. + private var identityRegion: String + ) : CredentialsProvider { - /** - * Constructor that initializes the provider with a context and retrieves cached credentials. - * Throws an exception if no cached credentials are found. - * @param context The application context used to initialize the key-value store. - */ - constructor(context: Context) { - initialize(context) - val credentials = getCachedCredentials() - if (credentials === null) throw Exception("No credentials found") - } + // staticCredentialsProvider holds the cached credentials. + // (Defaults to empty credentials) + private var staticCredentialsProvider = StaticCredentialsProvider( + Credentials.invoke( + accessKeyId = "", + secretAccessKey = "", + sessionToken = null, + expiration = null + )) /** - * Initializes the AWSKeyValueStore with the given context. - * @param context The application context used to initialize the key-value store. + * Provide the credentials, but refresh them first if they've expired. + * + * @param attributes + * @return The Cognito credentials to use for authentication. */ - private fun initialize(context: Context) { - securePreferences = EncryptedSharedPreferences(context, PREFS_NAME) - securePreferences?.initEncryptedSharedPreferences() + override suspend fun resolve(attributes: Attributes): Credentials { + if (!areCredentialsValid()) { + refreshCognitoCredentials() + } + + return staticCredentialsProvider.credentials } /** - * Saves the given credentials to the key-value store. - * @param identityId The identityId to be saved in the key-value store. - * @param credentials The credentials to be saved in the key-value store. - * @throws Exception if the key-value store is not initialized. + * Fetches a new set of credentials from Cognito. + * + * All of the Cognito access has been pulled out into this function so that it's easy to mock + * away the Cognito calls in unit tests by mocking this method. Successfully mocking at the + * Cognito level is much more difficult due to the nested Builder() calls and lambdas that + * get invoked. + * + * @throws Exception if the credential generation fails. */ - private fun saveCredentials(identityId: String, credentials: Credentials) { - if (securePreferences === null) throw Exception("Not initialized") - securePreferences?.put(IDENTITY_ID, identityId) - credentials.accessKeyId?.let { securePreferences?.put(ACCESS_KEY_ID, it) } - credentials.secretKey?.let { securePreferences?.put(SECRET_KEY, it) } - credentials.sessionToken?.let { securePreferences?.put(SESSION_TOKEN, it) } - credentials.expiration?.let { - securePreferences?.put( - EXPIRATION, - it.epochMilliseconds.toString() - ) + suspend fun fetchCognitoCredentials(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials { + var credentials = aws.sdk.kotlin.services.cognitoidentity.model.Credentials.invoke {} + + try { + val poolId = identityPoolId + val cognitoIdentityClient = CognitoIdentityClient { this.region = identityRegion } + val identityId = cognitoIdentityClient.getId(GetIdRequest { this.identityPoolId = poolId }) + .identityId ?: throw Exception("Failed to get identity ID for identity pool") + + if (identityId.isNotEmpty()) { + credentials = cognitoIdentityClient.getCredentialsForIdentity( + GetCredentialsForIdentityRequest { this.identityId = identityId }) + .credentials ?: throw Exception("Failed to get credentials for identity") + } + } catch (e: HttpException) { + Log.e("Auth", "Credentials generation failed: ${e.cause} ${e.message}") + throw HttpException("Credentials generation failed", e) + } catch (e: Exception) { + throw Exception("Credentials generation failed", e) } + return credentials } /** - * Retrieves cached credentials from the key-value store. - * @return A Credentials object if all required fields are present, otherwise null. + * Generates new AWS credentials using the specified region and identity pool ID. + * + * This function fetches the identity ID and credentials from Cognito, and then initializes + * the CognitoCredentialsProvider with the retrieved credentials. + * + * @throws Exception if the credential generation fails. */ - fun getCachedCredentials(): Credentials? { - if (securePreferences === null) return null - val mAccessKeyId = securePreferences?.get(ACCESS_KEY_ID) - val mSecretKey = securePreferences?.get(SECRET_KEY) - val mSessionToken = securePreferences?.get(SESSION_TOKEN) - val mExpiration = securePreferences?.get(EXPIRATION) - if (mAccessKeyId.isNullOrEmpty() || mSecretKey.isNullOrEmpty() || mSessionToken.isNullOrEmpty() || mExpiration.isNullOrEmpty()) return null - return Credentials.invoke { - accessKeyId = mAccessKeyId - secretKey = mSecretKey - sessionToken = mSessionToken - expiration = Instant.fromEpochMilliseconds(mExpiration.toLong()) + private suspend fun refreshCognitoCredentials() { + try { + val credentials = fetchCognitoCredentials() + requireNotNull(credentials.accessKeyId) { "Access key ID is null" } + requireNotNull(credentials.secretKey) { "Secret key is null" } + requireNotNull(credentials.sessionToken) { "Session token is null" } + + staticCredentialsProvider = StaticCredentialsProvider( + Credentials.invoke( + accessKeyId = credentials.accessKeyId!!, + secretAccessKey = credentials.secretKey!!, + sessionToken = credentials.sessionToken, + expiration = credentials.expiration + )) + } catch (e: Exception) { + throw e } } /** - * Clears all credentials from the key-value store. + * Check to see if the credentials have expired yet or not. */ - fun clearCredentials() { - if (securePreferences === null) throw Exception("Not initialized") - securePreferences?.remove(IDENTITY_ID) - securePreferences?.remove(ACCESS_KEY_ID) - securePreferences?.remove(SECRET_KEY) - securePreferences?.remove(SESSION_TOKEN) - securePreferences?.remove(EXPIRATION) + private fun areCredentialsValid(): Boolean { + if (staticCredentialsProvider.credentials.expiration == null) return false + + val expirationTimeMillis = staticCredentialsProvider.credentials.expiration!!.epochMilliseconds + return Instant.now().epochMilliseconds < expirationTimeMillis } -} \ No newline at end of file +} diff --git a/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt b/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt deleted file mode 100644 index 9435865..0000000 --- a/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt +++ /dev/null @@ -1,101 +0,0 @@ -package software.amazon.location.auth - -import android.content.Context -import android.content.SharedPreferences -import androidx.security.crypto.EncryptedSharedPreferences -import androidx.security.crypto.MasterKey -import java.io.IOException -import java.security.GeneralSecurityException - -/** - * Wrapper class for encrypted SharedPreferences. - * Provides methods to initialize, store, retrieve, and clear encrypted preferences. - * - * @property context The application context. - * @property preferenceName The name of the preferences file. - */ -class EncryptedSharedPreferences(private val context: Context, private val preferenceName: String) { - private var sharedPreferences: SharedPreferences? = null - - /** - * Initializes the EncryptedSharedPreferences instance. - * - * @throws RuntimeException if initialization fails due to security or I/O issues. - */ - fun initEncryptedSharedPreferences() { - try { - val masterKey = MasterKey.Builder(context) - .setKeyScheme(MasterKey.KeyScheme.AES256_GCM) - .build() - - sharedPreferences = EncryptedSharedPreferences.create( - context, - preferenceName, - masterKey, - EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV, - EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM - ) - - } catch (e: GeneralSecurityException) { - throw RuntimeException("Failed to initialize encrypted preferences", e) - } catch (e: IOException) { - throw RuntimeException("Failed to initialize encrypted preferences", e) - } - } - - /** - * Stores a string value in the encrypted preferences. - * @param key The key under which to store the value. - * @param value The value to store. - * @throws Exception if preferences are not initialized. - */ - fun put(key: String, value: String) { - if (sharedPreferences === null) throw Exception("SharedPreferences not initialized") - val editor = sharedPreferences!!.edit() - editor.putString(key, value) - editor.apply() - } - - /** - * Retrieves a string value from the encrypted preferences. - * @param key The key of the value to retrieve. - * @return The retrieved value, or null if the key does not exist. - * @throws Exception if preferences are not initialized. - */ - fun get(key: String): String? { - if (sharedPreferences === null) throw Exception("SharedPreferences not initialized") - return sharedPreferences!!.getString(key, null) - } - - /** - * Clears all entries from the encrypted preferences. - * @throws Exception if preferences are not initialized. - */ - fun clear() { - if (sharedPreferences === null) throw Exception("SharedPreferences not initialized") - sharedPreferences!!.edit().clear().apply() - } - - /** - * Removes a particular key from the encrypted preferences. - * @param key The key to remove. - * @throws Exception if preferences are not initialized. - */ - fun remove(key: String) { - if (sharedPreferences === null) throw Exception("SharedPreferences not initialized") - val editor = sharedPreferences!!.edit() - editor.remove(key) - editor.apply() - } - - /** - * Checks if a particular key exists in the encrypted preferences. - * @param key The key to check for existence. - * @return True if the key exists, false otherwise. - * @throws Exception if preferences are not initialized. - */ - fun contains(key: String): Boolean { - if (sharedPreferences === null) throw Exception("SharedPreferences not initialized") - return sharedPreferences!!.contains(key) - } -} 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 f262ffa..70629df 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -1,411 +1,195 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context -import android.util.Log import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider -import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient -import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityRequest -import aws.sdk.kotlin.services.cognitoidentity.model.GetIdRequest -import aws.sdk.kotlin.services.location.LocationClient import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials +import aws.sdk.kotlin.services.geomaps.GeoMapsClient +import aws.sdk.kotlin.services.geoplaces.GeoPlacesClient +import aws.sdk.kotlin.services.georoutes.GeoRoutesClient +import aws.sdk.kotlin.services.location.LocationClient import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider -import aws.smithy.kotlin.runtime.http.HttpException -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 - -const val PREFS_NAME = "software.amazon.location.auth" /** - * Provides credentials for accessing location-based services through Cognito authentication. - */ + * Provides credentials for accessing location-based services through Cognito authentication, + * custom authentication, or API keys. + **/ class LocationCredentialsProvider { - private var credentialsProvider: CredentialsProvider? = null - private var customCredentials: aws.sdk.kotlin.services.cognitoidentity.model.Credentials? = null - private var emptyCredentials: aws.sdk.kotlin.services.cognitoidentity.model.Credentials? = null - private var context: Context - private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null - private var securePreferences: EncryptedSharedPreferences - private var locationClient: LocationClient? = null - private var cognitoIdentityClient: CognitoIdentityClient? = null - private var apiKeyProvider: ApiKeyCredentialsProvider? = null + private var credentialsProvider : CredentialsProvider + + private var identityPoolId: String? = null + private var method: String + private var region: AwsRegions? = null + private var apiKey: String? = null /** * Initializes with Cognito credentials. - * @param context The application context. * @param identityPoolId The identity pool ID for Cognito authentication. * @param region The region for Cognito authentication. */ - constructor(context: Context, identityPoolId: String, region: AwsRegions) { - this.context = context - securePreferences = initPreference(context) - securePreferences.put(METHOD, "cognito") - securePreferences.put(IDENTITY_POOL_ID, identityPoolId) - securePreferences.put(REGION, region.regionName) + constructor(identityPoolId: String, region: AwsRegions) { + this.method = "cognito" + this.identityPoolId = identityPoolId + this.region = region + this.credentialsProvider = CognitoCredentialsProvider(identityPoolId, region.regionName) } /** - * Initializes with region. - * @param context The application context. - * @param region The region for Cognito authentication. + * Initializes with custom authentication. + * @param credentialsProvider The custom credentials provider to use for authentication. + * @param region The region for authentication. */ - constructor(context: Context, region: AwsRegions) { - this.context = context - securePreferences = initPreference(context) - securePreferences.put(METHOD, "custom") - securePreferences.put(REGION, region.regionName) + constructor(credentialsProvider: CredentialsProvider, region: AwsRegions) { + this.method = "custom" + this.region = region + this.credentialsProvider = credentialsProvider } /** * Initializes with an API key. - * @param context The application context. + * NOTE: The order of the parameters are important here to distinguish this constructor + * from the Cognito constructor that takes in (identityPoolId, region). * @param region The region for Cognito authentication. * @param apiKey The API key for authentication. */ - constructor(context: Context, region: AwsRegions, apiKey: String) { - this.context = context - securePreferences = initPreference(context) - securePreferences.put(METHOD, "apiKey") - securePreferences.put(API_KEY, apiKey) - securePreferences.put(REGION, region.regionName) - apiKeyProvider = ApiKeyCredentialsProvider(context, apiKey) - } - - - /** - * Initializes with cached credentials. - * @param context The application context. - * @throws Exception If credentials are not found. - */ - constructor(context: Context) { - this.context = context - 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) - if (identityPoolId === null || region === null) throw Exception("No credentials found") - cognitoCredentialsProvider = CognitoCredentialsProvider(context) - } - "apiKey" -> { - val apiKey = securePreferences.get(API_KEY) - val region = securePreferences.get(REGION) - if (apiKey === null || region === null) throw Exception("No credentials found") - apiKeyProvider = ApiKeyCredentialsProvider(context, apiKey) - } - else -> { - throw Exception("No credentials found") - } - } - } - - fun initPreference(context: Context): EncryptedSharedPreferences { - return EncryptedSharedPreferences(context, PREFS_NAME).apply { initEncryptedSharedPreferences() } - } - - /** - * Checks AWS credentials availability and validity. - * - * This function retrieves the identity pool ID and region from secure preferences. It then - * attempts to initialize the CognitoCredentialsProvider. If no credentials are found or if - * the cached credentials are invalid, new credentials are generated. - * - * @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) - 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()) { - generateCredentials(region, identityPoolId) - } - } - } - - /** - * 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. - * - * @param credentialsProvider The provider for AWS credentials. - */ - suspend fun initializeLocationClient(credentialsProvider: CredentialsProvider) { - val region = securePreferences.get(REGION) - if (region === null) throw Exception("No credentials found") - - this.credentialsProvider = credentialsProvider - 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 { - accessKeyId = credentials.accessKeyId - secretKey = credentials.secretAccessKey - sessionToken = credentials.sessionToken - expiration = credentials.expiration - } - locationClient = generateLocationClient(region, credentialsProvider) - } - - /** - * Initializes the AWS Location Service client using API key. - * - * This function retrieves the API key and region from `securePreferences`, - * creates an empty credentials provider, and generates the `locationClient` - * with the provided region and API key. - * - * @throws Exception if the API key or region is not found in `securePreferences`. - */ - suspend fun initializeLocationClient() { - val apiKey = securePreferences.get(API_KEY) - val region = securePreferences.get(REGION) - if (apiKey === null || region === null) throw Exception("No credentials found") - - val credentials = createEmptyCredentialsProvider().resolve() - emptyCredentials = aws.sdk.kotlin.services.cognitoidentity.model.Credentials.invoke { - accessKeyId = credentials.accessKeyId - secretKey = credentials.secretAccessKey - sessionToken = credentials.sessionToken - } - locationClient = generateLocationClient(region, createEmptyCredentialsProvider(), apiKey) - } - - /** - * Creates an empty `StaticCredentialsProvider` with no access keys or secret keys. - * - * This is useful for bypassing the default credentials provider chain when - * credentials are not yet available or are not required for certain operations. - * - * @return A `StaticCredentialsProvider` instance with empty credentials. - */ - private fun createEmptyCredentialsProvider(): CredentialsProvider = - StaticCredentialsProvider( + constructor(region: AwsRegions, apiKey: String) { + this.method = "apiKey" + this.apiKey = apiKey + this.region = region + // API Keys "empty out" the credentials provider, since the authentication happens + // via an HTTP interceptor that adds the API key to the HTTP request. + this.credentialsProvider = StaticCredentialsProvider( Credentials.invoke( accessKeyId = "", secretAccessKey = "", sessionToken = null, - ), - ) + expiration = null + )) + } /** - * Retrieves the LocationClient instance with configured AWS credentials. - * - * This function initializes and returns the LocationClient with the AWS region and - * credentials retrieved from secure preferences. - * - * @return An instance of LocationClient for interacting with the Amazon Location service. - * @throws Exception if the AWS region is not found in secure preferences. + * Get the config for constructing a LocationClient() with the correct credentials. + * Note that this actually returns a lambda that builds the config, since the LocationClient + * constructor actually takes in a builder lambda, not a config. + * Usage looks like this: + * val client = LocationClient(provider.getLocationClientConfig()) + * @throws Exception If credentials are not found. + * @return Lambda that builds a LocationClient config. */ - fun getLocationClient(): LocationClient? { - val region = securePreferences.get(REGION) ?: throw Exception("No credentials found") - - if (locationClient == null) { - val method = securePreferences.get(METHOD) ?: throw Exception("No credentials found") - locationClient = when (method) { - "apiKey" -> { - val apiKey = securePreferences.get(API_KEY) ?: throw Exception("API key not found") - generateLocationClient(region, createEmptyCredentialsProvider(), apiKey) - } - else -> { - val credentialsProvider = createCredentialsProvider() - generateLocationClient(region, credentialsProvider) - } + fun getLocationClientConfig() : LocationClient.Config.Builder.() -> Unit { + val region = this.region?.regionName ?: throw Exception("No valid region provided") + val apiKey = this.apiKey + val credentialsProvider = this.credentialsProvider + return { + this.region = region + this.credentialsProvider = credentialsProvider + if (apiKey != null) { + this.interceptors = mutableListOf(ApiKeyInterceptor(apiKey)) } } - return locationClient } - /** - * Generates a new instance of [LocationClient] with the specified region, - * credentials provider, and optional API key for request signing. - * - * @param region The AWS region to configure for the [LocationClient]. - * @param credentialsProvider The credentials provider for the [LocationClient]. - * It supplies the credentials required for authenticating requests. - * @param apiKey Optional. The API key to use for signing requests. If provided, - * an [ApiKeyInterceptor] will be added to the [LocationClient]. - * @return A new instance of [LocationClient] configured with the specified parameters. + * Get the config for constructing a GeoMapsClient() with the correct credentials. + * Note that this actually returns a lambda that builds the config, since the GeoMapsClient + * constructor actually takes in a builder lambda, not a config. + * Usage looks like this: + * val client = GeoMapsClient(provider.getGeoMapsClientConfig()) + * @throws Exception If credentials are not found. + * @return Lambda that builds a GeoMapsClient config. */ - fun generateLocationClient( - region: String, - credentialsProvider: CredentialsProvider, - apiKey: String? = null - ): LocationClient { - return LocationClient { + fun getGeoMapsClientConfig() : GeoMapsClient.Config.Builder.() -> Unit { + val region = this.region?.regionName ?: throw Exception("No valid region provided") + val apiKey = this.apiKey + val credentialsProvider = this.credentialsProvider + return { this.region = region this.credentialsProvider = credentialsProvider - apiKey?.let { - this.interceptors = mutableListOf(ApiKeyInterceptor(it)) + if (apiKey != null) { + this.interceptors = mutableListOf(ApiKeyInterceptor(apiKey)) } } } /** - * Creates a new instance of CredentialsProvider using the credentials obtained from the current provider. - * - * This function constructs a CredentialsProvider with the AWS credentials retrieved from the existing provider. - * It extracts the access key ID, secret access key, and session token from the current provider and initializes - * a StaticCredentialsProvider with these credentials. - * - * @return A new instance of CredentialsProvider initialized with the current AWS credentials. - * @throws Exception if credentials cannot be retrieved. - */ - private fun createCredentialsProvider(): CredentialsProvider { - if (getCredentialsProvider().accessKeyId == null || getCredentialsProvider().secretKey == null) throw Exception( - "Failed to get credentials" - ) - return StaticCredentialsProvider( - Credentials.invoke( - accessKeyId = getCredentialsProvider().accessKeyId!!, - secretAccessKey = getCredentialsProvider().secretKey!!, - sessionToken = getCredentialsProvider().sessionToken, - expiration = getCredentialsProvider().expiration - ) - ) - } - - /** - * Generates new AWS credentials using the specified region and identity pool ID. - * - * This function fetches the identity ID and credentials from Cognito, and then initializes - * the CognitoCredentialsProvider with the retrieved credentials. - * - * @param region The AWS region where the identity pool is located. - * @param identityPoolId The identity pool ID for Cognito. - * @throws Exception if the credential generation fails. + * Get the config for constructing a GeoPlacesClient() with the correct credentials. + * Note that this actually returns a lambda that builds the config, since the GeoPlacesClient + * constructor actually takes in a builder lambda, not a config. + * Usage looks like this: + * val client = GeoPlacesClient(provider.getGeoPlacesClientConfig()) + * @throws Exception If credentials are not found. + * @return Lambda that builds a GeoPlacesClient config. */ - private suspend fun generateCredentials(region: String, identityPoolId: String) { - try { - cognitoIdentityClient ?: run { - cognitoIdentityClient = generateCognitoIdentityClient(region) - } - val identityId = cognitoIdentityClient?.getId(GetIdRequest { this.identityPoolId = identityPoolId }) - ?.identityId ?: throw Exception("Failed to get identity ID") - - if (identityId.isNotEmpty()) { - val credentials = cognitoIdentityClient?.getCredentialsForIdentity(GetCredentialsForIdentityRequest { this.identityId = identityId }) - ?.credentials ?: throw Exception("Failed to get credentials") - - requireNotNull(credentials.accessKeyId) { "Access key ID is null" } - requireNotNull(credentials.secretKey) { "Secret key is null" } - requireNotNull(credentials.sessionToken) { "Session token is null" } - - cognitoCredentialsProvider = CognitoCredentialsProvider(context, identityId, credentials) - locationClient = null + fun getGeoPlacesClientConfig() : GeoPlacesClient.Config.Builder.() -> Unit { + val region = this.region?.regionName ?: throw Exception("No valid region provided") + val apiKey = this.apiKey + val credentialsProvider = this.credentialsProvider + return { + this.region = region + this.credentialsProvider = credentialsProvider + if (apiKey != null) { + this.interceptors = mutableListOf(ApiKeyInterceptor(apiKey)) } - } catch (e: HttpException) { - Log.e("Auth", "Credentials generation failed: ${e.cause} ${e.message}") - throw HttpException("Credentials generation failed") - } catch (e: Exception) { - throw Exception("Credentials generation failed", e) } } /** - * Generates a new instance of CognitoIdentityClient with the specified region. - * - * @param region The AWS region for the CognitoIdentityClient. - * @return A new instance of CognitoIdentityClient. - */ - fun generateCognitoIdentityClient(region: String): CognitoIdentityClient { - return CognitoIdentityClient { this.region = region } - } - - /** - * Checks if the provided credentials are still valid. - * - * @return True if the credentials are valid (i.e., not expired), false otherwise. + * Get the config for constructing a GeoRoutesClient() with the correct credentials. + * Note that this actually returns a lambda that builds the config, since the GeoRoutesClient + * constructor actually takes in a builder lambda, not a config. + * Usage looks like this: + * val client = GeoRoutesClient(provider.getGeoRoutesClientConfig()) + * @throws Exception If credentials are not found. + * @return Lambda that builds a GeoRoutesClient config. */ - fun isCredentialsValid(): Boolean { - val method = securePreferences.get(METHOD) - if (method == "apiKey") return true - - val expirationTimeMillis = customCredentials?.expiration?.epochMilliseconds - ?: cognitoCredentialsProvider?.getCachedCredentials()?.expiration?.epochMilliseconds - ?: throw Exception("Failed to get credentials") - - return Instant.now().epochMilliseconds < expirationTimeMillis + fun getGeoRoutesClientConfig() : GeoRoutesClient.Config.Builder.() -> Unit { + val region = this.region?.regionName ?: throw Exception("No valid region provided") + val apiKey = this.apiKey + val credentialsProvider = this.credentialsProvider + return { + this.region = region + this.credentialsProvider = credentialsProvider + if (apiKey != null) { + this.interceptors = mutableListOf(ApiKeyInterceptor(apiKey)) + } + } } /** - * Retrieves the Cognito credentials. - * @return The Credentials instance. - * @throws Exception If the Cognito provider is not initialized. + * Retrieves the method type ("cognito", "custom", "apiKey") + * This should only be needed by the AwsSignerInterceptor. + * @return The method type. */ - fun getCredentialsProvider(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials { - return when (securePreferences.get(METHOD)) { - "apiKey" -> emptyCredentials ?: throw Exception("API key empty credentials not initialized") - "custom" -> customCredentials ?: throw Exception("Custom credentials not initialized") - else -> cognitoCredentialsProvider?.getCachedCredentials() ?: throw Exception("Cognito credentials not initialized") - } + fun getMethod() : String { + return method } /** - * Retrieves the API key credentials provider. - * @return The ApiKeyCredentialsProvider instance. - * @throws Exception If the API key provider is not initialized. + * Retrieves the API Key if one was provided. + * This should only be needed by the AwsSignerInterceptor. + * @return The API Key. */ - fun getApiKeyProvider(): ApiKeyCredentialsProvider { - if (apiKeyProvider === null) throw Exception("Api key provider not initialized") - return apiKeyProvider!! + fun getApiKey(): String? { + return apiKey } /** - * Refreshes the Cognito credentials. - * @throws Exception If the Cognito provider is not initialized. + * Returns the CredentialsProvider. + * This can either be a CognitoCredentialsProvider, a custom CredentialsProvider, or an empty StaticCredentialsProvider. + * @return The CredentialsProvider. */ - suspend fun refresh() { - val region = securePreferences.get(REGION) ?: throw Exception("No credentials found") - val method = securePreferences.get(METHOD) ?: throw Exception("No method found") - - when (method) { - "apiKey" -> return - "custom" -> { - customCredentials?.let { - credentialsProvider?.let { provider -> setCustomCredentials(provider, region) } - return - } - } - else -> { - cognitoCredentialsProvider?.let { - locationClient = null - it.clearCredentials() - verifyAndRefreshCredentials() - } ?: throw Exception("Refresh is only supported for Cognito credentials. Make sure to use the cognito constructor.") - } - } + fun getCredentialsProvider(): CredentialsProvider { + return credentialsProvider } /** - * Clears the Cognito credentials. - * @throws Exception If the Cognito provider is not initialized. + * Retrieves the AWS credentials. + * @return The Credentials instance containing the accessKeyId, secretAccessKey, and sessionToken + * @throws Exception If the credentials 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.") - cognitoCredentialsProvider?.clearCredentials() + suspend fun getCredentials(): Credentials { + return credentialsProvider.resolve() } -} \ No newline at end of file +} diff --git a/library/src/main/java/software/amazon/location/auth/utils/AwsAuthorizationHeaderHelper.kt b/library/src/main/java/software/amazon/location/auth/utils/AwsAuthorizationHeaderHelper.kt index 5737d06..805b94a 100644 --- a/library/src/main/java/software/amazon/location/auth/utils/AwsAuthorizationHeaderHelper.kt +++ b/library/src/main/java/software/amazon/location/auth/utils/AwsAuthorizationHeaderHelper.kt @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth.utils import okhttp3.Headers diff --git a/library/src/main/java/software/amazon/location/auth/utils/AwsRegions.kt b/library/src/main/java/software/amazon/location/auth/utils/AwsRegions.kt index 0a47193..883b19e 100644 --- a/library/src/main/java/software/amazon/location/auth/utils/AwsRegions.kt +++ b/library/src/main/java/software/amazon/location/auth/utils/AwsRegions.kt @@ -1,5 +1,7 @@ -package software.amazon.location.auth.utils +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.location.auth.utils enum class AwsRegions(val regionName: String) { GovCloud("us-gov-west-1"), 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 2ad4a83..43c5b07 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 @@ -1,5 +1,7 @@ -package software.amazon.location.auth.utils +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 +package software.amazon.location.auth.utils object Constants { const val SIGNING_ALGORITHM = "AWS4-HMAC-SHA256" @@ -9,15 +11,12 @@ object Constants { const val HEADER_X_AMZ_SECURITY_TOKEN = "x-amz-security-token" const val HEADER_X_AMZ_CONTENT_SHA256= "x-amz-content-sha256" const val HEADER_AUTHORIZATION= "authorization" - const val IDENTITY_ID= "identityId" const val ACCESS_KEY_ID= "accessKeyId" const val SECRET_KEY= "secretKey" const val SESSION_TOKEN= "sessionToken" const val EXPIRATION= "expiration" const val REGION= "region" const val METHOD= "method" - const val IDENTITY_POOL_ID= "identityPoolId" const val DEFAULT_ENCODING = "UTF-8" - const val API_KEY = "apiKey" const val QUERY_PARAM_KEY = "key" } \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/utils/CryptoUtils.kt b/library/src/main/java/software/amazon/location/auth/utils/CryptoUtils.kt index 4046d74..4b89897 100644 --- a/library/src/main/java/software/amazon/location/auth/utils/CryptoUtils.kt +++ b/library/src/main/java/software/amazon/location/auth/utils/CryptoUtils.kt @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth.utils import java.io.UnsupportedEncodingException 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 4e19cc7..0000000 --- a/library/src/test/java/software/amazon/location/auth/ApiKeyCredentialsProviderTest.kt +++ /dev/null @@ -1,81 +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 -import software.amazon.location.auth.utils.Constants.API_KEY_TEST -import software.amazon.location.auth.utils.Constants.REGION - -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().remove(any()) } just runs - } - - @Test - fun `constructor with apiKey saves credentials`() { - every { anyConstructed().put(API_KEY_TEST, TEST_API_KEY) } just runs - - ApiKeyCredentialsProvider(context, TEST_API_KEY) - - verify { anyConstructed().put(API_KEY_TEST, TEST_API_KEY) } - } - - @Test - fun `constructor without apiKey throws when no credentials found`() { - every { anyConstructed().get(API_KEY_TEST) } returns null - - assertFailsWith { - ApiKeyCredentialsProvider(context) - } - } - - @Test - fun `getCachedCredentials returns apiKey when found`() { - val apiKey = "testApiKey" - every { anyConstructed().get(API_KEY_TEST) } 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(API_KEY_TEST) } throws Exception("Not initialized") - - assertFailsWith { - provider.getCachedCredentials() - } - } - - @Test - fun `clearCredentials clears the stored credentials`() { - val provider = ApiKeyCredentialsProvider(context, "testApiKey") - provider.clearCredentials() - - verify { anyConstructed().remove(any()) } - } -} \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/ApiKeyInterceptorTest.kt b/library/src/test/java/software/amazon/location/auth/ApiKeyInterceptorTest.kt index f33bb14..d89a4c0 100644 --- a/library/src/test/java/software/amazon/location/auth/ApiKeyInterceptorTest.kt +++ b/library/src/test/java/software/amazon/location/auth/ApiKeyInterceptorTest.kt @@ -1,3 +1,6 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth import aws.smithy.kotlin.runtime.client.ProtocolRequestInterceptorContext @@ -23,7 +26,7 @@ class ApiKeyInterceptorTest { } @Test - fun `test URL is modified when key is missing`() = runBlocking { + fun `test URL is modified when key is missing from the URL`() = runBlocking { val mockContext = mockk>(relaxed = true) val httpRequest = HttpRequest( method= HttpMethod.POST, @@ -38,7 +41,7 @@ class ApiKeyInterceptorTest { } @Test - fun `test URL is not modified when key is present`() = runBlocking { + fun `test URL is not modified when key is present in the URL`() = runBlocking { val mockContext = mockk>(relaxed = true) val httpRequest = HttpRequest( method= HttpMethod.POST, 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 f81aae3..9e893c6 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -1,64 +1,27 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context +import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider 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 import io.mockk.mockkConstructor -import io.mockk.runs import org.junit.Before import org.junit.Test import kotlin.test.assertNotNull 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_API_KEY import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID 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) - - 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(Constants.API_KEY) } returns "test" - 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 - coEvery { anyConstructed().initializeLocationClient(any()) } just runs - coEvery { anyConstructed().initializeLocationClient() } just runs - coEvery { anyConstructed().isCredentialsValid() } returns true - every { anyConstructed().initPreference(context) } returns encryptedSharedPreferences - every { anyConstructed().initEncryptedSharedPreferences() } just runs - } - @Test fun `authenticateWithCognitoIdentityPool with identityPoolId creates LocationCredentialsProvider`() { runBlocking { - val provider = authHelper.authenticateWithCognitoIdentityPool(TEST_IDENTITY_POOL_ID) + val provider = AuthHelper.withCognitoIdentityPool(TEST_IDENTITY_POOL_ID) assertNotNull(provider) } } @@ -67,30 +30,20 @@ class AuthHelperTest { fun `authenticateWithCognitoIdentityPool with identityPoolId and string region creates LocationCredentialsProvider`() { runBlocking { val provider = - authHelper.authenticateWithCognitoIdentityPool(TEST_IDENTITY_POOL_ID, "us-east-1") - assertNotNull(provider) - } - } - - @Test - fun `authenticateWithCognitoIdentityPool with identityPoolId and Regions enum creates LocationCredentialsProvider`() { - runBlocking { - val provider = - authHelper.authenticateWithCognitoIdentityPool( - TEST_IDENTITY_POOL_ID, - AwsRegions.US_EAST_1 - ) + AuthHelper.withCognitoIdentityPool(TEST_IDENTITY_POOL_ID, "us-east-1") assertNotNull(provider) } } @Test fun `authenticateWithCredentialsProvider with identityPoolId`() { + val credentialsProvider = mockk() + runBlocking { val provider = - authHelper.authenticateWithCredentialsProvider( - "us-east-1", - credentialsProvider + AuthHelper.withCredentialsProvider( + credentialsProvider, + "us-east-1" ) assertNotNull(provider) } @@ -99,7 +52,7 @@ class AuthHelperTest { @Test fun `authenticateWithApiKey creates LocationCredentialsProvider`() { runBlocking { - val provider = authHelper.authenticateWithApiKey(TEST_API_KEY,"us-east-1") + val provider = AuthHelper.withApiKey(TEST_API_KEY,"us-east-1") assertNotNull(provider) } } diff --git a/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt b/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt index cf7b4d9..b9b7fc9 100644 --- a/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt @@ -1,13 +1,13 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context -import aws.sdk.kotlin.services.cognitoidentity.model.Credentials +import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials import io.mockk.coEvery 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 java.net.URL import java.nio.charset.StandardCharsets @@ -22,34 +22,23 @@ import org.junit.Test import software.amazon.location.auth.utils.Constants.HEADER_AUTHORIZATION import software.amazon.location.auth.utils.Constants.HEADER_HOST import software.amazon.location.auth.utils.Constants.HEADER_X_AMZ_SECURITY_TOKEN -import software.amazon.location.auth.utils.Constants.METHOD -import software.amazon.location.auth.utils.Constants.REGION import software.amazon.location.auth.utils.Constants.TEST_REGION import software.amazon.location.auth.utils.Constants.TEST_URL import software.amazon.location.auth.utils.Constants.TEST_URL1 class AwsSignerInterceptorTest { - private lateinit var context: Context private lateinit var interceptor: AwsSignerInterceptor private lateinit var mockCredentialsProvider: LocationCredentialsProvider - private lateinit var encryptedSharedPreferences: EncryptedSharedPreferences @Before fun setUp() { - context = mockk(relaxed = true) - encryptedSharedPreferences = mockk(relaxed = true) mockCredentialsProvider = mockk(relaxed = true) - mockkConstructor(EncryptedSharedPreferences::class) mockkConstructor(AwsSignerInterceptor::class) interceptor = AwsSignerInterceptor( - context, "execute-api", TEST_REGION, mockCredentialsProvider ) - every { anyConstructed().initEncryptedSharedPreferences() } just runs - every { anyConstructed().get(METHOD) } returns "cognito" - every { anyConstructed().initPreference(context) } returns encryptedSharedPreferences } @Test @@ -77,14 +66,13 @@ class AwsSignerInterceptorTest { val credentials = mockk { every { accessKeyId } returns "testAccessKeyId" - every { secretKey } returns "testSecretKey" + every { secretAccessKey } returns "testSecretKey" every { sessionToken } returns "testSessionToken" } every { chain.request() } returns originalRequest every { chain.proceed(any()) } returns mockk(relaxed = true) - coEvery { mockCredentialsProvider.isCredentialsValid() } returns true - coEvery { mockCredentialsProvider.getCredentialsProvider() } returns credentials + coEvery { mockCredentialsProvider.getCredentials() } returns credentials val response = runBlocking { interceptor.intercept(chain) } diff --git a/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt index f2ce3fd..e050310 100644 --- a/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt @@ -1,155 +1,139 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context import aws.sdk.kotlin.services.cognitoidentity.model.Credentials -import aws.smithy.kotlin.runtime.http.auth.AnonymousIdentity.expiration import aws.smithy.kotlin.runtime.time.Instant -import io.mockk.every -import io.mockk.just -import io.mockk.mockk +import aws.smithy.kotlin.runtime.time.epochMilliseconds +import aws.smithy.kotlin.runtime.time.fromEpochMilliseconds +import io.mockk.coEvery import io.mockk.mockkConstructor -import io.mockk.runs -import io.mockk.verify +import junit.framework.TestCase.assertNotNull import junit.framework.TestCase.assertEquals -import junit.framework.TestCase.assertNull -import kotlin.test.assertFailsWith -import kotlin.test.assertNotNull -import org.junit.Before +import kotlinx.coroutines.runBlocking import org.junit.Test -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.SECRET_KEY -import software.amazon.location.auth.utils.Constants.SESSION_TOKEN class CognitoCredentialsProviderTest { + @Test + fun `Constructs successfully with identity pool and region`() { + val provider = CognitoCredentialsProvider("identityPool", "us-east-1") + assertNotNull(provider) + } - private lateinit var context: Context + @Test + fun `Calling resolve for the first time refreshes credentials successfully and returns them`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) + + // Create mock CognitoIdentityClient + mockkConstructor(CognitoCredentialsProvider::class) + coEvery { + anyConstructed().fetchCognitoCredentials() + } returns Credentials.invoke { + expiration = expirationTime + secretKey = "testSecretKey" + accessKeyId = "testAccessKeyId" + sessionToken = "testSessionToken" + } - @Before - fun setUp() { - context = mockk(relaxed = true) + val provider = CognitoCredentialsProvider("identityPool", "us-east-1") - mockkConstructor(EncryptedSharedPreferences::class) - mockkConstructor(EncryptedSharedPreferences::class) + runBlocking { - every { anyConstructed().initEncryptedSharedPreferences() } just runs - every { anyConstructed().put(any(), any()) } just runs - every { anyConstructed().get(any()) } returns null - every { anyConstructed().clear() } just runs - every { anyConstructed().remove(any()) } just runs - } + // Call resolve + val credentials = provider.resolve() + // Verify the returned credentials match what we expected + assertEquals("testAccessKeyId", credentials.accessKeyId) + assertEquals("testSessionToken", credentials.sessionToken) + assertEquals(expirationTime, credentials.expiration) + } + } @Test - fun `constructor with credentials saves credentials`() { - val credentials = Credentials.invoke { - accessKeyId = "accessKeyId" - expiration = Instant.now() - secretKey = "secretKey" - sessionToken = "sessionToken" + fun `If credentials aren't expired they are returned successfully from the cache`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) + val expirationTime2 = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 20000) + + // Create mock CognitoIdentityClient + mockkConstructor(CognitoCredentialsProvider::class) + coEvery { + anyConstructed().fetchCognitoCredentials() + } returns Credentials.invoke { + expiration = expirationTime + secretKey = "testSecretKey" + accessKeyId = "testAccessKeyId" + sessionToken = "testSessionToken" + } andThen Credentials.invoke { + expiration = expirationTime2 + secretKey = "testSecretKey2" + accessKeyId = "testAccessKeyId2" + sessionToken = "testSessionToken2" } - every { - anyConstructed().put( - ACCESS_KEY_ID, - credentials.accessKeyId!! - ) - } just runs - every { - anyConstructed().put( - SECRET_KEY, - credentials.secretKey!! - ) - } just runs - every { - anyConstructed().put( - SESSION_TOKEN, - credentials.sessionToken!! - ) - } just runs - every { - anyConstructed().put( - EXPIRATION, - credentials.expiration.toString() - ) - } just runs - - CognitoCredentialsProvider(context,"", credentials) - - verify { - anyConstructed().put( - ACCESS_KEY_ID, - credentials.accessKeyId!! - ) - anyConstructed().put(SECRET_KEY, credentials.secretKey!!) - anyConstructed().put( - SESSION_TOKEN, - credentials.sessionToken!! - ) - } - } + val provider = CognitoCredentialsProvider("identityPool", "us-east-1") - @Test - fun `constructor without credentials throws when no credentials found`() { - every { anyConstructed().get(ACCESS_KEY_ID) } returns null + runBlocking { + + // Call resolve + var credentials = provider.resolve() - assertFailsWith { - CognitoCredentialsProvider(context) + // Verify the returned credentials match what we expected + assertEquals("testAccessKeyId", credentials.accessKeyId) + assertEquals("testSessionToken", credentials.sessionToken) + assertEquals(expirationTime, credentials.expiration) + + credentials = provider.resolve() + + // Verify the returned credentials match what we expected + assertEquals("testAccessKeyId", credentials.accessKeyId) + assertEquals("testSessionToken", credentials.sessionToken) + assertEquals(expirationTime, credentials.expiration) } } - @Test - fun `getCachedCredentials returns null when not all credentials are found`() { - every { anyConstructed().get(ACCESS_KEY_ID) } returns "accessKeyId" - every { anyConstructed().get(SECRET_KEY) } returns null - every { anyConstructed().get(SESSION_TOKEN) } returns "sessionToken" - every { anyConstructed().get(EXPIRATION) } returns "1234567890.0" - - val provider = try { - CognitoCredentialsProvider(context) - } catch (e: Exception) { - null + fun `If credentials are expired they will trigger a refresh`() { + val expirationTime = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds - 10000) + val expirationTime2 = + Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 20000) + + // Create mock CognitoIdentityClient + mockkConstructor(CognitoCredentialsProvider::class) + coEvery { + anyConstructed().fetchCognitoCredentials() + } returns Credentials.invoke { + expiration = expirationTime + secretKey = "testSecretKey" + accessKeyId = "testAccessKeyId" + sessionToken = "testSessionToken" + } andThen Credentials.invoke { + expiration = expirationTime2 + secretKey = "testSecretKey2" + accessKeyId = "testAccessKeyId2" + sessionToken = "testSessionToken2" } - val cachedCredentials = provider?.getCachedCredentials() + val provider = CognitoCredentialsProvider("identityPool", "us-east-1") - assertNull(cachedCredentials) - } + runBlocking { - @Test - fun `getCachedCredentials throws when not initialized`() { - val provider = CognitoCredentialsProvider( - context, - "", - Credentials.invoke { - accessKeyId = "accessKeyId" - expiration = Instant.now() - secretKey = "secretKey" - sessionToken = "sessionToken" - } - ) - - every { anyConstructed().get(ACCESS_KEY_ID) } throws Exception("Not initialized") - - assertFailsWith { - provider.getCachedCredentials() - } - } + // Call resolve + var credentials = provider.resolve() - @Test - fun `clearCredentials clears the stored credentials`() { - val provider = CognitoCredentialsProvider( - context, - "", - Credentials.invoke { - accessKeyId = "accessKeyId" - expiration = Instant.now() - secretKey = "secretKey" - sessionToken = "sessionToken" - } - ) - - provider.clearCredentials() - - verify { anyConstructed().remove(any()) } + // Verify the returned credentials match what we expected + assertEquals("testAccessKeyId", credentials.accessKeyId) + assertEquals("testSessionToken", credentials.sessionToken) + assertEquals(expirationTime, credentials.expiration) + + credentials = provider.resolve() + + // Verify the returned credentials match what we expected + assertEquals("testAccessKeyId2", credentials.accessKeyId) + assertEquals("testSessionToken2", credentials.sessionToken) + assertEquals(expirationTime2, credentials.expiration) + } } } diff --git a/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt b/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt deleted file mode 100644 index b68c275..0000000 --- a/library/src/test/java/software/amazon/location/auth/CustomCredentialsProviderTest.kt +++ /dev/null @@ -1,87 +0,0 @@ -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 -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.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 -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`() { - 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.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 1766829..6afbf78 100644 --- a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt @@ -1,299 +1,129 @@ +// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +// SPDX-License-Identifier: Apache-2.0 + package software.amazon.location.auth -import android.content.Context -import aws.sdk.kotlin.services.cognitoidentity.CognitoIdentityClient +import aws.sdk.kotlin.runtime.auth.credentials.StaticCredentialsProvider import aws.sdk.kotlin.services.cognitoidentity.model.Credentials -import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityRequest -import aws.sdk.kotlin.services.cognitoidentity.model.GetCredentialsForIdentityResponse -import aws.sdk.kotlin.services.cognitoidentity.model.GetIdRequest -import aws.sdk.kotlin.services.cognitoidentity.model.GetIdResponse +import aws.sdk.kotlin.services.geomaps.GeoMapsClient +import aws.sdk.kotlin.services.geoplaces.GeoPlacesClient +import aws.sdk.kotlin.services.georoutes.GeoRoutesClient 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.coEvery 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 junit.framework.TestCase.assertFalse +import junit.framework.TestCase.assertNull import junit.framework.TestCase.assertNotNull -import kotlin.test.assertFailsWith -import kotlin.test.assertTrue +import junit.framework.TestCase.assertEquals 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.API_KEY_TEST -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 -import software.amazon.location.auth.utils.Constants.TEST_API_KEY import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID 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 lateinit var expirationTime: Instant @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().generateLocationClient( - "us-east-1", - any(), - 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 cached credentials for Cognito initializes correctly`() { - every { anyConstructed().get(METHOD) } returns "cognito" - 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" - every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID - val provider = LocationCredentialsProvider(context) - assertNotNull(provider) + expirationTime = Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future } @Test - fun `getCredentialsProvider returns cognito provider successfully`() { - val expirationTime = - Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future + fun `Constructs successfully with identity pool and region`() { val mockCredentials = Credentials.invoke { expiration = expirationTime - secretKey = "test" - accessKeyId = "test" - sessionToken = "test" + secretKey = "testSecretKey" + accessKeyId = "testAccessKeyId" + sessionToken = "testSessionToken" } - 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.verifyAndRefreshCredentials() - assertNotNull(provider.getCredentialsProvider()) - } - } + mockkConstructor(CognitoCredentialsProvider::class) + coEvery { + anyConstructed().fetchCognitoCredentials() + } returns mockCredentials - @Test - fun `initializeLocationClient_with_pool_id`() { - 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()) - } - } + val provider = LocationCredentialsProvider(TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) + assertNotNull(provider) + assertEquals(provider.getMethod(), "cognito") + assertNull(provider.getApiKey()) + assertNotNull(provider.getCredentialsProvider()) - @Test - fun `initializeLocationClient_with_api_key`() { - every { anyConstructed().get(METHOD) } returns "apiKey" - every { anyConstructed().get(API_KEY_TEST) } returns TEST_API_KEY - val provider = - LocationCredentialsProvider(context, AwsRegions.US_EAST_1, TEST_API_KEY) runBlocking { - provider.initializeLocationClient() - assertNotNull(provider.getLocationClient()) - assertNotNull(provider.getApiKeyProvider()) + val credentials = provider.getCredentials() + assertEquals(credentials.accessKeyId, mockCredentials.accessKeyId) + assertEquals(credentials.secretAccessKey, mockCredentials.secretKey) + assertEquals(credentials.sessionToken, mockCredentials.sessionToken) } } + @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 { + fun `Constructs successfully with custom CredentialsProvider`() { + val credentialsProvider = StaticCredentialsProvider( + aws.smithy.kotlin.runtime.auth.awscredentials.Credentials.invoke( + accessKeyId = "testAccessKey", + secretAccessKey = "testSecretAccessKey", + sessionToken = "testSessionToken", expiration = expirationTime - secretKey = "test" - accessKeyId = "test" - 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" - every { anyConstructed().get(SESSION_TOKEN) } returns "test" - every { anyConstructed().get(EXPIRATION) } returns "11111" - every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID - val provider = - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - runBlocking { - provider.verifyAndRefreshCredentials() - provider.refresh() - val result = provider.isCredentialsValid() - assertTrue(result) - } - } + )) + val provider = LocationCredentialsProvider(credentialsProvider, AwsRegions.US_EAST_1) + assertNotNull(provider) + assertEquals(provider.getMethod(), "custom") + assertNull(provider.getApiKey()) + assertNotNull(provider.getCredentialsProvider()) - @Test - 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 - } - every { anyConstructed().getCachedCredentials() } returns mockCredentials - every { anyConstructed().get(METHOD) } returns "cognito" - 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" - every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID - val provider = - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) runBlocking { - provider.verifyAndRefreshCredentials() - provider.refresh() - val result = provider.isCredentialsValid() - assertFalse(result) + val credentials = provider.getCredentials() + assertEquals(credentials.accessKeyId, credentialsProvider.credentials.accessKeyId) + assertEquals(credentials.secretAccessKey, credentialsProvider.credentials.secretAccessKey) + assertEquals(credentials.sessionToken, credentialsProvider.credentials.sessionToken) } } @Test - fun `clear successfully clears cognito credentials`() { - every { anyConstructed().get(METHOD) } returns "cognito" - 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" - every { anyConstructed().get(IDENTITY_POOL_ID) } returns TEST_IDENTITY_POOL_ID - val provider = - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - runBlocking { - provider.verifyAndRefreshCredentials() - provider.clear() - } - } + fun `Constructs successfully with ApiKey`() { + val apiKey = "TestApiKey" + val provider = LocationCredentialsProvider(AwsRegions.US_EAST_1, apiKey) + assertNotNull(provider) + assertEquals(provider.getMethod(), "apiKey") + assertEquals(provider.getApiKey(), apiKey) + assertNotNull(provider.getCredentialsProvider()) - @Test - fun `check credentials`() { - 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) runBlocking { - provider.verifyAndRefreshCredentials() + val credentials = provider.getCredentials() + assertEquals(credentials.accessKeyId, "") + assertEquals(credentials.secretAccessKey, "") + assertNull(credentials.sessionToken) } } @Test - fun `get Location Client`() { - every { anyConstructed().get(METHOD) } returns "cognito" - 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" - 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" - } - - 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 - } + fun `getLocationClientConfig can be used to successfully construct a LocationClient`() { + val apiKey = "TestApiKey" + val provider = LocationCredentialsProvider(AwsRegions.US_EAST_1, apiKey) + assertNotNull(provider.getLocationClientConfig()) + } - coEvery { cognitoIdentityClient.getCredentialsForIdentity(any()) } returns - GetCredentialsForIdentityResponse { - this.credentials = credentials - } - runBlocking { - provider.verifyAndRefreshCredentials() - val locationClient = provider.getLocationClient() - assertNotNull(locationClient) - } + fun `getGeoMapsClientConfig can be used to successfully construct a GeoMapsClient`() { + val apiKey = "TestApiKey" + val provider = LocationCredentialsProvider(AwsRegions.US_EAST_1, apiKey) + assertNotNull(provider.getGeoMapsClientConfig()) } - @Test - fun `constructor with cached cognito credentials throws exception on missing data`() { - every { anyConstructed().get(METHOD) } returns "cognito" - every { anyConstructed().get(IDENTITY_POOL_ID) } returns null // Simulate missing data - assertFailsWith { LocationCredentialsProvider(context) } + fun `getGeoPlacesClientConfig can be used to successfully construct a GeoPlacesClient`() { + val apiKey = "TestApiKey" + val provider = LocationCredentialsProvider(AwsRegions.US_EAST_1, apiKey) + assertNotNull(provider.getGeoPlacesClientConfig()) } - @Test - fun `verify SecurePreferences interactions for cognito initialization`() { - LocationCredentialsProvider(context, TEST_IDENTITY_POOL_ID, AwsRegions.US_EAST_1) - verify(exactly = 1) { anyConstructed().put(METHOD, "cognito") } - verify(exactly = 1) { - anyConstructed().put( - IDENTITY_POOL_ID, - TEST_IDENTITY_POOL_ID, - ) - } - verify(exactly = 1) { - anyConstructed().put( - REGION, - AwsRegions.US_EAST_1.regionName, - ) - } + fun `getGeoRoutesClientConfig can be used to successfully construct a GeoRoutesClient`() { + val apiKey = "TestApiKey" + val provider = LocationCredentialsProvider(AwsRegions.US_EAST_1, apiKey) + assertNotNull(provider.getGeoRoutesClientConfig()) } + } 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 a583163..508a635 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 @@ -1,14 +1,6 @@ package software.amazon.location.auth.utils object Constants { - const val API_KEY: String = "api_key_test" - const val ACCESS_KEY_ID = "accessKeyId" - const val SECRET_KEY = "secretKey" - const val SESSION_TOKEN = "sessionToken" - const val EXPIRATION = "expiration" - const val METHOD = "method" - const val IDENTITY_POOL_ID = "identityPoolId" - const val REGION = "region" const val HEADER_HOST = "Host" const val HEADER_X_AMZ_SECURITY_TOKEN = "x-amz-security-token" const val HEADER_AUTHORIZATION = "Authorization" @@ -17,5 +9,4 @@ object Constants { const val TEST_URL1 = "https://example.com" const val TEST_IDENTITY_POOL_ID = "us-east-1:dummyIdentityPoolId" const val TEST_API_KEY = "dummyApiKey" - const val API_KEY_TEST = "apiKey" } \ No newline at end of file