diff --git a/library/build.gradle.kts b/library/build.gradle.kts index ad6a529..4136a4b 100644 --- a/library/build.gradle.kts +++ b/library/build.gradle.kts @@ -79,8 +79,8 @@ dependencies { implementation("com.squareup.okhttp3:okhttp:4.12.0") implementation("com.google.code.gson:gson:2.10.1") implementation("androidx.security:security-crypto:1.1.0-alpha06") - implementation("com.squareup.retrofit2:retrofit:2.9.0") - implementation("com.squareup.retrofit2:converter-gson:2.9.0") + implementation("aws.sdk.kotlin:cognitoidentity:1.2.21") + implementation("aws.sdk.kotlin:location:1.2.21") testImplementation("junit:junit:4.13.2") testImplementation("org.jetbrains.kotlin:kotlin-test:1.9.20") diff --git a/library/src/main/java/software/amazon/location/auth/AmazonLocationClient.kt b/library/src/main/java/software/amazon/location/auth/AmazonLocationClient.kt new file mode 100644 index 0000000..647799d --- /dev/null +++ b/library/src/main/java/software/amazon/location/auth/AmazonLocationClient.kt @@ -0,0 +1,44 @@ +package software.amazon.location.auth + + +import aws.sdk.kotlin.services.location.LocationClient +import aws.sdk.kotlin.services.location.model.SearchPlaceIndexForPositionRequest +import aws.sdk.kotlin.services.location.model.SearchPlaceIndexForPositionResponse + +/** + * Provides methods to interact with the Amazon Location service. + * + * @property locationClient An instance of LocationClient used for making requests to the Amazon Location service. + */ +class AmazonLocationClient( + private val locationClient: LocationClient +) { + + /** + * Reverse geocodes a location specified by longitude and latitude coordinates. + * + * @param placeIndexName The name of the place index resource to use for the reverse geocoding request. + * @param longitude The longitude of the location to reverse geocode. + * @param latitude The latitude of the location to reverse geocode. + * @param mLanguage The language to use for the reverse geocoding results. + * @param mMaxResults The maximum number of results to return. + * @return A response containing the reverse geocoding results. + */ + suspend fun reverseGeocode( + placeIndexName: String, + longitude: Double, + latitude: Double, + mLanguage: String, + mMaxResults: Int + ): SearchPlaceIndexForPositionResponse { + val request = SearchPlaceIndexForPositionRequest { + indexName = placeIndexName + position = listOf(longitude, latitude) + maxResults = mMaxResults + language = mLanguage + } + + val response = locationClient.searchPlaceIndexForPosition(request) + return response + } +} 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 30b30bc..7527880 100644 --- a/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt +++ b/library/src/main/java/software/amazon/location/auth/AwsSignerInterceptor.kt @@ -31,17 +31,17 @@ class AwsSignerInterceptor( override fun intercept(chain: Interceptor.Chain): Response { val originalRequest = chain.request() - if (!originalRequest.url.host.contains("amazonaws.com")) { + if (!originalRequest.url.host.contains("amazonaws.com") || credentialsProvider?.getCredentialsProvider() == null) { return chain.proceed(originalRequest) } runBlocking { - if (credentialsProvider != null && !credentialsProvider.isCredentialsValid(credentialsProvider.getCredentialsProvider())) { + if (!credentialsProvider.isCredentialsValid(credentialsProvider.getCredentialsProvider()!!)) { credentialsProvider.checkCredentials() } } - val accessKeyId = credentialsProvider?.getCredentialsProvider()?.accessKeyId - val secretKey = credentialsProvider?.getCredentialsProvider()?.secretKey - val sessionToken = credentialsProvider?.getCredentialsProvider()?.sessionToken + val accessKeyId = credentialsProvider.getCredentialsProvider()?.accessKeyId + val secretKey = credentialsProvider.getCredentialsProvider()?.secretKey + val sessionToken = credentialsProvider.getCredentialsProvider()?.sessionToken if (!accessKeyId.isNullOrEmpty() && !secretKey.isNullOrEmpty() && !sessionToken.isNullOrEmpty() && region.isNotEmpty()) { val dateMilli = Date().time val host = extractHostHeader(originalRequest.url.toString()) 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 2824f92..f53e2b1 100644 --- a/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/CognitoCredentialsProvider.kt @@ -1,7 +1,10 @@ package software.amazon.location.auth import android.content.Context -import software.amazon.location.auth.data.model.response.Credentials +import aws.sdk.kotlin.services.cognitoidentity.model.Credentials +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.SECRET_KEY @@ -50,10 +53,11 @@ class CognitoCredentialsProvider { */ private fun saveCredentials(credentials: Credentials) { if (securePreferences === null) throw Exception("Not initialized") - securePreferences?.put(ACCESS_KEY_ID, credentials.accessKeyId) - securePreferences?.put(SECRET_KEY, credentials.secretKey) - securePreferences?.put(SESSION_TOKEN, credentials.sessionToken) - securePreferences?.put(EXPIRATION, credentials.expiration.toString()) + 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()) } + } /** @@ -62,12 +66,17 @@ class CognitoCredentialsProvider { */ fun getCachedCredentials(): Credentials? { if (securePreferences === null) return null - val accessKeyId = securePreferences?.get(ACCESS_KEY_ID) - val secretKey = securePreferences?.get(SECRET_KEY) - val sessionToken = securePreferences?.get(SESSION_TOKEN) - val expiration = securePreferences?.get(EXPIRATION) - if (accessKeyId.isNullOrEmpty() || secretKey.isNullOrEmpty() || sessionToken.isNullOrEmpty() || expiration.isNullOrEmpty()) return null - return Credentials(accessKeyId, expiration.toDouble(), secretKey, sessionToken) + 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()) + } } /** diff --git a/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt b/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt index 775e063..9435865 100644 --- a/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt +++ b/library/src/main/java/software/amazon/location/auth/EncryptedSharedPreferences.kt @@ -87,4 +87,15 @@ class EncryptedSharedPreferences(private val context: Context, private val prefe 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 272d318..1056305 100644 --- a/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt +++ b/library/src/main/java/software/amazon/location/auth/LocationCredentialsProvider.kt @@ -1,18 +1,20 @@ package software.amazon.location.auth import android.content.Context -import java.util.Date -import software.amazon.location.auth.data.model.response.Credentials -import software.amazon.location.auth.data.network.AwsRetrofitClient +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.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider +import aws.smithy.kotlin.runtime.time.Instant +import aws.smithy.kotlin.runtime.time.epochMilliseconds import software.amazon.location.auth.utils.AwsRegions -import software.amazon.location.auth.utils.AwsRegions.Companion.DEFAULT_REGION -import software.amazon.location.auth.utils.CognitoCredentialsClient import software.amazon.location.auth.utils.Constants.API_KEY -import software.amazon.location.auth.utils.Constants.BASE_URL 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.SERVICE_NAME const val PREFS_NAME = "software.amazon.location.auth" @@ -24,6 +26,7 @@ class LocationCredentialsProvider { private var cognitoCredentialsProvider: CognitoCredentialsProvider? = null private var apiKeyProvider: ApiKeyCredentialsProvider? = null private var securePreferences: EncryptedSharedPreferences + private var locationClient: LocationClient? = null /** * Initializes with Cognito credentials. @@ -86,13 +89,11 @@ class LocationCredentialsProvider { } /** - * check AWS credentials. + * Checks AWS credentials availability and validity. * - * This function retrieves the identity pool ID and region from a secure preferences - * - * The function first attempts to initialize the CognitoCredentialsProvider. If it fails - * or if there are no cached credentials or if the cached credentials are invalid, it - * generates new credentials. + * 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. */ @@ -112,39 +113,91 @@ class LocationCredentialsProvider { val credentials = cognitoCredentialsProvider?.getCachedCredentials() credentials?.let { if (!isCredentialsValid(it)) { - AwsRetrofitClient.clearApiService() generateCredentials(region, identityPoolId) - } else { - initAwsRetrofitClient() } } } } - private fun initAwsRetrofitClient() { - val region = securePreferences.get(REGION) ?: DEFAULT_REGION.regionName - AwsRetrofitClient.init(getUrl(region), SERVICE_NAME, region, this) + + /** + * 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. + */ + fun getLocationClient(): LocationClient? { + val identityPoolId = securePreferences.get(IDENTITY_POOL_ID) + val region = securePreferences.get(REGION) + if (identityPoolId === null || region === null) throw Exception("No credentials found") + if (locationClient == null) { + val credentialsProvider = createCredentialsProvider() + locationClient = LocationClient { + this.region = region + this.credentialsProvider = credentialsProvider + } + } + return locationClient + } + + /** + * 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() == null || 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, + ) + ) } /** * Generates new AWS credentials using the specified region and identity pool ID. * - * This function uses CognitoCredentialsClient to fetch the identity ID and credentials, - * and then initializes the CognitoCredentialsProvider with the retrieved credentials. + * 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. */ private suspend fun generateCredentials(region: String, identityPoolId: String) { - val cognitoCredentialsHttpHelper = CognitoCredentialsClient(region) + val client = CognitoIdentityClient { this.region = region } try { - val identityId = cognitoCredentialsHttpHelper.getIdentityId(identityPoolId) + val getIdResponse = client.getId(GetIdRequest { this.identityPoolId = identityPoolId }) + val identityId = + getIdResponse.identityId ?: throw Exception("Failed to get identity ID") if (identityId.isNotEmpty()) { - val credentials = cognitoCredentialsHttpHelper.getCredentials(identityId) - cognitoCredentialsProvider = - CognitoCredentialsProvider(context, credentials.credentials) - initAwsRetrofitClient() + val getCredentialsResponse = + client.getCredentialsForIdentity(GetCredentialsForIdentityRequest { + this.identityId = identityId + }) + + val credentials = getCredentialsResponse.credentials + ?: throw Exception("Failed to get credentials") + if (credentials.accessKeyId == null || credentials.secretKey == null || credentials.sessionToken == null) throw Exception( + "Credentials generation failed" + ) + cognitoCredentialsProvider = CognitoCredentialsProvider( + context, + credentials + ) + locationClient = null } } catch (e: Exception) { throw Exception("Credentials generation failed") @@ -154,16 +207,13 @@ class LocationCredentialsProvider { /** * Checks if the provided credentials are still valid. * - * This function compares the current date with the expiration date of the credentials. - * * @param credentials The AWS credentials to validate. * @return True if the credentials are valid (i.e., not expired), false otherwise. */ - fun isCredentialsValid(credentials: Credentials): Boolean { - val expirationTime = credentials.expiration.toLong() * 1000 - val expirationDate = Date(expirationTime) - val currentDate = Date() - return currentDate.before(expirationDate) + fun isCredentialsValid(credentials: aws.sdk.kotlin.services.cognitoidentity.model.Credentials): Boolean { + val currentTimeMillis = Instant.now().epochMilliseconds + val expirationTimeMillis = credentials.expiration?.epochMilliseconds ?: throw Exception("Failed to get credentials") + return currentTimeMillis < expirationTimeMillis } /** @@ -171,9 +221,9 @@ class LocationCredentialsProvider { * @return The Credentials instance. * @throws Exception If the Cognito provider is not initialized. */ - fun getCredentialsProvider(): Credentials { + fun getCredentialsProvider(): aws.sdk.kotlin.services.cognitoidentity.model.Credentials? { if (cognitoCredentialsProvider === null) throw Exception("Cognito credentials not initialized") - return cognitoCredentialsProvider?.getCachedCredentials()!! + return cognitoCredentialsProvider?.getCachedCredentials() } /** @@ -192,7 +242,7 @@ class LocationCredentialsProvider { */ suspend fun refresh() { if (cognitoCredentialsProvider === null) throw Exception("Refresh is only supported for Cognito credentials. Make sure to use the cognito constructor.") - AwsRetrofitClient.clearApiService() + locationClient = null cognitoCredentialsProvider?.clearCredentials() checkCredentials() } @@ -205,9 +255,4 @@ class LocationCredentialsProvider { if (cognitoCredentialsProvider === null) throw Exception("Clear is only supported for Cognito credentials. Make sure to use the cognito constructor.") cognitoCredentialsProvider?.clearCredentials() } - - private fun getUrl(region: String): String { - val urlBuilder = StringBuilder(BASE_URL.format(region)) - return urlBuilder.toString() - } } \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/request/GetCredentialRequest.kt b/library/src/main/java/software/amazon/location/auth/data/model/request/GetCredentialRequest.kt deleted file mode 100644 index 86f580f..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/request/GetCredentialRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.request - -import com.google.gson.annotations.SerializedName - -data class GetCredentialRequest( - @SerializedName("IdentityId") - val identityId: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/request/GetIdentityIdRequest.kt b/library/src/main/java/software/amazon/location/auth/data/model/request/GetIdentityIdRequest.kt deleted file mode 100644 index 5275262..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/request/GetIdentityIdRequest.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.request - -import com.google.gson.annotations.SerializedName - -data class GetIdentityIdRequest( - @SerializedName("IdentityPoolId") - val identityPoolId: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/request/ReverseGeocodeRequest.kt b/library/src/main/java/software/amazon/location/auth/data/model/request/ReverseGeocodeRequest.kt deleted file mode 100644 index 90a32ca..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/request/ReverseGeocodeRequest.kt +++ /dev/null @@ -1,12 +0,0 @@ -package software.amazon.location.auth.data.model.request - -import com.google.gson.annotations.SerializedName - -data class ReverseGeocodeRequest( - @SerializedName("Language") - val language: String, - @SerializedName("MaxResults") - val maxResults: Int, - @SerializedName("Position") - val position: List -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/Credentials.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/Credentials.kt deleted file mode 100644 index 7ac96ea..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/Credentials.kt +++ /dev/null @@ -1,10 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class Credentials( - @SerializedName("AccessKeyId") val accessKeyId: String, - @SerializedName("Expiration") val expiration: Double, - @SerializedName("SecretKey") val secretKey: String, - @SerializedName("SessionToken") val sessionToken: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/GetCredentialResponse.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/GetCredentialResponse.kt deleted file mode 100644 index 030db09..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/GetCredentialResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class GetCredentialResponse( - @SerializedName("Credentials") val credentials: Credentials, - @SerializedName("IdentityId") val identityId: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/GetIdentityIdResponse.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/GetIdentityIdResponse.kt deleted file mode 100644 index af990a1..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/GetIdentityIdResponse.kt +++ /dev/null @@ -1,7 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class GetIdentityIdResponse( - @SerializedName("IdentityId") val identityId: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeLabel.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeLabel.kt deleted file mode 100644 index ecf5cf7..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeLabel.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class ReverseGeocodeLabel( - @SerializedName("Label") - val label: String -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodePlace.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodePlace.kt deleted file mode 100644 index 9dd7920..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodePlace.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class ReverseGeocodePlace( - @SerializedName("Place") - val place: ReverseGeocodeLabel -) \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeResponse.kt b/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeResponse.kt deleted file mode 100644 index 96f61ff..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/model/response/ReverseGeocodeResponse.kt +++ /dev/null @@ -1,8 +0,0 @@ -package software.amazon.location.auth.data.model.response - -import com.google.gson.annotations.SerializedName - -data class ReverseGeocodeResponse( - @SerializedName("Results") - val results: List -) diff --git a/library/src/main/java/software/amazon/location/auth/data/network/AwsApiService.kt b/library/src/main/java/software/amazon/location/auth/data/network/AwsApiService.kt deleted file mode 100644 index c9d588c..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/network/AwsApiService.kt +++ /dev/null @@ -1,17 +0,0 @@ -package software.amazon.location.auth.data.network - -import retrofit2.http.Body -import retrofit2.http.Headers -import retrofit2.http.POST -import retrofit2.http.Path -import software.amazon.location.auth.data.model.request.ReverseGeocodeRequest -import software.amazon.location.auth.data.model.response.ReverseGeocodeResponse - -interface AwsApiService { - @POST("places/v0/indexes/{indexName}/search/position") - @Headers("Content-Type: application/json") - suspend fun reverseGeocode( - @Path("indexName") indexName: String, - @Body request: ReverseGeocodeRequest - ): ReverseGeocodeResponse -} diff --git a/library/src/main/java/software/amazon/location/auth/data/network/AwsOkHttpClient.kt b/library/src/main/java/software/amazon/location/auth/data/network/AwsOkHttpClient.kt deleted file mode 100644 index f3b55ca..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/network/AwsOkHttpClient.kt +++ /dev/null @@ -1,35 +0,0 @@ -package software.amazon.location.auth.data.network - -import java.util.concurrent.TimeUnit -import okhttp3.OkHttpClient -import software.amazon.location.auth.AwsSignerInterceptor -import software.amazon.location.auth.LocationCredentialsProvider -import software.amazon.location.auth.utils.Constants.CONNECTION_TIMEOUT -import software.amazon.location.auth.utils.Constants.READ_TIMEOUT - -internal class AwsOkHttpClient { - - companion object { - /** - * Creates and returns an OkHttpClient configured with AWS request signing. - * - * @param serviceName The name of the AWS service (e.g., "execute-api"). - * @param region The AWS region (e.g., "us-west-2"). - * @param credentialsProvider The provider for obtaining AWS credentials. - * @return An OkHttpClient instance with AWS signing interceptor. - */ - fun getClient( - serviceName: String, - region: String, - credentialsProvider: LocationCredentialsProvider? - ): OkHttpClient { - val awsSignerInterceptor = AwsSignerInterceptor(serviceName, region, credentialsProvider) - - return OkHttpClient.Builder() - .connectTimeout(CONNECTION_TIMEOUT, TimeUnit.SECONDS) - .readTimeout(READ_TIMEOUT, TimeUnit.SECONDS) - .addInterceptor(awsSignerInterceptor) - .build() - } - } -} \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/data/network/AwsRetrofitClient.kt b/library/src/main/java/software/amazon/location/auth/data/network/AwsRetrofitClient.kt deleted file mode 100644 index 8a165fe..0000000 --- a/library/src/main/java/software/amazon/location/auth/data/network/AwsRetrofitClient.kt +++ /dev/null @@ -1,73 +0,0 @@ -package software.amazon.location.auth.data.network - -import retrofit2.HttpException -import retrofit2.Retrofit -import retrofit2.converter.gson.GsonConverterFactory -import software.amazon.location.auth.LocationCredentialsProvider -import software.amazon.location.auth.utils.Constants.RESPONSE_CODE_CREDENTIAL_EXPIRED - -/** - * A singleton object that manages the Retrofit client and API service for AWS requests. - */ -object AwsRetrofitClient { - private lateinit var retrofit: Retrofit - private var _apiService: AwsApiService? = null - - /** - * Initializes the Retrofit client with the given parameters. - * - * @param baseUrl The base URL for the Retrofit client. - * @param serviceName The name of the AWS service (e.g., "execute-api"). - * @param region The AWS region (e.g., "us-west-2"). - * @param credentialsProvider The provider for obtaining AWS credentials. - */ - fun init(baseUrl: String, serviceName: String, region: String, credentialsProvider: LocationCredentialsProvider?) { - val client = AwsOkHttpClient.getClient(serviceName, region, credentialsProvider) - retrofit = Retrofit.Builder() - .baseUrl(baseUrl) - .client(client) - .addConverterFactory(GsonConverterFactory.create()) - .build() - } - - /** - * Retrieves the API service instance. If it doesn't exist, it will be created. - * - * @return The API service instance. - */ - val apiService: AwsApiService - get() { - if (_apiService == null) { - createApiService() - } - return _apiService!! - } - - /** - * Creates the API service instance using the Retrofit client. - */ - private fun createApiService() { - _apiService = retrofit.create(AwsApiService::class.java) - } - - /** - * Clears the current API service instance, forcing it to be recreated on the next access. - */ - @Synchronized - fun clearApiService() { - _apiService = null - } - - /** - * Checks if the given exception corresponds to an expired credentials error. - * - * @param e The exception to check. - * @return True if the exception is a HttpException with a status code indicating expired credentials, false otherwise. - */ - fun isHttpStatusCodeCredentialExpired(e: Exception): Boolean { - if (e is HttpException) { - return e.code() == RESPONSE_CODE_CREDENTIAL_EXPIRED - } - return false - } -} \ No newline at end of file diff --git a/library/src/main/java/software/amazon/location/auth/utils/CognitoCredentialsClient.kt b/library/src/main/java/software/amazon/location/auth/utils/CognitoCredentialsClient.kt deleted file mode 100644 index e0c5e02..0000000 --- a/library/src/main/java/software/amazon/location/auth/utils/CognitoCredentialsClient.kt +++ /dev/null @@ -1,94 +0,0 @@ -package software.amazon.location.auth.utils - -import com.google.gson.Gson -import java.io.IOException -import kotlin.coroutines.resume -import kotlin.coroutines.resumeWithException -import kotlinx.coroutines.suspendCancellableCoroutine -import okhttp3.Call -import okhttp3.Callback -import okhttp3.MediaType.Companion.toMediaType -import okhttp3.OkHttpClient -import okhttp3.Request -import okhttp3.RequestBody.Companion.toRequestBody -import okhttp3.Response -import software.amazon.location.auth.data.model.request.GetCredentialRequest -import software.amazon.location.auth.data.model.request.GetIdentityIdRequest -import software.amazon.location.auth.data.model.response.GetCredentialResponse -import software.amazon.location.auth.data.model.response.GetIdentityIdResponse -import software.amazon.location.auth.utils.Constants.HEADER_X_AMZ_TARGET -import software.amazon.location.auth.utils.Constants.MEDIA_TYPE -import software.amazon.location.auth.utils.Constants.URL - - -class CognitoCredentialsClient(private val region: String) { - var client = OkHttpClient() - - suspend fun getIdentityId(identityPoolId: String): String { - return suspendCancellableCoroutine { continuation -> - val requestBody = GetIdentityIdRequest(identityPoolId) - val mediaType = MEDIA_TYPE.toMediaType() - val json = Gson().toJson(requestBody) - - val request = Request.Builder() - .url(getUrl()) - .post(json.toRequestBody(mediaType)) - .addHeader(HEADER_X_AMZ_TARGET, "AWSCognitoIdentityService.GetId") - .build() - - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - continuation.resumeWithException(e) - } - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val jsonResponse = response.body?.string() - val getIdentityIdResponse = - Gson().fromJson(jsonResponse, GetIdentityIdResponse::class.java) - continuation.resume(getIdentityIdResponse?.identityId ?: "") - } else { - continuation.resumeWithException(IOException("Failed to get identity ID")) - } - } - }) - } - } - - suspend fun getCredentials(identityId: String): GetCredentialResponse { - return suspendCancellableCoroutine { continuation -> - val requestBody = GetCredentialRequest(identityId) - val mediaType = MEDIA_TYPE.toMediaType() - val json = Gson().toJson(requestBody) - - val request = Request.Builder() - .url(getUrl()) - .post(json.toRequestBody(mediaType)) - .addHeader(HEADER_X_AMZ_TARGET, "AWSCognitoIdentityService.GetCredentialsForIdentity") - .build() - - client.newCall(request).enqueue(object : Callback { - override fun onFailure(call: Call, e: IOException) { - continuation.resumeWithException(e) - } - - override fun onResponse(call: Call, response: Response) { - if (response.isSuccessful) { - val jsonResponse = response.body?.string() - - val getCredentialResponse = - Gson().fromJson(jsonResponse, GetCredentialResponse::class.java) - continuation.resume(getCredentialResponse ?: throw IOException("Failed to get credentials")) - } else { - continuation.resumeWithException(IOException("Failed to get credentials")) - } - } - }) - } - } - - private fun getUrl(): String { - val urlBuilder = StringBuilder(URL.format(region)) - return urlBuilder.toString() - } -} \ No newline at end of file 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 3ce17e0..d4f25e3 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 @@ -2,11 +2,7 @@ package software.amazon.location.auth.utils object Constants { - const val BASE_URL = "https://places.geo.%s.amazonaws.com/" - const val URL = "https://cognito-identity.%s.amazonaws.com/" - const val MEDIA_TYPE = "application/x-amz-json-1.1" const val SIGNING_ALGORITHM = "AWS4-HMAC-SHA256" - const val HEADER_X_AMZ_TARGET = "X-Amz-Target" const val TIME_PATTERN = "yyyyMMdd'T'HHmmss'Z'" const val HEADER_X_AMZ_DATE = "x-amz-date" const val HEADER_HOST = "host" @@ -22,9 +18,4 @@ object Constants { const val IDENTITY_POOL_ID= "identityPoolId" const val API_KEY = "apiKey" const val DEFAULT_ENCODING = "UTF-8" - const val SERVICE_NAME = "geo" - - const val RESPONSE_CODE_CREDENTIAL_EXPIRED = 403 - const val CONNECTION_TIMEOUT = 30L - const val READ_TIMEOUT = 30L } \ No newline at end of file diff --git a/library/src/test/java/software/amazon/location/auth/AmazonLocationClientTest.kt b/library/src/test/java/software/amazon/location/auth/AmazonLocationClientTest.kt new file mode 100644 index 0000000..24c33ed --- /dev/null +++ b/library/src/test/java/software/amazon/location/auth/AmazonLocationClientTest.kt @@ -0,0 +1,52 @@ + +package software.amazon.location.auth + +import android.content.Context +import aws.sdk.kotlin.services.location.LocationClient +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.mockkConstructor +import io.mockk.runs +import kotlin.test.assertNotNull +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.junit.Before +import org.junit.Test +import software.amazon.location.auth.utils.Constants + +class AmazonLocationClientTest { + + private lateinit var context: Context + private lateinit var mockLocationClient: LocationClient + private lateinit var amazonLocationClient: AmazonLocationClient + private val coroutineScope = CoroutineScope(Dispatchers.IO) + @Before + fun setUp() { + context = mockk(relaxed = true) + mockLocationClient = mockk(relaxed = true) + mockkConstructor(EncryptedSharedPreferences::class) + + every { anyConstructed().initEncryptedSharedPreferences() } just runs + every { anyConstructed().put(any(), any()) } just runs + every { anyConstructed().get(Constants.REGION) } returns "us-east-1" + every { anyConstructed().clear() } just runs + + } + + @Test + fun `test reverseGeocode with valid response`() { + every { anyConstructed().get(Constants.METHOD) } returns "cognito" + 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" + amazonLocationClient = AmazonLocationClient(mockLocationClient) + coroutineScope.launch { + amazonLocationClient.reverseGeocode("indexName", 0.0, 0.0, "en", 10) + assertNotNull(amazonLocationClient) + } + } + +} 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 b1b5305..203a9d8 100644 --- a/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AuthHelperTest.kt @@ -14,8 +14,9 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import software.amazon.location.auth.utils.AwsRegions import software.amazon.location.auth.utils.Constants +import software.amazon.location.auth.utils.Constants.TEST_IDENTITY_POOL_ID + -private const val TEST_IDENTITY_POOL_ID = "us-east-1:dummyIdentityPoolId" private const val TEST_API_KEY = "dummyApiKey" class AuthHelperTest { diff --git a/library/src/test/java/software/amazon/location/auth/AwsRetrofitClientTest.kt b/library/src/test/java/software/amazon/location/auth/AwsRetrofitClientTest.kt deleted file mode 100644 index 82cc96d..0000000 --- a/library/src/test/java/software/amazon/location/auth/AwsRetrofitClientTest.kt +++ /dev/null @@ -1,76 +0,0 @@ -package software.amazon.location.auth - -import io.mockk.every -import io.mockk.mockk -import io.mockk.mockkStatic -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertTrue -import org.junit.Before -import org.junit.Test -import retrofit2.HttpException -import retrofit2.Retrofit -import software.amazon.location.auth.data.network.AwsApiService -import software.amazon.location.auth.data.network.AwsOkHttpClient -import software.amazon.location.auth.data.network.AwsRetrofitClient -import software.amazon.location.auth.utils.Constants.TEST_REGION -import software.amazon.location.auth.utils.Constants.TEST_SERVICE -import software.amazon.location.auth.utils.Constants.TEST_URL1 - -class AwsRetrofitClientTest { - - private lateinit var mockRetrofit: Retrofit - private lateinit var mockAwsApiService: AwsApiService - private lateinit var mockCredentialsProvider: LocationCredentialsProvider - private lateinit var mockHttpException: HttpException - - @Before - fun setUp() { - mockRetrofit = mockk(relaxed = true) - mockAwsApiService = mockk(relaxed = true) - mockCredentialsProvider = mockk(relaxed = true) - mockHttpException = mockk() - - every { mockRetrofit.create(AwsApiService::class.java) } returns mockAwsApiService - - mockkStatic(AwsOkHttpClient::class) - AwsRetrofitClient.init(TEST_URL1, TEST_SERVICE, TEST_REGION, mockCredentialsProvider) - } - - @Test - fun `test init initializes Retrofit and AwsApiService`() { - assertNotNull(AwsRetrofitClient.apiService) - } - - @Test - fun `test apiService returns existing instance if initialized`() { - val firstInstance = AwsRetrofitClient.apiService - val secondInstance = AwsRetrofitClient.apiService - assertNotNull(firstInstance) - assertNotNull(secondInstance) - assert(firstInstance === secondInstance) { "Instances should be the same" } - } - - @Test - fun `test clearApiService sets _apiService to null`() { - assertNotNull(AwsRetrofitClient.apiService) - AwsRetrofitClient.clearApiService() - } - - @Test - fun `test isHttpStatusCodeCredentialExpired returns true for expired credential error`() { - every { mockHttpException.code() } returns RESPONSE_CODE_CREDENTIAL_EXPIRED - val result = AwsRetrofitClient.isHttpStatusCodeCredentialExpired(mockHttpException) - assertTrue(result) - } - - @Test - fun `test isHttpStatusCodeCredentialExpired returns false for non-HttpException`() { - val result = AwsRetrofitClient.isHttpStatusCodeCredentialExpired(Exception()) - assertFalse(result) - } - - companion object { - const val RESPONSE_CODE_CREDENTIAL_EXPIRED = 403 - } -} 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 b37ae2c..54fc25e 100644 --- a/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt +++ b/library/src/test/java/software/amazon/location/auth/AwsSignerInterceptorTest.kt @@ -1,5 +1,6 @@ package software.amazon.location.auth +import aws.sdk.kotlin.services.cognitoidentity.model.Credentials import io.mockk.coEvery import io.mockk.every import io.mockk.mockk @@ -14,7 +15,6 @@ import okhttp3.Interceptor import okhttp3.Request import org.junit.Before import org.junit.Test -import software.amazon.location.auth.data.model.response.Credentials 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 diff --git a/library/src/test/java/software/amazon/location/auth/CognitoCredentialsClientTest.kt b/library/src/test/java/software/amazon/location/auth/CognitoCredentialsClientTest.kt deleted file mode 100644 index ff19052..0000000 --- a/library/src/test/java/software/amazon/location/auth/CognitoCredentialsClientTest.kt +++ /dev/null @@ -1,71 +0,0 @@ -package software.amazon.location.auth - -import android.content.Context -import io.mockk.every -import io.mockk.mockk -import kotlin.test.assertTrue -import kotlinx.coroutines.runBlocking -import okhttp3.Call -import okhttp3.Callback -import okhttp3.MediaType.Companion.toMediaTypeOrNull -import okhttp3.OkHttpClient -import okhttp3.Response -import okhttp3.ResponseBody.Companion.toResponseBody -import org.junit.Before -import org.junit.Test -import software.amazon.location.auth.utils.CognitoCredentialsClient -import software.amazon.location.auth.utils.Constants.JSON -import software.amazon.location.auth.utils.Constants.JSON_2 - - -class CognitoCredentialsClientTest { - private lateinit var cognitoCredentialsClient: CognitoCredentialsClient - private lateinit var mockClient: OkHttpClient - private lateinit var mockCall: Call - private lateinit var mockResponse: Response - lateinit var context: Context - - @Before - fun setUp() { - context = mockk(relaxed = true) - mockClient = mockk() - mockCall = mockk() - mockResponse = mockk() - cognitoCredentialsClient = CognitoCredentialsClient("us-east-1") - cognitoCredentialsClient.client = mockClient - } - - @Test - fun `test getIdentityId`() = runBlocking { - val jsonResponseBody = JSON.toResponseBody("application/json".toMediaTypeOrNull()) - - every { mockClient.newCall(any()) } returns mockCall - every { mockCall.enqueue(any()) } answers { - val callback = arg(0) - callback.onResponse(mockCall, mockResponse) - } - every { mockResponse.isSuccessful } returns true - every { mockResponse.body } returns jsonResponseBody - - - val result = cognitoCredentialsClient.getIdentityId("us-east-1:xxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx") - assertTrue(result == "us-east-1:xxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx") - } - - @Test - fun `test getCredentials`() = runBlocking { - val jsonResponseBody = JSON_2.toResponseBody("application/json".toMediaTypeOrNull()) - - every { mockClient.newCall(any()) } returns mockCall - every { mockCall.enqueue(any()) } answers { - val callback = arg(0) - callback.onResponse(mockCall, mockResponse) - } - every { mockResponse.isSuccessful } returns true - every { mockResponse.body } returns jsonResponseBody - - - val result = cognitoCredentialsClient.getCredentials("us-east-1:xxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx") - assertTrue(result.credentials.secretKey == "test") - } -} \ No newline at end of file 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 192f242..c6a8945 100644 --- a/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/CognitoCredentialsProviderTest.kt @@ -1,6 +1,9 @@ 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 @@ -10,9 +13,9 @@ import io.mockk.verify import junit.framework.TestCase.assertEquals import junit.framework.TestCase.assertNull import kotlin.test.assertFailsWith +import kotlin.test.assertNotNull import org.junit.Before import org.junit.Test -import software.amazon.location.auth.data.model.response.Credentials 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 @@ -38,24 +41,29 @@ class CognitoCredentialsProviderTest { @Test fun `constructor with credentials saves credentials`() { - val credentials = Credentials("accessKeyId", 1234567890.0, "secretKey", "sessionToken") + val credentials = Credentials.invoke { + accessKeyId = "accessKeyId" + expiration = Instant.now() + secretKey = "secretKey" + sessionToken = "sessionToken" + } every { anyConstructed().put( ACCESS_KEY_ID, - credentials.accessKeyId + credentials.accessKeyId!! ) } just runs every { anyConstructed().put( SECRET_KEY, - credentials.secretKey + credentials.secretKey!! ) } just runs every { anyConstructed().put( SESSION_TOKEN, - credentials.sessionToken + credentials.sessionToken!! ) } just runs every { @@ -68,10 +76,15 @@ class CognitoCredentialsProviderTest { CognitoCredentialsProvider(context, credentials) verify { - anyConstructed().put(ACCESS_KEY_ID, credentials.accessKeyId) - anyConstructed().put(SECRET_KEY, credentials.secretKey) - anyConstructed().put(SESSION_TOKEN, credentials.sessionToken) - anyConstructed().put(EXPIRATION, credentials.expiration.toString()) + anyConstructed().put( + ACCESS_KEY_ID, + credentials.accessKeyId!! + ) + anyConstructed().put(SECRET_KEY, credentials.secretKey!!) + anyConstructed().put( + SESSION_TOKEN, + credentials.sessionToken!! + ) } } @@ -84,20 +97,6 @@ class CognitoCredentialsProviderTest { } } - @Test - fun `getCachedCredentials returns credentials when found`() { - val credentials = Credentials("accessKeyId", 1234567890.0, "secretKey", "sessionToken") - every { anyConstructed().get(ACCESS_KEY_ID) } returns credentials.accessKeyId - every { anyConstructed().get(SECRET_KEY) } returns credentials.secretKey - every { anyConstructed().get(SESSION_TOKEN) } returns credentials.sessionToken - every { anyConstructed().get(EXPIRATION) } returns credentials.expiration.toString() - - val provider = CognitoCredentialsProvider(context, credentials) - val cachedCredentials = provider.getCachedCredentials() - - assertEquals(credentials, cachedCredentials) - } - @Test fun `getCachedCredentials returns null when not all credentials are found`() { every { anyConstructed().get(ACCESS_KEY_ID) } returns "accessKeyId" @@ -120,7 +119,12 @@ class CognitoCredentialsProviderTest { fun `getCachedCredentials throws when not initialized`() { val provider = CognitoCredentialsProvider( context, - Credentials("accessKeyId", 1234567890.0, "secretKey", "sessionToken") + Credentials.invoke { + accessKeyId = "accessKeyId" + expiration = Instant.now() + secretKey = "secretKey" + sessionToken = "sessionToken" + } ) every { anyConstructed().get(ACCESS_KEY_ID) } throws Exception("Not initialized") @@ -134,7 +138,12 @@ class CognitoCredentialsProviderTest { fun `clearCredentials clears the stored credentials`() { val provider = CognitoCredentialsProvider( context, - Credentials("accessKeyId", 1234567890.0, "secretKey", "sessionToken") + Credentials.invoke { + accessKeyId = "accessKeyId" + expiration = Instant.now() + secretKey = "secretKey" + sessionToken = "sessionToken" + } ) provider.clearCredentials() 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 c5f992a..5d2ecae 100644 --- a/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt +++ b/library/src/test/java/software/amazon/location/auth/LocationCredentialsProviderTest.kt @@ -1,16 +1,24 @@ package software.amazon.location.auth import android.content.Context +import aws.sdk.kotlin.services.cognitoidentity.model.Credentials +import aws.sdk.kotlin.services.location.LocationClient +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 io.mockk.verify +import junit.framework.TestCase.assertFalse import org.junit.Before import org.junit.Test import kotlin.test.assertFailsWith import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch @@ -23,17 +31,19 @@ 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 -private const val TEST_IDENTITY_POOL_ID = "us-east-1:dummyIdentityPoolId" private const val TEST_API_KEY = "dummyApiKey" class LocationCredentialsProviderTest { private lateinit var context: Context + private lateinit var locationClient: LocationClient private val coroutineScope = CoroutineScope(Dispatchers.Default) @Before fun setUp() { context = mockk(relaxed = true) + locationClient = mockk(relaxed = true) mockkConstructor(EncryptedSharedPreferences::class) every { anyConstructed().initEncryptedSharedPreferences() } just runs @@ -93,7 +103,43 @@ class LocationCredentialsProviderTest { val provider = LocationCredentialsProvider(context, TEST_API_KEY) assertNotNull(provider.getApiKeyProvider()) } + @Test + fun `isCredentialsValid returns true when credentials are valid`() { + val expirationTime = Instant.fromEpochMilliseconds(Instant.now().epochMilliseconds + 10000) // 10 seconds in the future + val mockCredentials = mockk { + every { expiration } returns expirationTime + } + 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) + coroutineScope.launch { + provider.refresh() + } + val result = provider.isCredentialsValid(mockCredentials) + assertTrue(result) + } + @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().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) + val result = provider.isCredentialsValid(mockCredentials) + assertFalse(result) + } @Test fun `clear successfully clears cognito credentials`() { every { anyConstructed().get(METHOD) } returns "cognito" 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 cdd02bb..bfb7ed5 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 @@ -3,23 +3,19 @@ package software.amazon.location.auth.utils import io.mockk.impl.recording.WasNotCalled.method object Constants { - val JSON = - "{\"IdentityId\":\"us-east-1:xxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx\"}" - val JSON_2 = - "{\"Credentials\":{\"AccessKeyId\":\"test\",\"Expiration\":1.715700986E9,\"SecretKey\":\"test\",\"SessionToken\":\"test\"},\"IdentityId\":\"us-east-1:xxxxxx-xxxx-xxxx-xxxxx-xxxxxxxxxx\"}" - 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 API_KEY= "apiKey" - const val REGION= "region" + 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 API_KEY = "apiKey" + 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" - const val TEST_SERVICE = "geo" const val TEST_REGION = "us-west-2" const val TEST_URL = "https://service.amazonaws.com" const val TEST_URL1 = "https://example.com" + const val TEST_IDENTITY_POOL_ID = "us-east-1:dummyIdentityPoolId" } \ No newline at end of file