Skip to content

Commit 1d27acb

Browse files
authored
feat(auth): OIDC/Lambda Support (#777)
* OIDC/Lambda support * Clean up * Fix iOS test * Add unit tests * Fix Android test * Fix Android tests * Refactor and remove AuthToken from the public API * Remove concurrent guards * Clean up
1 parent daf1f68 commit 1d27acb

File tree

23 files changed

+876
-39
lines changed

23 files changed

+876
-39
lines changed

packages/amplify_analytics_pinpoint/example/.gitignore

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,24 @@ app.*.map.json
4444
!**/.idea/
4545
!**/.idea/codeStyles/
4646
!**/.idea/codeStyles/*
47+
48+
#amplify-do-not-edit-begin
49+
amplify/\#current-cloud-backend
50+
amplify/.config/local-*
51+
amplify/logs
52+
amplify/mock-data
53+
amplify/backend/amplify-meta.json
54+
amplify/backend/awscloudformation
55+
amplify/backend/.temp
56+
build/
57+
dist/
58+
node_modules/
59+
aws-exports.js
60+
awsconfiguration.json
61+
amplifyconfiguration.json
62+
amplifyconfiguration.dart
63+
amplify-build-config.json
64+
amplify-gradle-config.json
65+
amplifytools.xcconfig
66+
.secret-*
67+
#amplify-do-not-edit-end

packages/amplify_analytics_pinpoint/ios/amplify_analytics_pinpoint.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ This code is the iOS part of the Amplify Flutter Pinpoint Analytics Plugin. The
1515
s.source = { :path => '.' }
1616
s.source_files = 'Classes/**/*'
1717
s.dependency 'Flutter'
18-
s.dependency 'Amplify', '~> 1.11.0'
19-
s.dependency 'AmplifyPlugins/AWSPinpointAnalyticsPlugin', '~> 1.11.0'
18+
s.dependency 'Amplify', '~> 1.13.0'
19+
s.dependency 'AmplifyPlugins/AWSPinpointAnalyticsPlugin', '~> 1.13.0'
2020
s.dependency 'amplify_core'
2121
s.platform = :ios, '11.0'
2222

packages/amplify_api/android/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,9 @@ android {
4949
testCoverageEnabled = true
5050
}
5151
}
52+
testOptions {
53+
unitTests.includeAndroidResources = true
54+
}
5255
}
5356

