Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Backed out API implementation changes #15

Merged
merged 3 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading