Cognito: support for partial UpdateUserPoolClient calls
- Dominant language
- Java
- Stars
- 2.6k
- Forks
- 1k
- Avg merge
- 2d 9h
- Merged PRs (30d)
- 51
Description
### Describe the feature
The `UpdateUserPoolClient` should support partial updates instead of having to provide every single parameter in the request.
### Use Case
Currently, the process to update a user pool client through the SDK is very cumbersome. For example, if we only want to update the `callbackURLs` on an existing user pool client, we have to pass every single property with the *existing* value to the `UpdateUserPoolClientRequest`. This is because Cognito [doesn't support partial updates](https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UpdateUserPoolClient.html):
> If you don't provide a value for an attribute, Amazon Cognito sets it to its default value.
```kotlin
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient
import software.amazon.awssdk.services.cognitoidentityprovider.model.DescribeUserPoolClientRequest
import software.amazon.awssdk.services.cognitoidentityprovider.model.UpdateUserPoolClientRequest
class CognitoClient(
private val cognitoConfig: CognitoConfig,
private val cognitoClient: CognitoIdentityProviderClient,
) {
// redacted other methods
fun addCallbackUrlToAppClient(clientId: String, callbackUrl: String) {
val describeUserPoolClientRequest = DescribeUserPoolClientRequest.builder()
.clientId(clientId)
.userPoolId(cognitoConfig.userPoolId)
.build()
val existingUserPoolClient = cognitoClient
.describeUserPoolClient(describeUserPoolClientRequest)
.userPoolClient()
val existingCallbackUrls = existingUserPoolClient.callbackURLs()
if (existingCallbackUrls.contains(callbackUrl)) {
logger.info("Callback URL $callbackUrl already exists for client $clientId, skipping.")
return
}
// We need to copy the list because it's immutable.
val updatedCallbackUrls = existingCallbackUrls.toMutableList()
updatedCallbackUrls.add(callbackUrl)
/**
* From the Cognito docs: "WARNING - If you don't provide a value for an attribute,
* Amazon Cognito sets it to its default value."
*
* So here we are, setting all the attributes to their already existing values.
*/
val updateRequest = UpdateUserPoolClientRequest.builder()
.accessTokenValidity(existingUserPoolClient.accessTokenValidity())
.allowedOAuthFlows(existingUserPoolClient.allowedOAuthFlows())
.allowedOAuthFlowsUserPoolClient(existingUserPoolClient.allowedOAuthFlowsUserPoolClient())
.allowedOAuthScopes(existingUserPoolClient.allowedOAuthScopes())
.analyticsConfiguration(existingUserPoolClient.analyticsConfiguration())
.authSessionValidity(existingUserPoolClient.authSessionValidity())
// This is the only field we're _actually_ changing
.callbackURLs(updatedCallbackUrls)
.clientId(existingUserPoolClient.clientId())
.clientName(existingUserPoolClient.clientName())
.defaultRedirectURI(existingUserPoolClient.defaultRedirectURI())
.enablePropagateAdditionalUserContextData(existingUserPoolClient.enablePropagateAdditionalUserContextData())
.enableTokenRevocation(existingUserPoolClient.enableTokenRevocation())
.explicitAuthFlows(existingUserPoolClient.explicitAuthFlows())
.idTokenValidity(existingUserPoolClient.idTokenValidity())
.logoutURLs(existingUserPoolClient.logoutURLs())
.preventUserExistenceErrors(existingUserPoolClient.preventUserExistenceErrors())
.readAttributes(existingUserPoolClient.readAttributes())
.refreshTokenValidity(existingUserPoolClient.refreshTokenValidity())
.supportedIdentityProviders(existingUserPoolClient.supportedIdentityProviders())
.tokenValidityUnits(existingUserPoolClient.tokenValidityUnits())
.userPoolId(existingUserPoolClient.userPoolId())
.writeAttributes(existingUserPoolClient.writeAttributes())
.build()
cognitoClient.updateUserPoolClient(updateRequest)
logger.info("Successfully added callback URL $callbackUrl to Cognito App Client $clientId")
}
}
```
### Proposed Solution
This probably will need updates on the Cognito service side. However, I do think it would be nice if the SDK could provide a convenience method in the meantime. I guess something like:
```kotlin
import software.amazon.awssdk.services.cognitoidentityprovider.CognitoIdentityProviderClient
import software.amazon.awssdk.services.cognitoidentityprovider.model.DescribeUserPoolClientRequest
import software.amazon.awssdk.services.cognitoidentityprovider.model.UpdateUserPoolClientRequest
class CognitoClient(
private val cognitoConfig: CognitoConfig,
private val cognitoClient: CognitoIdentityProviderClient,
) {
// redacted other methods
fun addCallbackUrlToAppClient(clientId: String, callbackUrl: String) {
val describeUserPoolClientRequest = DescribeUserPoolClientRequest.builder()
.clientId(clientId)
.userPoolId(cognitoConfig.userPoolId)
.build()
val existingUserPoolClient = cognitoClient
.describeUserPoolClient(describeUserPoolClientRequest)
.userPoolClient()
val existingCallbackUrls = existingUserPoolClient.callbackURLs()
if (existingCallbackUrls.contains(callbackUrl)) {
logger.info("Callback URL $callbackUrl already exists for client $clientId, skipping.")
return
}
// We need to copy the list because it's immutable.
val updatedCallbackUrls = existingCallbackUrls.toMutableList()
updatedCallbackUrls.add(callbackUrl)
val updateRequest = UpdateUserPoolClientRequest.builder()
.callbackURLs(updatedCallbackUrls)
.clientId(existingUserPoolClient.clientId())
.build()
cognitoClient.updateUserPoolClient(updateRequest, true)
logger.info("Successfully added callback URL $callbackUrl to Cognito App Client $clientId")
}
}
```
Note that `updateUserPoolClient` now takes a second parameter. This parameter could be named something like `keepExistingParameters` and default to `false`, so it's not a breaking change. If `true`, it could get the existing user pool (either by doing a `describeUserPoolClient` under the hood or require an existing `UserPoolClientType` to be passed).
### Other Information
Related Cognito docs: https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_UpdateUserPoolClient.html
### Acknowledgements
- [ ] I may be able to implement this feature request
- [ ] This feature might incur a breaking change
### AWS Java SDK version used
2.25.50
### JDK version used
17.0.10
### Operating System and version
macOS 14.4.1
Contributor guide
Assessment
This issue has not been assessed yet.