Skip to content

Commit

Permalink
Add updates for IDP feature releases (#58)
Browse files Browse the repository at this point in the history
* Updates for IDP feature releases

* Web experience updates for IDP feature release.

* Update pom.xml to include latest qbusiness sdk.
  • Loading branch information
jregistr committed Sep 3, 2024
1 parent 47cc991 commit ede13c5
Show file tree
Hide file tree
Showing 17 changed files with 419 additions and 13 deletions.
90 changes: 89 additions & 1 deletion aws-qbusiness-application/aws-qbusiness-application.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,28 @@
"DISABLED"
]
},
"AutoSubscriptionConfiguration": {
"type": "object",
"properties": {
"AutoSubscribe": {
"$ref": "#/definitions/AutoSubscriptionStatus"
},
"DefaultSubscriptionType": {
"$ref": "#/definitions/SubscriptionType"
}
},
"required": [
"AutoSubscribe"
],
"additionalProperties": false
},
"AutoSubscriptionStatus": {
"type": "string",
"enum": [
"ENABLED",
"DISABLED"
]
},
"EncryptionConfiguration": {
"type": "object",
"properties": {
Expand All @@ -42,6 +64,14 @@
},
"additionalProperties": false
},
"IdentityType": {
"type": "string",
"enum": [
"AWS_IAM_IDP_SAML",
"AWS_IAM_IDP_OIDC",
"AWS_IAM_IDC"
]
},
"QAppsConfiguration": {
"type": "object",
"properties": {
Expand All @@ -61,6 +91,32 @@
"DISABLED"
]
},
"SubscriptionType": {
"type": "string",
"enum": [
"Q_LITE",
"Q_BUSINESS"
]
},
"PersonalizationConfiguration": {
"type": "object",
"properties": {
"PersonalizationControlMode": {
"$ref": "#/definitions/PersonalizationControlMode"
}
},
"required": [
"PersonalizationControlMode"
],
"additionalProperties": false
},
"PersonalizationControlMode": {
"type": "string",
"enum": [
"ENABLED",
"DISABLED"
]
},
"Tag": {
"type": "object",
"properties": {
Expand Down Expand Up @@ -98,6 +154,19 @@
"AttachmentsConfiguration": {
"$ref": "#/definitions/AttachmentsConfiguration"
},
"AutoSubscriptionConfiguration": {
"$ref": "#/definitions/AutoSubscriptionConfiguration"
},
"ClientIdsForOIDC": {
"type": "array",
"insertionOrder": false,
"items": {
"type": "string",
"maxLength": 255,
"minLength": 1,
"pattern": "^[a-zA-Z0-9_.:/()*?=-]*$"
}
},
"CreatedAt": {
"type": "string",
"format": "date-time"
Expand All @@ -117,6 +186,12 @@
"EncryptionConfiguration": {
"$ref": "#/definitions/EncryptionConfiguration"
},
"IamIdentityProviderArn": {
"type": "string",
"maxLength": 2048,
"minLength": 20,
"pattern": "^arn:aws:iam::\\d{12}:(oidc-provider|saml-provider)/[a-zA-Z0-9_\\.\\/@\\-]+$"
},
"IdentityCenterApplicationArn": {
"type": "string",
"maxLength": 1224,
Expand All @@ -126,12 +201,18 @@
"QAppsConfiguration": {
"$ref": "#/definitions/QAppsConfiguration"
},
"PersonalizationConfiguration": {
"$ref": "#/definitions/PersonalizationConfiguration"
},
"IdentityCenterInstanceArn": {
"type": "string",
"maxLength": 1224,
"minLength": 10,
"pattern": "^arn:(aws|aws-us-gov|aws-cn|aws-iso|aws-iso-b):sso:::instance/(sso)?ins-[a-zA-Z0-9-.]{16}$"
},
"IdentityType": {
"$ref": "#/definitions/IdentityType"
},
"RoleArn": {
"type": "string",
"maxLength": 1284,
Expand Down Expand Up @@ -170,23 +251,29 @@
"/properties/IdentityCenterInstanceArn"
],
"createOnlyProperties": [
"/properties/EncryptionConfiguration"
"/properties/ClientIdsForOIDC",
"/properties/EncryptionConfiguration",
"/properties/IamIdentityProviderArn",
"/properties/IdentityType"
],
"primaryIdentifier": [
"/properties/ApplicationId"
],
"handlers": {
"create": {
"permissions": [
"iam:GetSAMLProvider",
"iam:PassRole",
"kms:CreateGrant",
"kms:DescribeKey",
"qbusiness:CreateApplication",
"qbusiness:GetApplication",
"qbusiness:UpdateApplication",
"qbusiness:ListTagsForResource",
"qbusiness:TagResource",
"sso:CreateApplication",
"sso:DeleteApplication",
"sso:DescribeInstance",
"sso:PutApplicationAccessScope",
"sso:PutApplicationAuthenticationMethod",
"sso:PutApplicationGrant"
Expand All @@ -208,6 +295,7 @@
"qbusiness:UpdateApplication",
"sso:CreateApplication",
"sso:DeleteApplication",
"sso:DescribeInstance",
"sso:PutApplicationAccessScope",
"sso:PutApplicationAuthenticationMethod",
"sso:PutApplicationGrant"
Expand Down
2 changes: 1 addition & 1 deletion aws-qbusiness-application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
<dependency>
<groupId>software.amazon.awssdk</groupId>
<artifactId>qbusiness</artifactId>
<version>2.26.14</version>
<version>2.27.17</version>
</dependency>
<!-- https://mvnrepository.com/artifact/software.amazon.awssdk/aws-core -->
<dependency>
Expand Down
2 changes: 2 additions & 0 deletions aws-qbusiness-application/resource-role.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Resources:
Statement:
- Effect: Allow
Action:
- "iam:GetSAMLProvider"
- "iam:PassRole"
- "kms:CreateGrant"
- "kms:DescribeKey"
Expand All @@ -44,6 +45,7 @@ Resources:
- "qbusiness:UpdateApplication"
- "sso:CreateApplication"
- "sso:DeleteApplication"
- "sso:DescribeInstance"
- "sso:PutApplicationAccessScope"
- "sso:PutApplicationAuthenticationMethod"
- "sso:PutApplicationGrant"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleError(

if (error instanceof ResourceNotFoundException) {
cfnException = new CfnNotFoundException(ResourceModel.TYPE_NAME, primaryIdentifier, error);
} else if (error instanceof ValidationException) {
} else if (error instanceof ValidationException || error instanceof CfnInvalidRequestException) {
cfnException = new CfnInvalidRequestException(error);
} else if (error instanceof ThrottlingException) {
cfnException = new CfnThrottlingException(apiName, error);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ public final class Constants {
public static final String SERVICE_NAME_LOWER = SERVICE_NAME.toLowerCase(Locale.ENGLISH);
public static final String ENV_AWS_REGION = "AWS_REGION";

public static final String AUTOSUBSCRIBE_FIELD_VALIDATION_ERROR =
"AutoSubscriptionConfiguration must be ENABLED with a default subscription tier for %s identity type.";

private Constants() {
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,24 @@
package software.amazon.qbusiness.application;

import static software.amazon.qbusiness.application.Constants.API_CREATE_APPLICATION;
import static software.amazon.qbusiness.application.Constants.API_UPDATE_APPLICATION;
import static software.amazon.qbusiness.application.Constants.AUTOSUBSCRIBE_FIELD_VALIDATION_ERROR;

import java.time.Duration;
import java.util.Objects;

import software.amazon.awssdk.services.qbusiness.QBusinessClient;
import software.amazon.awssdk.services.qbusiness.model.ApplicationStatus;
import software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionStatus;
import software.amazon.awssdk.services.qbusiness.model.CreateApplicationRequest;
import software.amazon.awssdk.services.qbusiness.model.CreateApplicationResponse;
import software.amazon.awssdk.services.qbusiness.model.GetApplicationResponse;
import software.amazon.awssdk.services.qbusiness.model.IdentityType;
import software.amazon.awssdk.services.qbusiness.model.SubscriptionType;
import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationRequest;
import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationResponse;
import software.amazon.awssdk.utils.StringUtils;
import software.amazon.cloudformation.exceptions.CfnInvalidRequestException;
import software.amazon.cloudformation.exceptions.CfnNotStabilizedException;
import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy;
import software.amazon.cloudformation.proxy.Logger;
Expand Down Expand Up @@ -58,11 +66,41 @@ protected ProgressEvent<ResourceModel, CallbackContext> handleRequest(
.handleError((createReq, error, client, model, context) ->
handleError(createReq, model, error, context, logger, API_CREATE_APPLICATION))
.progress()
).then(progress -> {
if (!isIAMFederatedApp(IdentityType.fromValue(request.getDesiredResourceState().getIdentityType()))) {
return progress;
}
// Immediately update the application to add auto-subscribe configuration to it.
// TODO: Remove after AutoSubscribeConfiguration is added to the CreateApplication API.
return proxy.initiate("AWS-QBusiness-Application::Update", proxyClient, progress.getResourceModel(), progress.getCallbackContext())
.translateToServiceRequest(model -> Translator.translateToPostCreateUpdateRequest(model))
.backoffDelay(backOffStrategy)
.makeServiceCall((awsRequest, clientProxyClient) -> callUpdateApplication(awsRequest, clientProxyClient))
.stabilize((awsReq, response, clientProxyClient, model, context) -> isStabilized(clientProxyClient, model, logger))
.handleError((updateReq, error, client, model, context) ->
handleError(updateReq, model, error, context, logger, API_UPDATE_APPLICATION))
.progress();
}
).then(progress ->
new ReadHandler().handleRequest(proxy, request, callbackContext, proxyClient, logger)
);
}

private void validateAutoSubscriptionConfiguration(ResourceModel desiredResourceState) {
if (isIAMFederatedApp(IdentityType.fromValue(desiredResourceState.getIdentityType()))) {
AutoSubscriptionConfiguration config = desiredResourceState.getAutoSubscriptionConfiguration();
if (config != null && AutoSubscriptionStatus.ENABLED.toString().equals(config.getAutoSubscribe()) &&
config.getDefaultSubscriptionType()!= null) {
return;
}
throw new CfnInvalidRequestException(String.format(AUTOSUBSCRIBE_FIELD_VALIDATION_ERROR, desiredResourceState.getIdentityType()));
}
}

private boolean isIAMFederatedApp(IdentityType identityType) {
return IdentityType.AWS_IAM_IDP_OIDC.equals(identityType) || IdentityType.AWS_IAM_IDP_SAML.equals(identityType);
}

private boolean isStabilized(
ProxyClient<QBusinessClient> proxyClient,
ResourceModel model,
Expand Down Expand Up @@ -93,11 +131,18 @@ private boolean isStabilized(
}

private CreateApplicationResponse callCreateApplication(CreateApplicationRequest request,
ProxyClient<QBusinessClient> proxyClient,
ResourceModel model) {
ProxyClient<QBusinessClient> proxyClient,
ResourceModel model) {
validateAutoSubscriptionConfiguration(model);
var client = proxyClient.client();
CreateApplicationResponse response = proxyClient.injectCredentialsAndInvokeV2(request, client::createApplication);
model.setApplicationId(response.applicationId());
return response;
}

private UpdateApplicationResponse callUpdateApplication(UpdateApplicationRequest request,
ProxyClient<QBusinessClient> proxyClient) {
var client = proxyClient.client();
return proxyClient.injectCredentialsAndInvokeV2(request, client::updateApplication);
}
}
Loading

0 comments on commit ede13c5

Please sign in to comment.