Skip to content

Commit

Permalink
Backed out API implementation changes (#15)
Browse files Browse the repository at this point in the history
* Backed out API implementation changes.
A lot of the internals of the implementation are relied on
by the Tracking SDK, so they can't be removed at this time.

* Moved context to last parameter.
This will make it easier to deprecate the parameter in the future.

* Fixed another context parameter and README
  • Loading branch information
mbalfour-amzn authored Oct 31, 2024
1 parent 29656c5 commit 4c6abff
Show file tree
Hide file tree
Showing 17 changed files with 1,270 additions and 395 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ 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() {
val authHelper = AuthHelper.withCognitoIdentityPool("MY-COGNITO-IDENTITY-POOL-ID")
val authHelper = AuthHelper.withCognitoIdentityPool("MY-COGNITO-IDENTITY-POOL-ID", applicationContext)
// Get instances of the standalone clients:
var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig())
Expand All @@ -68,7 +68,7 @@ OR
// Create a credential provider using custom credential provider with AuthHelper
private suspend fun exampleCustomCredentialLogin() {
var authHelper = AuthHelper.withCredentialsProvider(MY-CUSTOM-CREDENTIAL-PROVIDER, "MY-AWS-REGION")
var authHelper = AuthHelper.withCredentialsProvider(MY-CUSTOM-CREDENTIAL-PROVIDER, "MY-AWS-REGION", applicationContext)
// Get instances of the standalone clients:
var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig())
Expand All @@ -82,7 +82,7 @@ OR
// Create a credential provider using Api key with AuthHelper
private suspend fun exampleApiKeyLogin() {
var authHelper = AuthHelper.withApiKey("MY-API-KEY", "MY-AWS-REGION")
var authHelper = AuthHelper.withApiKey("MY-API-KEY", "MY-AWS-REGION", applicationContext)
// Get instances of the standalone clients:
var geoMapsClient = GeoMapsClient(authHelper?.getGeoMapsClientConfig())
Expand All @@ -100,10 +100,10 @@ HttpRequestUtil.setOkHttpClient(
OkHttpClient.Builder()
.addInterceptor(
AwsSignerInterceptor(
applicationContext,
"geo",
"MY-AWS-REGION",
locationCredentialsProvider
locationCredentialsProvider,
applicationContext
)
)
.build()
Expand Down
2 changes: 1 addition & 1 deletion gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
androidx-junit = "1.2.1"
appcompat = "1.7.0"
commons-math3 = "3.6.1"
core-ktx = "1.15.0"
core-ktx = "1.13.1"
espresso-core = "3.6.1"
guava = "32.1.3-jre"
junit = "4.13.2"
Expand Down
2 changes: 1 addition & 1 deletion library/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ mavenPublishing {
publishToMavenCentral(SonatypeHost.DEFAULT, automaticRelease = true)
signAllPublications()

coordinates("software.amazon.location", "auth", "1.0.0")
coordinates("software.amazon.location", "auth", "1.1.0")

pom {
name.set("Amazon Location Service Mobile Authentication SDK for Android")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
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)
}
}
37 changes: 35 additions & 2 deletions library/src/main/java/software/amazon/location/auth/AuthHelper.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

package software.amazon.location.auth

import android.content.Context
import aws.smithy.kotlin.runtime.auth.awscredentials.CredentialsProvider
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
Expand All @@ -20,13 +21,16 @@ object AuthHelper {
*/
suspend fun withCognitoIdentityPool(
identityPoolId: String,
context: Context,
): 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
}
}
Expand All @@ -40,12 +44,37 @@ object AuthHelper {
suspend fun withCognitoIdentityPool(
identityPoolId: String,
region: String,
context: Context,
): 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 withCognitoIdentityPool(
identityPoolId: String,
region: AwsRegions,
context: Context,
): LocationCredentialsProvider {
return withContext(Dispatchers.IO) {
val locationCredentialsProvider = LocationCredentialsProvider(
context,
identityPoolId,
region,
)
locationCredentialsProvider.verifyAndRefreshCredentials()
locationCredentialsProvider // Return the generated locationCredentialsProvider
}
}
Expand Down Expand Up @@ -85,12 +114,14 @@ object AuthHelper {
suspend fun withCredentialsProvider(
credentialsProvider: CredentialsProvider,
region: String,
context: Context,
): LocationCredentialsProvider {
return withContext(Dispatchers.IO) {
val locationCredentialsProvider = LocationCredentialsProvider(
credentialsProvider,
context,
AwsRegions.fromName(region),
)
locationCredentialsProvider.initializeLocationClient(credentialsProvider)
locationCredentialsProvider
}
}
Expand All @@ -101,12 +132,14 @@ object AuthHelper {
* @param region The AWS region as a string.
* @return A LocationCredentialsProvider instance.
*/
suspend fun withApiKey(apiKey: String, region: String): LocationCredentialsProvider {
suspend fun withApiKey(apiKey: String, region: String, context: Context): LocationCredentialsProvider {
return withContext(Dispatchers.IO) {
val locationCredentialsProvider = LocationCredentialsProvider(
context,
AwsRegions.fromName(region),
apiKey,
)
locationCredentialsProvider.initializeLocationClient()
locationCredentialsProvider
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@

package software.amazon.location.auth

import aws.smithy.kotlin.runtime.auth.awscredentials.Credentials

import android.content.Context
import java.net.URL
import java.nio.charset.StandardCharsets
import java.security.MessageDigest
Expand All @@ -16,10 +17,12 @@ 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
Expand All @@ -28,34 +31,30 @@ import software.amazon.location.auth.utils.awsAuthorizationHeader
class AwsSignerInterceptor(
private val serviceName: String,
private val region: String,
private val credentialsProvider: LocationCredentialsProvider?
private val credentialsProvider: LocationCredentialsProvider?,
private val context: Context,
) : Interceptor {

private val sdfMap = HashMap<String, SimpleDateFormat>()
private var securePreferences: EncryptedSharedPreferences?= null
override fun intercept(chain: Interceptor.Chain): Response {
val originalRequest = chain.request()
val credentials: Credentials?
runBlocking {
credentials = credentialsProvider?.getCredentials()
}
if (!originalRequest.url.host.contains("amazonaws.com") || credentials == null) {
if (!originalRequest.url.host.contains("amazonaws.com") || credentialsProvider?.getCredentialsProvider() == null) {
return chain.proceed(originalRequest)
}
val method = credentialsProvider?.getMethod()
if (securePreferences == null){
securePreferences = initPreference(context)
}
val method = securePreferences?.get(METHOD)
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 = credentialsProvider?.getApiKey()
if (!apiKey.isNullOrEmpty()) {
originalHttpUrl.newBuilder()
.addQueryParameter(QUERY_PARAM_KEY, apiKey)
.build()
}
else {
originalHttpUrl
}
val apiKey = securePreferences?.get(API_KEY)
originalHttpUrl.newBuilder()
.addQueryParameter(QUERY_PARAM_KEY, apiKey)
.build()
} else {
originalHttpUrl
}
Expand All @@ -65,9 +64,14 @@ class AwsSignerInterceptor(

return chain.proceed(newRequest)
} else {
val accessKeyId = credentials.accessKeyId
val secretKey = credentials.secretAccessKey
val sessionToken = credentials.sessionToken
runBlocking {
if (!credentialsProvider.isCredentialsValid()) {
credentialsProvider.verifyAndRefreshCredentials()
}
}
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())
Expand Down Expand Up @@ -100,6 +104,10 @@ 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
Expand Down
Loading

0 comments on commit 4c6abff

Please sign in to comment.