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

fix(datastore): OIDC Rework #966

Merged
merged 15 commits into from
Oct 14, 2021
Merged
Show file tree
Hide file tree
Changes from 8 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
Original file line number Diff line number Diff line change
Expand Up @@ -36,20 +36,16 @@ import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
import io.flutter.plugin.common.MethodChannel.Result
import kotlinx.coroutines.ObsoleteCoroutinesApi

/** AmplifyApiPlugin */
@ObsoleteCoroutinesApi
class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {

companion object {
/**
* Thrown when [tokenType] is used but is not a valid [AuthorizationType].
*/
private fun invalidTokenType(tokenType: String? = null) = ApiException.ApiAuthException(
"Invalid arguments",
"Invalid token type: $tokenType"
)
lateinit var channel: MethodChannel
}

private lateinit var channel: MethodChannel
private lateinit var eventchannel: EventChannel
private lateinit var context: Context
private val graphqlSubscriptionStreamHandler: GraphQLSubscriptionStreamHandler
Expand Down Expand Up @@ -103,12 +99,6 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
try {
val arguments: Map<String, Any> = call.arguments as Map<String, Any>

// Update tokens if included with request
val tokens = arguments["tokens"] as? List<*>
if (tokens != null && tokens.isNotEmpty()) {
updateTokens(tokens)
}

when (call.method) {
"get" -> FlutterRestApi.get(result, arguments)
"post" -> FlutterRestApi.post(result, arguments)
Expand All @@ -123,17 +113,6 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
arguments,
graphqlSubscriptionStreamHandler
)
"updateTokens" -> {
if (tokens == null || tokens.isEmpty()) {
throw ApiException(
"Invalid token map provided",
"Provide tokens in the \"tokens\" field"
)
}

// Tokens already updated
result.success(null)
}
else -> result.notImplemented()
}
} catch (e: Exception) {
Expand Down Expand Up @@ -162,19 +141,6 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
}
}

private fun updateTokens(tokens: List<*>) {
for (authToken in tokens.cast<Map<String, Any?>>()) {
val token = authToken["token"] as? String?
val tokenType = authToken["type"] as? String ?: throw invalidTokenType()
val authType: AuthorizationType = try {
AuthorizationType.from(tokenType)
} catch (e: Exception) {
throw invalidTokenType(tokenType)
}
FlutterAuthProviders.setToken(authType, token)
}
}

override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,27 @@
*/
package com.amazonaws.amplify.amplify_api.auth

import com.amazonaws.amplify.amplify_api.AmplifyApiPlugin
import com.amplifyframework.api.ApiException
import com.amplifyframework.api.aws.ApiAuthProviders
import com.amplifyframework.api.aws.AuthorizationType
import com.amplifyframework.api.aws.sigv4.FunctionAuthProvider
import com.amplifyframework.api.aws.sigv4.OidcAuthProvider
import io.flutter.Log
import io.flutter.plugin.common.MethodChannel
import kotlinx.coroutines.*