5457
dependencies {
@@ -57,10 +60,12 @@ dependencies {
5760
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
5861
implementation "com.amplifyframework:aws-api:1.24.0"
5962
implementation "com.amplifyframework:aws-api-appsync:1.24.0"
63+
implementation 'androidx.test.ext:junit-ktx:1.1.3'
6064

6165
testImplementation 'junit:junit:4.13'
6266
testImplementation 'org.mockito:mockito-core:3.10.0'
6367
testImplementation 'org.mockito:mockito-inline:3.1.0'
6468
testImplementation 'androidx.test:core:1.2.0'
6569
testImplementation 'org.robolectric:robolectric:4.3.1'
70+
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.3.9'
6671
}

packages/amplify_api/android/src/main/kotlin/com/amazonaws/amplify/amplify_api/AmplifyApiPlugin.kt

Lines changed: 63 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -20,21 +20,33 @@ import android.os.Handler
2020
import android.os.Looper
2121
import androidx.annotation.NonNull
2222
import androidx.annotation.VisibleForTesting
23+
import com.amazonaws.amplify.amplify_api.auth.FlutterAuthProviders
2324
import com.amazonaws.amplify.amplify_api.rest_api.FlutterRestApi
25+
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.createSerializedUnrecognizedError
26+
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.handleAddPluginException
27+
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.postExceptionToFlutterChannel
28+
import com.amplifyframework.api.ApiException
2429
import com.amplifyframework.api.aws.AWSApiPlugin
30+
import com.amplifyframework.api.aws.AuthorizationType
2531
import com.amplifyframework.core.Amplify
2632
import io.flutter.embedding.engine.plugins.FlutterPlugin
2733
import io.flutter.plugin.common.EventChannel
2834
import io.flutter.plugin.common.MethodCall
2935
import io.flutter.plugin.common.MethodChannel
3036
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
3137
import io.flutter.plugin.common.MethodChannel.Result
32-
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.createSerializedUnrecognizedError
33-
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.handleAddPluginException
34-
import com.amazonaws.amplify.amplify_core.exception.ExceptionUtil.Companion.postExceptionToFlutterChannel
3538

3639
/** AmplifyApiPlugin */
3740
class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
41+
companion object {
42+
/**
43+
* Thrown when [tokenType] is used but is not a valid [AuthorizationType].
44+
*/
45+
private fun invalidTokenType(tokenType: String? = null) = ApiException.ApiAuthException(
46+
"Invalid arguments",
47+
"Invalid token type: $tokenType"
48+
)
49+
}
3850

3951
private lateinit var channel: MethodChannel
4052
private lateinit var eventchannel: EventChannel
@@ -50,26 +62,35 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
5062
constructor(eventHandler: GraphQLSubscriptionStreamHandler) {
5163
graphqlSubscriptionStreamHandler = eventHandler
5264
}
65+
5366
private val handler = Handler(Looper.getMainLooper())
5467

5568
override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
5669
channel = MethodChannel(flutterPluginBinding.binaryMessenger, "com.amazonaws.amplify/api")
5770
channel.setMethodCallHandler(this)
58-
eventchannel = EventChannel(flutterPluginBinding.binaryMessenger, "com.amazonaws.amplify/api_observe_events")
71+
eventchannel = EventChannel(
72+
flutterPluginBinding.binaryMessenger,
73+
"com.amazonaws.amplify/api_observe_events"
74+
)
5975
eventchannel.setStreamHandler(graphqlSubscriptionStreamHandler)
6076
context = flutterPluginBinding.applicationContext
6177
}
6278

