From 8b0893cd7d8a5f280b28d49da25ef4f0fbc45723 Mon Sep 17 00:00:00 2001 From: Jordan Nelson Date: Tue, 8 Jun 2021 14:31:05 -0400 Subject: [PATCH] feat(auth): add updateUserAttributes (batch) (#601) * feat: add auth.updateUserAttributes * Apply suggestions from code review Co-authored-by: Chris F <5827964+cshfang@users.noreply.github.com> * address pr comments * refactor android user attributes * consolidate user attr logic on iOS * refactor deliveryDetails serialization * add missing newline * revert missing attribute changes * update serializeAuthUpdateAttributeResult for nil * move comment to to of file * bump amplify-android to 1.17.7 Co-authored-by: Chris F <5827964+cshfang@users.noreply.github.com> --- .../android/build.gradle | 4 +- packages/amplify_api/android/build.gradle | 4 +- .../amplify_auth_cognito/android/build.gradle | 2 +- .../amplify_auth_cognito/AuthCognito.kt | 25 +++ .../FlutterConfirmUserAttributeRequest.kt | 1 + ...endUserAttributeConfirmationCodeRequest.kt | 1 + .../types/FlutterSignUpRequest.kt | 1 + .../FlutterUpdateUserAttributeRequest.kt | 15 +- .../types/FlutterUpdateUserAttributeResult.kt | 22 +-- .../FlutterUpdateUserAttributesRequest.kt | 46 +++++ .../FlutterUpdateUserAttributesResult.kt | 30 ++++ .../AuthCodeDeliveryDetailsSerialization.kt | 28 +++ .../UserAttributeDeserialization.kt} | 2 +- .../utils/UserAttributeSerialization.kt | 35 ++++ .../utils/UserAttributeValidation.kt | 30 ++++ .../AmplifyAuthCognitoPluginTest.kt | 129 ++++++++++++++ .../amplify_auth_cognito_tests.swift | 164 ++++++++++++++++- .../lib/Widgets/UpdateUserAttributes.dart | 166 ++++++++++++++++++ .../lib/Widgets/ViewUserAttributes.dart | 14 ++ .../ios/Classes/AuthCognitoBridge.swift | 12 ++ .../FlutterUpdateUserAttributeResult.swift | 97 +--------- .../FlutterUpdateUserAttributesRequest.swift | 41 +++++ .../FlutterUpdateUserAttributesResult.swift | 35 ++++ .../ios/Classes/SwiftAuthCognito.swift | 8 + ...AuthCodeDeliveryDetailsSerialization.swift | 43 +++++ .../UserAttributeDeserialization.swift} | 0 .../Utils/UserAttributeSerialization.swift | 49 ++++++ .../Utils/UserAttributeValidation.swift | 29 +++ .../lib/amplify_auth_cognito.dart | 6 + .../lib/method_channel_auth_cognito.dart | 27 +++ ...uth_cognito_updateUserAttributes_test.dart | 109 ++++++++++++ .../lib/amplify_auth_plugin_interface.dart | 5 + .../Session/UpdateUserAttributesRequest.dart | 36 ++++ .../lib/src/types.dart | 1 + packages/amplify_core/android/build.gradle | 2 +- .../amplify_datastore/android/build.gradle | 4 +- packages/amplify_flutter/android/build.gradle | 4 +- .../lib/categories/amplify_auth_category.dart | 10 ++ .../amplify_storage_s3/android/build.gradle | 2 +- 39 files changed, 1105 insertions(+), 134 deletions(-) create mode 100644 packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesRequest.kt create mode 100644 packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesResult.kt create mode 100644 packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/AuthCodeDeliveryDetailsSerialization.kt rename packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/{types/FormatUserAttribute.kt => utils/UserAttributeDeserialization.kt} (97%) create mode 100644 packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeSerialization.kt create mode 100644 packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeValidation.kt create mode 100644 packages/amplify_auth_cognito/example/lib/Widgets/UpdateUserAttributes.dart create mode 100644 packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesRequest.swift create mode 100644 packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesResult.swift create mode 100644 packages/amplify_auth_cognito/ios/Classes/Utils/AuthCodeDeliveryDetailsSerialization.swift rename packages/amplify_auth_cognito/ios/Classes/{FormatUserAttribute.swift => Utils/UserAttributeDeserialization.swift} (100%) create mode 100644 packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeSerialization.swift create mode 100644 packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeValidation.swift create mode 100644 packages/amplify_auth_cognito/test/amplify_auth_cognito_updateUserAttributes_test.dart create mode 100644 packages/amplify_auth_plugin_interface/lib/src/Session/UpdateUserAttributesRequest.dart diff --git a/packages/amplify_analytics_pinpoint/android/build.gradle b/packages/amplify_analytics_pinpoint/android/build.gradle index 252103b6a8..2f67221d24 100644 --- a/packages/amplify_analytics_pinpoint/android/build.gradle +++ b/packages/amplify_analytics_pinpoint/android/build.gradle @@ -60,8 +60,8 @@ dependencies { api amplifyCore implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.amplifyframework:aws-analytics-pinpoint:1.17.4' - implementation 'com.amplifyframework:aws-auth-cognito:1.17.4' + implementation 'com.amplifyframework:aws-analytics-pinpoint:1.17.7' + implementation 'com.amplifyframework:aws-auth-cognito:1.17.7' testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-inline:3.1.0' diff --git a/packages/amplify_api/android/build.gradle b/packages/amplify_api/android/build.gradle index f5f1d1af41..bc7f68dab8 100644 --- a/packages/amplify_api/android/build.gradle +++ b/packages/amplify_api/android/build.gradle @@ -55,8 +55,8 @@ dependencies { api amplifyCore implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "com.amplifyframework:aws-api:1.17.4" - implementation "com.amplifyframework:aws-api-appsync:1.17.4" + implementation "com.amplifyframework:aws-api:1.17.7" + implementation "com.amplifyframework:aws-api-appsync:1.17.7" testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' diff --git a/packages/amplify_auth_cognito/android/build.gradle b/packages/amplify_auth_cognito/android/build.gradle index ca99b6a29c..12cf9c5d78 100644 --- a/packages/amplify_auth_cognito/android/build.gradle +++ b/packages/amplify_auth_cognito/android/build.gradle @@ -61,7 +61,7 @@ android { dependencies { api amplifyCore implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.amplifyframework:aws-auth-cognito:1.17.4' + implementation 'com.amplifyframework:aws-auth-cognito:1.17.7' testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-inline:3.1.0' diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AuthCognito.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AuthCognito.kt index d266757192..4089d003eb 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AuthCognito.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AuthCognito.kt @@ -42,6 +42,8 @@ import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterSignInWithWebUIRe import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterFetchUserAttributesResult import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributeRequest import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributeResult +import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributesRequest +import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributesResult import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterConfirmUserAttributeRequest import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterResendUserAttributeConfirmationCodeRequest import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterResendUserAttributeConfirmationCodeResult @@ -58,6 +60,7 @@ import com.amplifyframework.auth.result.AuthSignInResult import com.amplifyframework.auth.result.AuthSignUpResult import com.amplifyframework.auth.result.AuthUpdateAttributeResult import com.amplifyframework.auth.AuthUserAttribute +import com.amplifyframework.auth.AuthUserAttributeKey import com.amplifyframework.auth.AuthCodeDeliveryDetails import com.amplifyframework.core.Amplify import io.flutter.embedding.engine.plugins.FlutterPlugin @@ -173,6 +176,7 @@ public class AuthCognito : FlutterPlugin, ActivityAware, MethodCallHandler, Plug "fetchUserAttributes" -> onFetchUserAttributes(result) "signInWithWebUI" -> onSignInWithWebUI(result, data) "updateUserAttribute" -> onUpdateUserAttribute(result, data) + "updateUserAttributes" -> onUpdateUserAttributes(result, data) "confirmUserAttribute" -> onConfirmUserAttribute(result, data) "resendUserAttributeConfirmationCode" -> onResendUserAttributeConfirmationCode(result, data) else -> result.notImplemented() @@ -449,6 +453,20 @@ public class AuthCognito : FlutterPlugin, ActivityAware, MethodCallHandler, Plug } } + private fun onUpdateUserAttributes (@NonNull flutterResult: Result, @NonNull request: HashMap) { + try { + FlutterUpdateUserAttributesRequest.validate(request) + var req = FlutterUpdateUserAttributesRequest(request) + Amplify.Auth.updateUserAttributes( + req.attributes, + { result -> prepareUpdateUserAttributesResult(flutterResult, result) }, + { error -> errorHandler.handleAuthError(flutterResult, error) } + ); + } catch (e: Exception) { + errorHandler.prepareGenericException(flutterResult, e) + } + } + private fun onConfirmUserAttribute (@NonNull flutterResult: Result, @NonNull request: HashMap) { try { FlutterConfirmUserAttributeRequest.validate(request) @@ -554,6 +572,13 @@ public class AuthCognito : FlutterPlugin, ActivityAware, MethodCallHandler, Plug } } + fun prepareUpdateUserAttributesResult(@NonNull flutterResult: Result, @NonNull result: Map) { + var updateUserAttributesResult = FlutterUpdateUserAttributesResult(result); + Handler (Looper.getMainLooper()).post { + flutterResult.success(updateUserAttributesResult.toValueMap()); + } + } + fun prepareConfirmUserAttributeResult(@NonNull flutterResult: Result) { var parsedResult = mutableMapOf(); Handler (Looper.getMainLooper()).post { diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterConfirmUserAttributeRequest.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterConfirmUserAttributeRequest.kt index 1f50aaaa4a..0dec811216 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterConfirmUserAttributeRequest.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterConfirmUserAttributeRequest.kt @@ -15,6 +15,7 @@ package com.amazonaws.amplify.amplify_auth_cognito.types +import com.amazonaws.amplify.amplify_auth_cognito.utils.createAuthUserAttributeKey import com.amplifyframework.auth.AuthUserAttributeKey import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterResendUserAttributeConfirmationCodeRequest.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterResendUserAttributeConfirmationCodeRequest.kt index c41b8e0bab..34477d2d3a 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterResendUserAttributeConfirmationCodeRequest.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterResendUserAttributeConfirmationCodeRequest.kt @@ -15,6 +15,7 @@ package com.amazonaws.amplify.amplify_auth_cognito.types +import com.amazonaws.amplify.amplify_auth_cognito.utils.createAuthUserAttributeKey import com.amplifyframework.auth.AuthUserAttributeKey import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterSignUpRequest.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterSignUpRequest.kt index aa831c9d0a..932942e2f7 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterSignUpRequest.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterSignUpRequest.kt @@ -18,6 +18,7 @@ package com.amazonaws.amplify.amplify_auth_cognito.types import androidx.annotation.NonNull +import com.amazonaws.amplify.amplify_auth_cognito.utils.createAuthUserAttribute import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException import com.amplifyframework.AmplifyException diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeRequest.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeRequest.kt index 52e1db9cc3..054ca99541 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeRequest.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeRequest.kt @@ -16,6 +16,8 @@ package com.amazonaws.amplify.amplify_auth_cognito.types import androidx.annotation.NonNull +import com.amazonaws.amplify.amplify_auth_cognito.utils.createAuthUserAttribute +import com.amazonaws.amplify.amplify_auth_cognito.utils.validateUserAttribute import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException import com.amplifyframework.auth.AuthUserAttribute @@ -33,21 +35,14 @@ data class FlutterUpdateUserAttributeRequest(val map: HashMap) { companion object { private const val validationErrorMessage: String = "UpdateUserAttributeRequest Request malformed." - fun validate(req : HashMap?) { + fun validate(req: HashMap?) { if (req == null) { throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("request map")) } else if (!req.containsKey("attribute") || req["attribute"] !is HashMap<*, *>) { - throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format( "attribute" )) + throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("attribute")) } else { val attribute = req["attribute"] as HashMap<*, *>; - if (!attribute.containsKey("value")) { - throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format( "value" )) - } else if (!attribute.containsKey("userAttributeKey")) { - throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format( "userAttributeKey" )) - } else if (attribute["value"] !is String) { - // Android SDK expects a string for user attr values, regardless of the configuration in cognito - throw InvalidRequestException(validationErrorMessage, "Attribute value is not a String.") - } + validateUserAttribute(attribute, validationErrorMessage) } } } diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeResult.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeResult.kt index c52603bae5..4c48f8bbbd 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeResult.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributeResult.kt @@ -15,29 +15,13 @@ package com.amazonaws.amplify.amplify_auth_cognito.types +import com.amazonaws.amplify.amplify_auth_cognito.utils.serializeAuthUpdateAttributeResult import com.amplifyframework.auth.result.AuthUpdateAttributeResult -import com.google.gson.Gson data class FlutterUpdateUserAttributeResult(private val raw: AuthUpdateAttributeResult) { - val isUpdated: Boolean = raw.isUpdated - val nextStep: Map = setNextStep(); - - private fun setNextStep(): Map { - return mapOf( - "updateAttributeStep" to raw.nextStep.updateAttributeStep.toString(), - "additionalInfo" to Gson().toJson(raw.nextStep.additionalInfo), - "codeDeliveryDetails" to mapOf( - "destination" to (raw.nextStep.codeDeliveryDetails?.destination ?: ""), - "deliveryMedium" to (raw.nextStep.codeDeliveryDetails?.deliveryMedium?.name ?: ""), - "attributeName" to (raw.nextStep.codeDeliveryDetails?.attributeName ?: "") - ) - ) - } + val result: AuthUpdateAttributeResult = raw; fun toValueMap(): Map { - return mapOf( - "isUpdated" to this.isUpdated, - "nextStep" to this.nextStep - ) + return serializeAuthUpdateAttributeResult(result) } } diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesRequest.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesRequest.kt new file mode 100644 index 0000000000..18cf1ea755 --- /dev/null +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesRequest.kt @@ -0,0 +1,46 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazonaws.amplify.amplify_auth_cognito.types + +import com.amazonaws.amplify.amplify_auth_cognito.utils.createAuthUserAttribute +import com.amazonaws.amplify.amplify_auth_cognito.utils.validateUserAttribute +import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages +import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException +import com.amplifyframework.auth.AuthUserAttribute + +data class FlutterUpdateUserAttributesRequest(val map: HashMap) { + + val attributes: List = (map["attributes"] as List>) + .map { createAuthUserAttribute(it["userAttributeKey"] as String, it["value"] as String) } + + companion object { + private const val validationErrorMessage: String = "UpdateUserAttributesRequest Request malformed." + fun validate(req: HashMap?) { + if (req == null) { + throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("request map")) + } else if (!req.containsKey("attributes") || req["attributes"] !is List<*>) { + throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("attributes")) + } else { + val attributes = req["attributes"] as List> + if (attributes.isEmpty()) { + throw InvalidRequestException(validationErrorMessage, "The request must have at least one attribute.") + } else { + attributes.forEach { validateUserAttribute(it, validationErrorMessage) } + } + } + } + } +} diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesResult.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesResult.kt new file mode 100644 index 0000000000..ea7142b484 --- /dev/null +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FlutterUpdateUserAttributesResult.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazonaws.amplify.amplify_auth_cognito.types + +import com.amazonaws.amplify.amplify_auth_cognito.utils.serializeAuthUpdateAttributeResult +import com.amplifyframework.auth.AuthUserAttributeKey +import com.amplifyframework.auth.result.AuthUpdateAttributeResult + +data class FlutterUpdateUserAttributesResult(private val raw: Map) { + val attributes: Map = raw; + + fun toValueMap(): Map { + return attributes.entries.associate { + it.key.keyString to serializeAuthUpdateAttributeResult(it.value) + } + } +} diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/AuthCodeDeliveryDetailsSerialization.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/AuthCodeDeliveryDetailsSerialization.kt new file mode 100644 index 0000000000..a0ae150202 --- /dev/null +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/AuthCodeDeliveryDetailsSerialization.kt @@ -0,0 +1,28 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + + +package com.amazonaws.amplify.amplify_auth_cognito.utils + +import com.amplifyframework.auth.AuthCodeDeliveryDetails + +fun serializeAuthCodeDeliveryDetails(deliveryDetails: AuthCodeDeliveryDetails?): Map { + return mapOf( + "destination" to (deliveryDetails?.destination ?: ""), + "deliveryMedium" to (deliveryDetails?.deliveryMedium?.name + ?: ""), + "attributeName" to (deliveryDetails?.attributeName ?: "") + ) +} diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FormatUserAttribute.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeDeserialization.kt similarity index 97% rename from packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FormatUserAttribute.kt rename to packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeDeserialization.kt index 46900e2ccf..242c02d894 100644 --- a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/types/FormatUserAttribute.kt +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeDeserialization.kt @@ -13,7 +13,7 @@ * permissions and limitations under the License. */ -package com.amazonaws.amplify.amplify_auth_cognito.types +package com.amazonaws.amplify.amplify_auth_cognito.utils import com.amplifyframework.auth.AuthUserAttribute import com.amplifyframework.auth.AuthUserAttributeKey diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeSerialization.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeSerialization.kt new file mode 100644 index 0000000000..d475ab5bf9 --- /dev/null +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeSerialization.kt @@ -0,0 +1,35 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazonaws.amplify.amplify_auth_cognito.utils + +import com.amplifyframework.auth.result.AuthUpdateAttributeResult +import com.amplifyframework.auth.result.step.AuthNextUpdateAttributeStep +import com.google.gson.Gson + +fun serializeAuthUpdateAttributeResult(result: AuthUpdateAttributeResult): Map { + return mapOf( + "isUpdated" to result.isUpdated, + "nextStep" to serializeAuthUpdateAttributeStep(result.nextStep) + ) +} + +fun serializeAuthUpdateAttributeStep(nextStep: AuthNextUpdateAttributeStep): Map { + return mapOf( + "updateAttributeStep" to nextStep.updateAttributeStep.toString(), + "additionalInfo" to Gson().toJson(nextStep.additionalInfo), + "codeDeliveryDetails" to serializeAuthCodeDeliveryDetails(nextStep.codeDeliveryDetails) + ) +} diff --git a/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeValidation.kt b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeValidation.kt new file mode 100644 index 0000000000..9f9be2f0b2 --- /dev/null +++ b/packages/amplify_auth_cognito/android/src/main/kotlin/com/amazonaws/amplify/amplify_auth_cognito/utils/UserAttributeValidation.kt @@ -0,0 +1,30 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.amazonaws.amplify.amplify_auth_cognito.utils + +import com.amazonaws.amplify.amplify_core.exception.ExceptionMessages +import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException + +fun validateUserAttribute(attribute: HashMap<*, *>, validationErrorMessage: String) { + if (!attribute.containsKey("value")) { + throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("value")) + } else if (!attribute.containsKey("userAttributeKey")) { + throw InvalidRequestException(validationErrorMessage, ExceptionMessages.missingAttribute.format("userAttributeKey")) + } else if (attribute["value"] !is String) { + // Android SDK expects a string for user attr values, regardless of the configuration in cognito + throw InvalidRequestException(validationErrorMessage, "Attribute value is not a String.") + } +} diff --git a/packages/amplify_auth_cognito/android/src/test/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AmplifyAuthCognitoPluginTest.kt b/packages/amplify_auth_cognito/android/src/test/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AmplifyAuthCognitoPluginTest.kt index 5a1787e506..5cfcc5e153 100644 --- a/packages/amplify_auth_cognito/android/src/test/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AmplifyAuthCognitoPluginTest.kt +++ b/packages/amplify_auth_cognito/android/src/test/kotlin/com/amazonaws/amplify/amplify_auth_cognito/AmplifyAuthCognitoPluginTest.kt @@ -19,6 +19,7 @@ import android.app.Activity import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterConfirmUserAttributeRequest import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterResendUserAttributeConfirmationCodeRequest import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributeRequest +import com.amazonaws.amplify.amplify_auth_cognito.types.FlutterUpdateUserAttributesRequest import com.amazonaws.amplify.amplify_core.exception.InvalidRequestException import com.amazonaws.auth.AWSCredentials import com.amazonaws.auth.BasicAWSCredentials @@ -66,6 +67,7 @@ class AmplifyAuthCognitoPluginTest { private val signInStep = AuthNextSignInStep(AuthSignInStep.CONFIRM_SIGN_IN_WITH_SMS_MFA_CODE, emptyMap(), codeDeliveryDetails) private val resetStep = AuthNextResetPasswordStep(AuthResetPasswordStep.CONFIRM_RESET_PASSWORD_WITH_CODE, emptyMap(), codeDeliveryDetails) private val updateAttributeStep = AuthNextUpdateAttributeStep(AuthUpdateAttributeStep.CONFIRM_ATTRIBUTE_WITH_CODE, emptyMap(), codeDeliveryDetails) + private val updateAttributeStepWithoutConfirmation = AuthNextUpdateAttributeStep(AuthUpdateAttributeStep.DONE, emptyMap(), null) private val mockSignUpResult = AuthSignUpResult(false, signUpStep, null) private val mockSignInResult = AuthSignInResult(false, signInStep) private val mockResetPasswordResult = AuthResetPasswordResult(false, resetStep) @@ -621,6 +623,133 @@ class AmplifyAuthCognitoPluginTest { } } + @Test + fun updateUserAttributes_returnsSuccess() { + // Arrange + doAnswer { invocation: InvocationOnMock -> + plugin.prepareUpdateUserAttributesResult(mockResult, mapOf( + AuthUserAttributeKey.email() to AuthUpdateAttributeResult(true, updateAttributeStep), + AuthUserAttributeKey.name() to AuthUpdateAttributeResult(true, updateAttributeStepWithoutConfirmation) + )) + null as Void? + }.`when`(mockAuth).updateUserAttributes(any(), ArgumentMatchers.any>>(), ArgumentMatchers.any>()) + val emailAttribute = hashMapOf( + "userAttributeKey" to "email", + "value" to "test@test.com" + ) + val usernameAttribute = hashMapOf( + "userAttributeKey" to "name", + "value" to "testname" + ) + val data: HashMap<*, *> = hashMapOf( + "attributes" to listOf( + emailAttribute, + usernameAttribute + ) + ) + val arguments = hashMapOf("data" to data) + val call = MethodCall("updateUserAttributes", arguments) + val res = mapOf( + "email" to mapOf( + "isUpdated" to true, + "nextStep" to mapOf( + "updateAttributeStep" to "CONFIRM_ATTRIBUTE_WITH_CODE", + "additionalInfo" to "{}", + "codeDeliveryDetails" to mapOf( + "destination" to "test@test.com", + "deliveryMedium" to AuthCodeDeliveryDetails.DeliveryMedium.EMAIL.name, + "attributeName" to "email" + ) + ) + ), + "name" to mapOf( + "isUpdated" to true, + "nextStep" to mapOf( + "updateAttributeStep" to "DONE", + "additionalInfo" to "{}", + "codeDeliveryDetails" to mapOf( + "destination" to "", + "deliveryMedium" to "", + "attributeName" to "" + ) + ) + ) + ) + + // Act + plugin.onMethodCall(call, mockResult) + + // Assert + verify(mockResult, times(1)).success(res); + } + + @Test() + fun updateUserAttributes_validation() { + var attributeOne: HashMap + var attributeTwo: HashMap + var attributes: List + var data: HashMap + + // Throws an exception with no attributes + data = hashMapOf( + "foo" to "bar" + ) + assertThrows(InvalidRequestException::class.java) { + FlutterUpdateUserAttributesRequest.validate(data) + } + + // Throws an exception with no userAttributeKey + attributeOne = hashMapOf( + "value" to "custom attribute value" + ) + attributeTwo = hashMapOf( + "userAttributeKey" to "my_custom_attribute_2", + "value" to "custom attribute value" + ) + attributes = listOf(attributeOne, attributeTwo) + data = hashMapOf( + "attributes" to attributes + ) + assertThrows(InvalidRequestException::class.java) { + FlutterUpdateUserAttributesRequest.validate(data) + } + + // Throws an exception with no value + attributeOne = hashMapOf( + "userAttributeKey" to "my_custom_attribute" + ) + attributeTwo = hashMapOf( + "userAttributeKey" to "my_custom_attribute_2", + "value" to "custom attribute value" + ) + attributes = listOf(attributeOne, attributeTwo) + data = hashMapOf( + "attributes" to attributes + ) + assertThrows(InvalidRequestException::class.java) { + FlutterUpdateUserAttributesRequest.validate(data) + } + + // Does not throw an exception with valid params + attributeOne = hashMapOf( + "userAttributeKey" to "my_custom_attribute", + "value" to "custom attribute value" + ) + attributeTwo = hashMapOf( + "userAttributeKey" to "my_custom_attribute_2", + "value" to "custom attribute value" + ) + attributes = listOf(attributeOne, attributeTwo) + data = hashMapOf( + "attributes" to attributes + ) + try { + FlutterUpdateUserAttributesRequest.validate(data) + } catch (e: Exception) { + fail("Expected no exception to be thrown with valid data") + } + } + @Test fun confirmUserAttribute_returnsSuccess() { // Arrange diff --git a/packages/amplify_auth_cognito/example/ios/unit_tests/amplify_auth_cognito_tests.swift b/packages/amplify_auth_cognito/example/ios/unit_tests/amplify_auth_cognito_tests.swift index 79dcbdf3bc..d3da311a2d 100644 --- a/packages/amplify_auth_cognito/example/ios/unit_tests/amplify_auth_cognito_tests.swift +++ b/packages/amplify_auth_cognito/example/ios/unit_tests/amplify_auth_cognito_tests.swift @@ -25,6 +25,7 @@ import AWSMobileClient var _data: NSMutableDictionary = [:] var _args: Dictionary = [:] var _attributes: Dictionary = [:] +var _attributeArray: Array> = [] var _attribute: Dictionary = [:] var _options: Dictionary = [:] let _username: String = "testuser" @@ -1469,8 +1470,11 @@ class amplify_auth_cognito_tests: XCTestCase { let call = FlutterMethodCall(methodName: "updateUserAttribute", arguments: _args) plugin.handle(call, result: {(result)->Void in if let res = result as? FlutterUpdateUserAttributeResult { - XCTAssertEqual( "DONE", res.updateAttributeStep) - XCTAssertEqual( true, res.isUpdated) + let isUpdated = res.toJSON()["isUpdated"] as! Bool + let nextStep = res.toJSON()["nextStep"] as! Dictionary + let updateAttributeStep = nextStep["updateAttributeStep"] as! String + XCTAssertEqual( true, isUpdated) + XCTAssertEqual( "DONE", updateAttributeStep) } else { XCTFail() } @@ -1500,8 +1504,12 @@ class amplify_auth_cognito_tests: XCTestCase { let call = FlutterMethodCall(methodName: "updateUserAttribute", arguments: _args) plugin.handle(call, result: {(result)->Void in if let res = result as? FlutterUpdateUserAttributeResult { - XCTAssertEqual( "DONE", res.updateAttributeStep) - XCTAssertEqual( true, res.isUpdated) + let isUpdated = res.toJSON()["isUpdated"] as! Bool + let nextStep = res.toJSON()["nextStep"] as! Dictionary + let updateAttributeStep = nextStep["updateAttributeStep"] as! String + XCTAssertEqual( true, isUpdated) + XCTAssertEqual( "DONE", updateAttributeStep) + } else { XCTFail() } @@ -1581,6 +1589,154 @@ class amplify_auth_cognito_tests: XCTestCase { }) } + func test_updateUserAttributes() { + + class UpdateUserAttributesMock: AuthCognitoBridge { + override func onUpdateUserAttributes(flutterResult: @escaping FlutterResult, request: FlutterUpdateUserAttributesRequest){ + let updateUserAttributesSuccess = [ + AuthUserAttributeKey.email: AuthUpdateAttributeResult(isUpdated: true, nextStep: AuthUpdateAttributeStep.done), + AuthUserAttributeKey.name: AuthUpdateAttributeResult(isUpdated: true, nextStep: AuthUpdateAttributeStep.done) + ] + let updateUserAttributesRes = Result,AuthError>.success(updateUserAttributesSuccess) + let updateUserAttributesData = FlutterUpdateUserAttributesResult(res: updateUserAttributesRes) + flutterResult(updateUserAttributesData) + } + } + + plugin = SwiftAuthCognito.init(cognito: UpdateUserAttributesMock()) + + _attributeArray = [ + [ + "userAttributeKey" : "email", + "value": _email + ], + [ + "userAttributeKey" : "name", + "value": "testname" + ] + ] + _data = [ + "attributes": _attributeArray, + ] + _args = ["data": _data] + let call = FlutterMethodCall(methodName: "updateUserAttributes", arguments: _args) + plugin.handle(call, result: {(result)->Void in + if let res = result as? FlutterUpdateUserAttributesResult { + let jsonRes = res.toJSON() + let emailRes = jsonRes["email"] as! Dictionary + let emailNextStep = emailRes["nextStep"] as! Dictionary + let nameRes = jsonRes["name"] as! Dictionary + let nameNextStep = emailRes["nextStep"] as! Dictionary + XCTAssertEqual( true, emailRes["isUpdated"] as! Bool) + XCTAssertEqual( "DONE", emailNextStep["updateAttributeStep"] as! String) + XCTAssertEqual( true, nameRes["isUpdated"] as! Bool) + XCTAssertEqual( "DONE", nameNextStep["updateAttributeStep"] as! String) + } else { + XCTFail() + } + }) + } + + func test_updateUserAttributesValidation() { + var rawAttributes: Array> + var rawAttributeOne: Dictionary + var rawAttributeTwo: Dictionary + var rawData: NSMutableDictionary + + // Throws an error with no attributes + rawData = [:] + XCTAssertThrowsError(try FlutterUpdateUserAttributesRequest.validate(dict: rawData)) + + // Throws an error with no attribute key + rawAttributeOne = [ + "value": _email + ] + rawAttributeTwo = [ + "userAttributeKey": "name", + "value": "testname" + ] + rawAttributes = [rawAttributeOne, rawAttributeTwo] + rawData = ["attributes": rawAttributes] + XCTAssertThrowsError(try FlutterUpdateUserAttributesRequest.validate(dict: rawData)) + + // Throws an error with no attribute value + rawAttributeOne = [ + "userAttributeKey": "email", + ] + rawAttributeTwo = [ + "userAttributeKey": "name", + "value": "testname" + ] + rawAttributes = [rawAttributeOne, rawAttributeTwo] + rawData = ["attributes": rawAttributes] + XCTAssertThrowsError(try FlutterUpdateUserAttributesRequest.validate(dict: rawData)) + + // Throws an error with non string value + rawAttributeOne = [ + "userAttributeKey": "email", + "value": 1 + ] + rawAttributeTwo = [ + "userAttributeKey": "name", + "value": "testname" + ] + rawAttributes = [rawAttributeOne, rawAttributeTwo] + rawData = ["attributes": rawAttributes] + XCTAssertThrowsError(try FlutterUpdateUserAttributesRequest.validate(dict: rawData)) + + // Does not throw an error with valid parameters + rawAttributeOne = [ + "userAttributeKey": "email", + "value": _email + ] + rawAttributeTwo = [ + "userAttributeKey": "name", + "value": "testname" + ] + rawAttributes = [rawAttributeOne, rawAttributeTwo] + rawData = ["attributes": rawAttributes] + XCTAssertNoThrow(try FlutterUpdateUserAttributesRequest.validate(dict: rawData)) + } + + func test_updateUserAttributesError() { + + class UpdateUserAttributesMock: AuthCognitoBridge { + override func onUpdateUserAttributes(flutterResult: @escaping FlutterResult, request: FlutterUpdateUserAttributesRequest) { + let authError = AuthError.service("Invalid parameter", MockErrorConstants.invalidParameterError, AWSCognitoAuthError.invalidParameter) + errorHandler.handleAuthError(authError: authError, flutterResult: flutterResult) + } + } + + plugin = SwiftAuthCognito.init(cognito: UpdateUserAttributesMock()) + + _attributeArray = [ + [ + "userAttributeKey" : "email", + "value": _email + ], + [ + "userAttributeKey" : "name", + "value": "testname" + ] + ] + _data = [ + "attributes": _attributeArray, + ] + _args = ["data": _data] + let call = FlutterMethodCall(methodName: "updateUserAttributes", arguments: _args) + plugin.handle(call, result: {(result)->Void in + if let res = result as? FlutterError { + let details = res.details as? Dictionary + XCTAssertEqual( "InvalidParameterException", res.code ) + XCTAssert( ((details?["underlyingException"])! as String).contains(MockErrorTemplate)) + XCTAssertEqual( MockErrorConstants.invalidParameterError, details?["recoverySuggestion"]) + XCTAssertEqual( "Invalid parameter", details?["message"]) + } else { + XCTFail() + } + }) + } + func test_confirmUserAttribute() { class ConfirmUserAttributeMock: AuthCognitoBridge { diff --git a/packages/amplify_auth_cognito/example/lib/Widgets/UpdateUserAttributes.dart b/packages/amplify_auth_cognito/example/lib/Widgets/UpdateUserAttributes.dart new file mode 100644 index 0000000000..301306a821 --- /dev/null +++ b/packages/amplify_auth_cognito/example/lib/Widgets/UpdateUserAttributes.dart @@ -0,0 +1,166 @@ +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; +import 'package:amplify_core/amplify_core.dart'; +import 'package:amplify_flutter/amplify.dart'; +import 'package:flutter/material.dart'; + +// ignore_for_file: public_member_api_docs + +class _UserAttributeController { + TextEditingController keyController; + TextEditingController valueController; + _UserAttributeController({String keyValue}) { + keyController = TextEditingController(text: keyValue); + valueController = TextEditingController(); + } +} + +class UpdateUserAttributesWidget extends StatefulWidget { + UpdateUserAttributesWidget(); + + @override + _UpdateUserAttributesWidgetState createState() => + _UpdateUserAttributesWidgetState(); +} + +class _UpdateUserAttributesWidgetState + extends State { + final List<_UserAttributeController> _userAttributeControllers = [ + _UserAttributeController(keyValue: 'name'), + _UserAttributeController(keyValue: 'preferred_username'), + ]; + + final _formKey = GlobalKey(); + + void _showSuccess(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.green[800], content: Text(message))); + } + + void _showInfo(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.blue[800], content: Text(message))); + } + + void _showError(String message) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(backgroundColor: Colors.red[900], content: Text(message))); + } + + void _updateAttributes() async { + if (_formKey.currentState.validate()) { + try { + var attributes = _userAttributeControllers + .map( + (controller) => AuthUserAttribute( + userAttributeKey: + controller.keyController.text.replaceAll('custom:', ''), + value: controller.valueController.text, + ), + ) + .toList(); + var res = + await Amplify.Auth.updateUserAttributes(attributes: attributes); + var attributesWithConfirmation = res.entries + .where((element) => + element.value.nextStep.updateAttributeStep == + 'CONFIRM_ATTRIBUTE_WITH_CODE') + .map((element) => element.key) + .toList(); + if (attributesWithConfirmation.isNotEmpty) { + _showInfo( + 'Confirmation Code sent for attributes: $attributesWithConfirmation'); + } else { + _showSuccess('Attributes Updated Successfully'); + } + } on AmplifyException catch (e) { + _showError(e.message); + } + } + } + + void _addAttribute() { + setState(() { + _userAttributeControllers.add(_UserAttributeController()); + }); + } + + void _removeAttribute(_UserAttributeController controller) { + setState(() { + _userAttributeControllers.remove(controller); + }); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text('Update Attribute'), + ), + body: Padding( + padding: const EdgeInsets.all(8), + child: Form( + key: _formKey, + child: ListView( + children: [ + ..._userAttributeControllers.map((element) { + return Card( + child: Stack(children: [ + Padding( + padding: + const EdgeInsets.only(left: 16, bottom: 24, right: 16), + child: Column(children: [ + TextFormField( + controller: element.keyController, + decoration: const InputDecoration( + labelText: 'Attribute Name', + ), + validator: (String value) { + if (value.isEmpty) { + return 'An Attribute name is required.'; + } + }, + ), + TextFormField( + controller: element.valueController, + decoration: const InputDecoration( + labelText: 'Attribute Value', + ), + validator: (String value) { + if (value.isEmpty) { + return 'An Attribute value is required.'; + } + }, + ), + ]), + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + IconButton( + padding: EdgeInsets.zero, + icon: Icon( + Icons.close, + size: 18, + ), + onPressed: () => _removeAttribute(element), + ) + ], + ), + ])); + }), + const SizedBox(height: 12), + ElevatedButton( + onPressed: _updateAttributes, + child: const Text('Update Attributes'), + ), + TextButton( + onPressed: _addAttribute, + child: Text('Add New Attribute'), + ), + ], + ), + ), + ), + ); + } +} diff --git a/packages/amplify_auth_cognito/example/lib/Widgets/ViewUserAttributes.dart b/packages/amplify_auth_cognito/example/lib/Widgets/ViewUserAttributes.dart index cc7c08bf13..520a8d82ee 100644 --- a/packages/amplify_auth_cognito/example/lib/Widgets/ViewUserAttributes.dart +++ b/packages/amplify_auth_cognito/example/lib/Widgets/ViewUserAttributes.dart @@ -2,6 +2,7 @@ import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_flutter/amplify.dart'; import 'package:flutter/material.dart'; +import 'UpdateUserAttributes.dart'; import 'UpdateUserAttribute.dart'; // ignore: public_member_api_docs @@ -57,6 +58,19 @@ class _ViewUserAttributesState extends State { appBar: AppBar( title: Text('User Attributes'), actions: [ + TextButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => UpdateUserAttributesWidget(), + ), + ); + }, + child: Text( + 'Bulk Update', + style: TextStyle(color: Colors.white), + ), + ), IconButton( icon: Icon(Icons.refresh), onPressed: () => _fetchAttributes(isRefresh: true), diff --git a/packages/amplify_auth_cognito/ios/Classes/AuthCognitoBridge.swift b/packages/amplify_auth_cognito/ios/Classes/AuthCognitoBridge.swift index 754f800887..b62ccd0866 100644 --- a/packages/amplify_auth_cognito/ios/Classes/AuthCognitoBridge.swift +++ b/packages/amplify_auth_cognito/ios/Classes/AuthCognitoBridge.swift @@ -228,6 +228,18 @@ class AuthCognitoBridge { } } + func onUpdateUserAttributes(flutterResult: @escaping FlutterResult, request: FlutterUpdateUserAttributesRequest) { + Amplify.Auth.update(userAttributes: request.attributes) { response in + switch response { + case .success: + let updateAttributesData = FlutterUpdateUserAttributesResult(res: response) + flutterResult(updateAttributesData.toJSON()) + case .failure(let error): + self.errorHandler.handleAuthError(authError: error, flutterResult: flutterResult) + } + } + } + func onConfirmUserAttribute(flutterResult: @escaping FlutterResult, request: FlutterConfirmUserAttributeRequest) { Amplify.Auth.confirm(userAttribute: request.userAttributeKey, confirmationCode: request.confirmationCode) { response in switch response { diff --git a/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributeResult.swift b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributeResult.swift index d8ffec6fd8..1fd6b98859 100644 --- a/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributeResult.swift +++ b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributeResult.swift @@ -18,99 +18,18 @@ import Foundation import Amplify struct FlutterUpdateUserAttributeResult { - var isUpdated: Bool - var updateAttributeStep: String; - var additionalInfo: [String: String] - var codeDeliveryDetails: [String: String] + var result: AuthUpdateAttributeResult? init(res: AmplifyOperation.OperationResult){ - self.isUpdated = isComplete(res: res) - self.updateAttributeStep = setState(res: res) - self.additionalInfo = setAdditionalInfo(res: res) - self.codeDeliveryDetails = setCodeDeliveryDetails(res: res) - } - - func toJSON() -> Dictionary { - return [ - "isUpdated": self.isUpdated, - "nextStep": [ - "updateAttributeStep": self.updateAttributeStep, - "additionalInfo": self.additionalInfo, - "codeDeliveryDetails": self.codeDeliveryDetails - ] - ] - } -} - -func isComplete(res: AmplifyOperation.OperationResult) -> Bool { - var complete: Bool = false; - switch res { - case .success(let signUpResult): - complete = signUpResult.isUpdated - case .failure: - complete = false - } - return complete; -} - -func setCodeDeliveryDetails(res: AmplifyOperation.OperationResult) -> [String: String] { - var deliveryMap: [String: String] = [:] - switch res { - case .success(let updateResult): - if case let .confirmAttributeWithCode(deliveryDetails, _) = updateResult.nextStep { - if case let .email(e) = deliveryDetails.destination { - deliveryMap["destination"] = e! as String - deliveryMap["attributeName"] = "email" - deliveryMap["deliveryMedium"] = "EMAIL" - } - - if case let .phone(e) = deliveryDetails.destination { - deliveryMap["destination"] = e! as String - deliveryMap["attributeName"] = "phone" - } - - if case let .sms(e) = deliveryDetails.destination { - deliveryMap["destination"] = e! as String - deliveryMap["attributeName"] = "sms" - deliveryMap["deliveryMedium"] = "SMS" - } - - if case let .unknown(e) = deliveryDetails.destination { - deliveryMap["destination"] = e! as String - deliveryMap["attributeName"] = "unknown" - } - } + switch res { + case .success(let res): + self.result = res case .failure: - deliveryMap = [:] + self.result = nil + } } - return deliveryMap -} -func setAdditionalInfo(res: AmplifyOperation.OperationResult) -> [String: String] { - var infoMap: [String: String] = [:] - switch res { - case .success(let updateResult): - if case let .confirmAttributeWithCode(_, additionalInfo) = updateResult.nextStep { - infoMap = additionalInfo ?? [:] - } - case .failure: - infoMap = [:] - } - return infoMap -} - -func setState(res: AmplifyOperation.OperationResult) -> String { - let state: String = "ERROR" - switch res { - case .success(let updateResult): - if case .done = updateResult.nextStep { - return "DONE" - } - if case .confirmAttributeWithCode = updateResult.nextStep { - return "CONFIRM_ATTRIBUTE_WITH_CODE" - } - case .failure: - return "ERROR" + func toJSON() -> Dictionary { + return serializeAuthUpdateAttributeResult(result: self.result) } - return state } diff --git a/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesRequest.swift b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesRequest.swift new file mode 100644 index 0000000000..a29fc67031 --- /dev/null +++ b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesRequest.swift @@ -0,0 +1,41 @@ + +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify +import amplify_core + +struct FlutterUpdateUserAttributesRequest { + var attributes: Array + + init(dict: NSMutableDictionary) { + self.attributes = (dict["attributes"] as! Array>).map { + return createAuthUserAttribute(key: $0["userAttributeKey"]!, value: $0["value"]!); + } + } + + static func validate(dict: NSMutableDictionary) throws { + let validationErrorMessage = "UpdateUserAttributeRequest Request malformed." + if (dict["attributes"] == nil || !(dict["attributes"] is Array>)) { + throw InvalidRequestError.auth(comment: validationErrorMessage, suggestion: String(format: ErrorMessages.missingAttribute, "attributes")) + } else { + let attributes = dict["attributes"] as! Array>; + for attribute in attributes { + try validateUserAttribute(attribute: attribute, validationErrorMessage: validationErrorMessage) + } + } + } +} diff --git a/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesResult.swift b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesResult.swift new file mode 100644 index 0000000000..1533d0ce7e --- /dev/null +++ b/packages/amplify_auth_cognito/ios/Classes/FlutterUpdateUserAttributesResult.swift @@ -0,0 +1,35 @@ + +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify + +struct FlutterUpdateUserAttributesResult { + var attributes: Dictionary + + init(res: AmplifyOperation, AuthError>.OperationResult){ + switch res { + case .success(let resultMap): + self.attributes = resultMap + case .failure: + self.attributes = [:] + } + } + + func toJSON() -> Dictionary { + return Dictionary(uniqueKeysWithValues: self.attributes.map { key, value in (key.rawValue, serializeAuthUpdateAttributeResult(result: value))}) + } +} diff --git a/packages/amplify_auth_cognito/ios/Classes/SwiftAuthCognito.swift b/packages/amplify_auth_cognito/ios/Classes/SwiftAuthCognito.swift index 1c5fde3fcb..5b43124b2c 100644 --- a/packages/amplify_auth_cognito/ios/Classes/SwiftAuthCognito.swift +++ b/packages/amplify_auth_cognito/ios/Classes/SwiftAuthCognito.swift @@ -204,6 +204,14 @@ public class SwiftAuthCognito: NSObject, FlutterPlugin { } catch { self.errorHandler.prepareGenericException(flutterResult: result, error: error) } + case "updateUserAttributes": + do { + try FlutterUpdateUserAttributesRequest.validate(dict: data) + let request = FlutterUpdateUserAttributesRequest(dict: data) + cognito.onUpdateUserAttributes(flutterResult: result, request: request) + } catch { + self.errorHandler.prepareGenericException(flutterResult: result, error: error) + } case "confirmUserAttribute": do { try FlutterConfirmUserAttributeRequest.validate(dict: data) diff --git a/packages/amplify_auth_cognito/ios/Classes/Utils/AuthCodeDeliveryDetailsSerialization.swift b/packages/amplify_auth_cognito/ios/Classes/Utils/AuthCodeDeliveryDetailsSerialization.swift new file mode 100644 index 0000000000..4e046501c6 --- /dev/null +++ b/packages/amplify_auth_cognito/ios/Classes/Utils/AuthCodeDeliveryDetailsSerialization.swift @@ -0,0 +1,43 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify + +func serializeAuthCodeDeliveryDetails(deliveryDetails: AuthCodeDeliveryDetails) -> [String: String] { + var deliveryMap: [String: String] = [:] + if case let .email(e) = deliveryDetails.destination { + deliveryMap["destination"] = e! as String + deliveryMap["attributeName"] = "email" + deliveryMap["deliveryMedium"] = "EMAIL" + } + + if case let .phone(e) = deliveryDetails.destination { + deliveryMap["destination"] = e! as String + deliveryMap["attributeName"] = "phone" + } + + if case let .sms(e) = deliveryDetails.destination { + deliveryMap["destination"] = e! as String + deliveryMap["attributeName"] = "sms" + deliveryMap["deliveryMedium"] = "SMS" + } + + if case let .unknown(e) = deliveryDetails.destination { + deliveryMap["destination"] = e! as String + deliveryMap["attributeName"] = "unknown" + } + return deliveryMap +} diff --git a/packages/amplify_auth_cognito/ios/Classes/FormatUserAttribute.swift b/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeDeserialization.swift similarity index 100% rename from packages/amplify_auth_cognito/ios/Classes/FormatUserAttribute.swift rename to packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeDeserialization.swift diff --git a/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeSerialization.swift b/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeSerialization.swift new file mode 100644 index 0000000000..608de3277b --- /dev/null +++ b/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeSerialization.swift @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify + +func serializeAuthUpdateAttributeResult(result: AuthUpdateAttributeResult?) -> Dictionary { + return [ + "isUpdated": result?.isUpdated ?? false, + "nextStep": serializeAuthUpdateAttributeStep(nextStep: result?.nextStep) + ] +} + +private func serializeAuthUpdateAttributeStep(nextStep: AuthUpdateAttributeStep?) -> Dictionary { + let serializedUpdateAttributeStep = serializeUpdateAttributeStep(nextStep: nextStep) + var serializedAdditionalInfo: Dictionary = [:] + var serializedCodeDeliveryDetails: Dictionary = [:] + if case let .confirmAttributeWithCode(deliveryDetails, additionalInfo) = nextStep { + serializedAdditionalInfo = additionalInfo ?? [:] + serializedCodeDeliveryDetails = serializeAuthCodeDeliveryDetails(deliveryDetails: deliveryDetails) + } + return [ + "updateAttributeStep": serializedUpdateAttributeStep, + "additionalInfo": serializedAdditionalInfo, + "codeDeliveryDetails": serializedCodeDeliveryDetails + ] +} + +private func serializeUpdateAttributeStep(nextStep: AuthUpdateAttributeStep?) -> String { + if case .done = nextStep { + return "DONE" + } + if case .confirmAttributeWithCode = nextStep { + return "CONFIRM_ATTRIBUTE_WITH_CODE" + } + return "ERROR" +} diff --git a/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeValidation.swift b/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeValidation.swift new file mode 100644 index 0000000000..6d199f9479 --- /dev/null +++ b/packages/amplify_auth_cognito/ios/Classes/Utils/UserAttributeValidation.swift @@ -0,0 +1,29 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import Foundation +import Amplify +import amplify_core + +func validateUserAttribute(attribute: Dictionary, validationErrorMessage: String) throws { + if (attribute["userAttributeKey"] == nil) { + throw InvalidRequestError.auth(comment: validationErrorMessage, suggestion: String(format: ErrorMessages.missingAttribute, "userAttributeKey")) + } else if (attribute["value"] == nil) { + throw InvalidRequestError.auth(comment: validationErrorMessage, suggestion: String(format: ErrorMessages.missingAttribute, "value")) + } else if (!(attribute["value"] is String)) { + // iOS SDK expects a string for user attr values, regardless of the configuration in cognito + throw InvalidRequestError.auth(comment: validationErrorMessage, suggestion: "Attribute value is not a String.") + } +} diff --git a/packages/amplify_auth_cognito/lib/amplify_auth_cognito.dart b/packages/amplify_auth_cognito/lib/amplify_auth_cognito.dart index c18d8f3fed..2e8ae16a81 100644 --- a/packages/amplify_auth_cognito/lib/amplify_auth_cognito.dart +++ b/packages/amplify_auth_cognito/lib/amplify_auth_cognito.dart @@ -125,6 +125,12 @@ class AmplifyAuthCognito extends AuthPluginInterface { return res; } + Future> updateUserAttributes( + {@required UpdateUserAttributesRequest request}) async { + final res = await _instance.updateUserAttributes(request: request); + return res; + } + Future confirmUserAttribute( {@required ConfirmUserAttributeRequest request}) async { final res = await _instance.confirmUserAttribute(request: request); diff --git a/packages/amplify_auth_cognito/lib/method_channel_auth_cognito.dart b/packages/amplify_auth_cognito/lib/method_channel_auth_cognito.dart index f5db826d97..388be82879 100644 --- a/packages/amplify_auth_cognito/lib/method_channel_auth_cognito.dart +++ b/packages/amplify_auth_cognito/lib/method_channel_auth_cognito.dart @@ -313,6 +313,25 @@ class AmplifyAuthCognitoMethodChannel extends AmplifyAuthCognito { return res; } + @override + Future> updateUserAttributes( + {@required UpdateUserAttributesRequest request}) async { + Map res; + try { + final Map data = + await _channel.invokeMapMethod( + 'updateUserAttributes', + { + 'data': request.serializeAsMap(), + }, + ); + return _formatUpdateUserAttributesResponse(data); + } on PlatformException catch (e) { + castAndThrowPlatformException(e); + } + return res; + } + @override Future confirmUserAttribute( {@required ConfirmUserAttributeRequest request}) async { @@ -429,6 +448,14 @@ class AmplifyAuthCognitoMethodChannel extends AmplifyAuthCognito { : {})); } + Map _formatUpdateUserAttributesResponse( + Map res) { + return res.map((key, value) => MapEntry( + key, + _formatUpdateUserAttributeResponse( + Map.from(value)))); + } + ConfirmUserAttributeResult _formatConfirmUserAttributeResponse() { return ConfirmUserAttributeResult(); } diff --git a/packages/amplify_auth_cognito/test/amplify_auth_cognito_updateUserAttributes_test.dart b/packages/amplify_auth_cognito/test/amplify_auth_cognito_updateUserAttributes_test.dart new file mode 100644 index 0000000000..a39c677396 --- /dev/null +++ b/packages/amplify_auth_cognito/test/amplify_auth_cognito_updateUserAttributes_test.dart @@ -0,0 +1,109 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; + +void main() { + const MethodChannel authChannel = + MethodChannel('com.amazonaws.amplify/auth_cognito'); + + AmplifyAuthCognito auth = AmplifyAuthCognito(); + + TestWidgetsFlutterBinding.ensureInitialized(); + + int testCode = 0; + + final String exceptionMessage = "exception message"; + + setUp(() { + authChannel.setMockMethodCallHandler((MethodCall methodCall) async { + if (methodCall.method == "updateUserAttributes") { + switch (testCode) { + case 1: + return Map.of({ + "email": { + "isUpdated": true, + "nextStep": { + "updateAttributeStep": "DONE", + "codeDeliveryDetails": {"attributeName": "email"} + } + }, + "name": { + "isUpdated": true, + "nextStep": { + "updateAttributeStep": "DONE", + "codeDeliveryDetails": {"attributeName": "name"} + } + } + }); + case 2: + return throw PlatformException( + code: "UnknownException", + details: Map.from({"message": exceptionMessage}), + ); + } + } + }); + }); + + tearDown(() { + authChannel.setMockMethodCallHandler(null); + }); + + test('updateUserAttributes request returns a UpdateUserAttributesResult', + () async { + testCode = 1; + var res = await auth.updateUserAttributes( + request: UpdateUserAttributesRequest(attributes: [ + AuthUserAttribute(userAttributeKey: "email", value: "email@email.com"), + AuthUserAttribute(userAttributeKey: "name", value: "testname") + ]), + ); + expect(res, isInstanceOf>()); + }); + + test( + 'updateUserAttributes request nextStep casts to AuthNextUpdateAttributeStep and AuthNextStep', + () async { + testCode = 1; + var res = await auth.updateUserAttributes( + request: UpdateUserAttributesRequest(attributes: [ + AuthUserAttribute(userAttributeKey: "email", value: "email@email.com"), + AuthUserAttribute(userAttributeKey: "name", value: "testname") + ]), + ); + expect(res["email"].nextStep, isInstanceOf()); + }); + + test('updateUserAttributes thrown PlatFormException results in AuthError', + () async { + testCode = 2; + AuthException err; + try { + await auth.updateUserAttributes( + request: UpdateUserAttributesRequest(attributes: [ + AuthUserAttribute( + userAttributeKey: "email", value: "email@email.com"), + AuthUserAttribute(userAttributeKey: "name", value: "testname") + ]), + ); + } on AuthException catch (e) { + err = e; + } + expect(err.message, exceptionMessage); + }); +} diff --git a/packages/amplify_auth_plugin_interface/lib/amplify_auth_plugin_interface.dart b/packages/amplify_auth_plugin_interface/lib/amplify_auth_plugin_interface.dart index 63dcd398f7..96f3dbea68 100644 --- a/packages/amplify_auth_plugin_interface/lib/amplify_auth_plugin_interface.dart +++ b/packages/amplify_auth_plugin_interface/lib/amplify_auth_plugin_interface.dart @@ -97,6 +97,11 @@ abstract class AuthPluginInterface extends AmplifyPluginInterface { throw UnimplementedError('updateUserAttribute() has not been implemented.'); } + Future> updateUserAttributes( + {@required UpdateUserAttributesRequest request}) { + throw UnimplementedError('updateUserAttributes() has not been implemented.'); + } + Future confirmUserAttribute( {@required ConfirmUserAttributeRequest request}) { throw UnimplementedError( diff --git a/packages/amplify_auth_plugin_interface/lib/src/Session/UpdateUserAttributesRequest.dart b/packages/amplify_auth_plugin_interface/lib/src/Session/UpdateUserAttributesRequest.dart new file mode 100644 index 0000000000..0579bf4343 --- /dev/null +++ b/packages/amplify_auth_plugin_interface/lib/src/Session/UpdateUserAttributesRequest.dart @@ -0,0 +1,36 @@ +/* + * Copyright 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"). + * You may not use this file except in compliance with the License. + * A copy of the License is located at + * + * http://aws.amazon.com/apache2.0 + * + * or in the "license" file accompanying this file. This file is distributed + * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +import 'package:flutter/foundation.dart'; +import 'AuthUserAttribute.dart'; + +/// Encapsulates parameters for a update user attributes operation +class UpdateUserAttributesRequest { + /// The list of user attribute to update + final List attributes; + + /// Default constructor + UpdateUserAttributesRequest({ + @required this.attributes, + }); + + /// Serialize the object to a map for use over the method channel + Map serializeAsMap() { + final Map pendingRequest = {}; + pendingRequest['attributes'] = + attributes.map((attribute) => attribute.serializeAsMap()).toList(); + return pendingRequest; + } +} diff --git a/packages/amplify_auth_plugin_interface/lib/src/types.dart b/packages/amplify_auth_plugin_interface/lib/src/types.dart index b34ede1ac6..2e127d3d91 100644 --- a/packages/amplify_auth_plugin_interface/lib/src/types.dart +++ b/packages/amplify_auth_plugin_interface/lib/src/types.dart @@ -58,6 +58,7 @@ export 'Session/AuthUserAttributeOptions.dart'; export 'Session/AuthUserAttributeRequest.dart'; export 'Session/UpdateUserAttributeResult.dart'; export 'Session/UpdateUserAttributeRequest.dart'; +export 'Session/UpdateUserAttributesRequest.dart'; export 'Session/AuthNextUpdateAttributeStep.dart'; export 'Session/ConfirmUserAttributeRequest.dart'; export 'Session/ConfirmUserAttributeResult.dart'; diff --git a/packages/amplify_core/android/build.gradle b/packages/amplify_core/android/build.gradle index ee5141129d..b8e1c48817 100644 --- a/packages/amplify_core/android/build.gradle +++ b/packages/amplify_core/android/build.gradle @@ -61,7 +61,7 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.amplifyframework:core:1.17.4' + implementation 'com.amplifyframework:core:1.17.7' implementation 'com.google.code.gson:gson:2.8.6' testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' diff --git a/packages/amplify_datastore/android/build.gradle b/packages/amplify_datastore/android/build.gradle index 129617966c..764a3a0a29 100644 --- a/packages/amplify_datastore/android/build.gradle +++ b/packages/amplify_datastore/android/build.gradle @@ -57,8 +57,8 @@ android { dependencies { implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation "com.amplifyframework:aws-datastore:1.17.4" - implementation "com.amplifyframework:aws-api-appsync:1.17.4" + implementation "com.amplifyframework:aws-datastore:1.17.7" + implementation "com.amplifyframework:aws-api-appsync:1.17.7" testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' testImplementation 'org.mockito:mockito-inline:3.1.0' diff --git a/packages/amplify_flutter/android/build.gradle b/packages/amplify_flutter/android/build.gradle index 9bcc540833..c9edf56aa0 100644 --- a/packages/amplify_flutter/android/build.gradle +++ b/packages/amplify_flutter/android/build.gradle @@ -58,7 +58,7 @@ android { dependencies { api amplifyCore - implementation 'com.amplifyframework:core:1.17.4' + implementation 'com.amplifyframework:core:1.17.7' implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" testImplementation 'junit:junit:4.13' testImplementation 'org.mockito:mockito-core:3.1.0' @@ -66,5 +66,5 @@ dependencies { testImplementation 'androidx.test:core:1.3.0' testImplementation 'org.robolectric:robolectric:4.3.1' testImplementation 'com.google.code.gson:gson:2.8.6' - testImplementation 'com.amplifyframework:aws-auth-cognito:1.17.4' + testImplementation 'com.amplifyframework:aws-auth-cognito:1.17.7' } diff --git a/packages/amplify_flutter/lib/categories/amplify_auth_category.dart b/packages/amplify_flutter/lib/categories/amplify_auth_category.dart index 1e0c96c6fe..36ce7c7ee0 100644 --- a/packages/amplify_flutter/lib/categories/amplify_auth_category.dart +++ b/packages/amplify_flutter/lib/categories/amplify_auth_category.dart @@ -178,6 +178,16 @@ class AuthCategory { : throw _pluginNotAddedException("Auth"); } + /// Updates multiple user attributes and returns a map of [UpdateUserAttributeResult] + Future> updateUserAttributes({ + @required List attributes, + }) { + var request = UpdateUserAttributesRequest(attributes: attributes); + return plugins.length == 1 + ? plugins[0].updateUserAttributes(request: request) + : throw _pluginNotAddedException("Auth"); + } + /// Confirms a user attribute update and returns a [ConfirmUserAttributeResult] Future confirmUserAttribute({ @required String userAttributeKey, diff --git a/packages/amplify_storage_s3/android/build.gradle b/packages/amplify_storage_s3/android/build.gradle index a84e82caa5..45336da260 100644 --- a/packages/amplify_storage_s3/android/build.gradle +++ b/packages/amplify_storage_s3/android/build.gradle @@ -49,5 +49,5 @@ android { dependencies { api amplifyCore implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'com.amplifyframework:aws-storage-s3:1.17.4' + implementation 'com.amplifyframework:aws-storage-s3:1.17.7' }