/**
* Manages the shared state of all [FlutterAuthProvider] instances.
*/
@ObsoleteCoroutinesApi
object FlutterAuthProviders {

/**
* Thread to run token retrieval operations so to not block calling thread or main thread.
*/
private val coroutineContext = newSingleThreadContext("FlutterAuthProviders")

/**
* A factory of [FlutterAuthProvider] instances.
*/
Expand All @@ -35,36 +46,78 @@ object FlutterAuthProviders {
.build()
}

/**
* Token cache for all [FlutterAuthProvider] instances.
*/
private var tokens: MutableMap<AuthorizationType, String?> = mutableMapOf()

/**
* Retrieves the token for [authType] or `null`, if unavailable.
*
* This requires a dance of threads to be able to not block the main thread. This function
* is called from within the Amplify library and from a thread besides the main one. In order
* to not block the calling thread or the main one, we create a private thread ([coroutineContext])
* which we can safely block on and wait for method channel calls to complete.
*
* This also allows the Flutter app to make method channel calls of its own, in response to our
* method channel invocation, without deadlock.
*/
fun getToken(authType: AuthorizationType): String? = tokens[authType]
fun getToken(authType: AuthorizationType): String? {
try {
return runBlocking(coroutineContext) {
val completer = Job()

/**
* Sets the token for [authType] to [value].
*/
fun setToken(authType: AuthorizationType, value: String?) {
tokens[authType] = value
val result = object : MethodChannel.Result {
var token: String? = null

override fun success(result: Any?) {
token = result as? String
launch(Dispatchers.Main) {
completer.complete()
}
}

override fun error(errorCode: String?, errorMessage: String?, errorDetails: Any?) {
launch(Dispatchers.Main) {
completer.complete()
}
}

override fun notImplemented() {
launch(Dispatchers.Main) {
completer.complete()
}
}
}
launch(Dispatchers.Main) {
AmplifyApiPlugin.channel.invokeMethod(
"getLatestAuthToken",
authType.name,
result
)
}

completer.join()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will this timeout like the ios version after 2 seconds?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nope, good catch. Fixed.

return@runBlocking result.token
}
} catch (e: Exception) {
// runBlocking can throw if the thread is interrupted, for example.
return null
}
}
}

/**
* A provider which manages token retrieval for its [AuthorizationType].
*/
@ObsoleteCoroutinesApi
class FlutterAuthProvider(private val type: AuthorizationType) : FunctionAuthProvider,
OidcAuthProvider {
private companion object {
/**
* Thrown when there is no token available for [type].
*/
fun noTokenAvailable(type: AuthorizationType) = ApiException.ApiAuthException(
"No $type token available",
"Ensure that `getLatestAuthToken` returns a value"
"Unable to retrieve token for $type",
"""
Make sure you register your auth providers in the addPlugin call and
that getLatestAuthToken returns a value.
""".trimIndent()
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
4B6CB97626BAEC7B004E4AA2 /* AuthProviderTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B6CB97526BAEC7B004E4AA2 /* AuthProviderTests.swift */; };
4BF054AD269DE2FB00D1F2BF /* FlutterURLSessionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF054AC269DE2FB00D1F2BF /* FlutterURLSessionTests.swift */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
7E94B1FC95440E5C0AD8AB8D /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 30DF0857352AA115AF86C33E /* Pods_Runner.framework */; };
Expand Down Expand Up @@ -40,7 +39,6 @@
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
30DF0857352AA115AF86C33E /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
4B6CB97526BAEC7B004E4AA2 /* AuthProviderTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthProviderTests.swift; sourceTree = "<group>"; };
4BF054AC269DE2FB00D1F2BF /* FlutterURLSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FlutterURLSessionTests.swift; sourceTree = "<group>"; };
7457497C86253194DEE2467D /* Pods-unit_tests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-unit_tests.release.xcconfig"; path = "Target Support Files/Pods-unit_tests/Pods-unit_tests.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
Expand Down Expand Up @@ -160,7 +158,6 @@
FB7C4075259A81E00021F98A /* Info.plist */,
840F5D16259C147800C968A8 /* RestApiUnitTests.swift */,
4BF054AC269DE2FB00D1F2BF /* FlutterURLSessionTests.swift */,
4B6CB97526BAEC7B004E4AA2 /* AuthProviderTests.swift */,
);
path = unit_tests;
sourceTree = "<group>";
Expand Down Expand Up @@ -393,7 +390,6 @@
files = (
FB7C4074259A81E00021F98A /* GraphQLApiUnitTests.swift in Sources */,
840F5D17259C147800C968A8 /* RestApiUnitTests.swift in Sources */,
4B6CB97626BAEC7B004E4AA2 /* AuthProviderTests.swift in Sources */,
4BF054AD269DE2FB00D1F2BF /* FlutterURLSessionTests.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
Loading