79+
@Suppress("UNCHECKED_CAST")
6380
override fun onMethodCall(call: MethodCall, result: Result) {
64-
var methodName = call.method
81+
val methodName = call.method
6582

66-
if(methodName == "cancel"){
83+
if (methodName == "cancel") {
6784
onCancel(result, (call.arguments as String))
6885
return
69-
}
70-
else if(methodName == "addPlugin"){
86+
} else if (methodName == "addPlugin") {
7187
try {
72-
Amplify.addPlugin(AWSApiPlugin())
88+
Amplify.addPlugin(
89+
AWSApiPlugin
90+
.builder()
91+
.apiAuthProviders(FlutterAuthProviders.factory)
92+
.build()
93+
)
7394
LOG.info("Added API plugin")
7495
result.success(null)
7596
} catch (e: Exception) {
@@ -79,7 +100,22 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
79100
}
80101

81102
try {
82-
var arguments : Map<String, Any> = call.arguments as Map<String,Any>
103+
val arguments: Map<String, Any> = call.arguments as Map<String, Any>
104+
105+
// Update tokens if included with request
106+
val tokens = arguments["tokens"] as? List<*>
107+
if (tokens != null && tokens.isNotEmpty()) {
108+
for (authToken in tokens as List<Map<*, *>>) {
109+
val token = authToken["token"] as? String?
110+
val tokenType = authToken["type"] as? String ?: throw invalidTokenType()
111+
val authType: AuthorizationType = try {
112+
AuthorizationType.from(tokenType)
113+
} catch (e: Exception) {
114+
throw invalidTokenType(tokenType)
115+
}
116+
FlutterAuthProviders.setToken(authType, token)
117+
}
118+
}
83119

84120
when (call.method) {
85121
"get" -> FlutterRestApi.get(result, arguments)
@@ -90,28 +126,36 @@ class AmplifyApiPlugin : FlutterPlugin, MethodCallHandler {
90126
"patch" -> FlutterRestApi.patch(result, arguments)
91127
"query" -> FlutterGraphQLApi.query(result, arguments)
92128
"mutate" -> FlutterGraphQLApi.mutate(result, arguments)
93-
"subscribe" -> FlutterGraphQLApi.subscribe(result, arguments, graphqlSubscriptionStreamHandler)
129+
"subscribe" -> FlutterGraphQLApi.subscribe(
130+
result,
131+
arguments,
132+
graphqlSubscriptionStreamHandler
133+
)
94134
else -> result.notImplemented()
95135
}
96136
} catch (e: Exception) {
97137
handler.post {
98-
postExceptionToFlutterChannel(result, "ApiException",
99-
createSerializedUnrecognizedError(e))
138+
postExceptionToFlutterChannel(
139+
result, "ApiException",
140+
createSerializedUnrecognizedError(e)
141+
)
100142
}
101143
}
102144
}
103145

104146
fun onCancel(
105-
flutterResult: Result,
106-
cancelToken: String) {
107-
if(OperationsManager.containsOperation(cancelToken)) {
147+
flutterResult: Result,
148+
cancelToken: String
149+
) {
150+
if (OperationsManager.containsOperation(cancelToken)) {
108151
OperationsManager.cancelOperation(cancelToken)
109152
flutterResult.success("Operation Canceled")
110153
} else {
111154
flutterResult.error(
112-
"AmplifyAPI-CancelError",
113-
"The Operation may have already been completed or expired and cannot be canceled anymore",
114-
"Operation does not exist")
155+
"AmplifyAPI-CancelError",
156+
"The Operation may have already been completed or expired and cannot be canceled anymore",
157+
"Operation does not exist"
158+
)
115159
}
116160
}
117161

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/*
2+
* Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
package com.amazonaws.amplify.amplify_api.auth
16+
17+
import com.amplifyframework.api.ApiException
18+
import com.amplifyframework.api.aws.ApiAuthProviders
19+
import com.amplifyframework.api.aws.AuthorizationType
20+
import com.amplifyframework.api.aws.sigv4.FunctionAuthProvider
21+
import com.amplifyframework.api.aws.sigv4.OidcAuthProvider
22+
23+
/**
24+
* Manages the shared state of all [FlutterAuthProvider] instances.
25+
*/
26+
object FlutterAuthProviders {
27+
/**
28+
* A factory of [FlutterAuthProvider] instances.
29+
*/
30+
val factory: ApiAuthProviders by lazy {
31+
ApiAuthProviders
32+
.Builder()
33+
.functionAuthProvider(FlutterAuthProvider(AuthorizationType.AWS_LAMBDA))
34+
.oidcAuthProvider(FlutterAuthProvider(AuthorizationType.OPENID_CONNECT))
35+
.build()
36+
}
37+
38+
/**
39+
* Token cache for all [FlutterAuthProvider] instances.
40+
*/
41+
private var tokens: MutableMap<AuthorizationType, String?> = mutableMapOf()
42+
43+
/**
44+
* Retrieves the token for [authType] or `null`, if unavailable.
45+
*/
46+
fun getToken(authType: AuthorizationType): String? = tokens[authType]
47+
48+
/**
49+
* Sets the token for [authType] to [value].
50+
*/
51+
fun setToken(authType: AuthorizationType, value: String?) {
52+
tokens[authType] = value
53+
}
54+
}
55+
56+
/**
57+
* A provider which manages token retrieval for its [AuthorizationType].
58+
*/
59+
class FlutterAuthProvider(private val type: AuthorizationType) : FunctionAuthProvider,
60+
OidcAuthProvider {
61+
private companion object {
62+
/**
63+
* Thrown when there is no token available for [type].
64+
*/
65+
fun noTokenAvailable(type: AuthorizationType) = ApiException.ApiAuthException(
66+
"No $type token available",
67+
"Ensure that `getLatestAuthToken` returns a value"
68+
)
69+
}
70+
71+
override fun getLatestAuthToken(): String =
72+
FlutterAuthProviders.getToken(type) ?: throw noTokenAvailable(type)
73+
}

0 commit comments

Comments
 (0)