diff --git a/aws-qbusiness-application/aws-qbusiness-application.json b/aws-qbusiness-application/aws-qbusiness-application.json index 58b8536..97fcb15 100755 --- a/aws-qbusiness-application/aws-qbusiness-application.json +++ b/aws-qbusiness-application/aws-qbusiness-application.json @@ -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": { @@ -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": { @@ -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": { @@ -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" @@ -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, @@ -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, @@ -170,7 +251,10 @@ "/properties/IdentityCenterInstanceArn" ], "createOnlyProperties": [ - "/properties/EncryptionConfiguration" + "/properties/ClientIdsForOIDC", + "/properties/EncryptionConfiguration", + "/properties/IamIdentityProviderArn", + "/properties/IdentityType" ], "primaryIdentifier": [ "/properties/ApplicationId" @@ -178,15 +262,18 @@ "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" @@ -208,6 +295,7 @@ "qbusiness:UpdateApplication", "sso:CreateApplication", "sso:DeleteApplication", + "sso:DescribeInstance", "sso:PutApplicationAccessScope", "sso:PutApplicationAuthenticationMethod", "sso:PutApplicationGrant" diff --git a/aws-qbusiness-application/pom.xml b/aws-qbusiness-application/pom.xml index 1152ce9..454691d 100644 --- a/aws-qbusiness-application/pom.xml +++ b/aws-qbusiness-application/pom.xml @@ -55,7 +55,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-application/resource-role.yaml b/aws-qbusiness-application/resource-role.yaml index 4a3e59f..9068825 100644 --- a/aws-qbusiness-application/resource-role.yaml +++ b/aws-qbusiness-application/resource-role.yaml @@ -30,6 +30,7 @@ Resources: Statement: - Effect: Allow Action: + - "iam:GetSAMLProvider" - "iam:PassRole" - "kms:CreateGrant" - "kms:DescribeKey" @@ -44,6 +45,7 @@ Resources: - "qbusiness:UpdateApplication" - "sso:CreateApplication" - "sso:DeleteApplication" + - "sso:DescribeInstance" - "sso:PutApplicationAccessScope" - "sso:PutApplicationAuthenticationMethod" - "sso:PutApplicationGrant" diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/BaseHandlerStd.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/BaseHandlerStd.java index 1c1c9a9..0bd5226 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/BaseHandlerStd.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/BaseHandlerStd.java @@ -90,7 +90,7 @@ protected ProgressEvent 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); diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Constants.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Constants.java index cc9f69c..7590b15 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Constants.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Constants.java @@ -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() { } } diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java index bd91620..a1b5b79 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/CreateHandler.java @@ -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; @@ -58,11 +66,41 @@ protected ProgressEvent 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 proxyClient, ResourceModel model, @@ -93,11 +131,18 @@ private boolean isStabilized( } private CreateApplicationResponse callCreateApplication(CreateApplicationRequest request, - ProxyClient proxyClient, - ResourceModel model) { + ProxyClient 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 proxyClient) { + var client = proxyClient.client(); + return proxyClient.injectCredentialsAndInvokeV2(request, client::updateApplication); + } } diff --git a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java index 3887c81..385ddcb 100644 --- a/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java +++ b/aws-qbusiness-application/src/main/java/software/amazon/qbusiness/application/Translator.java @@ -6,6 +6,8 @@ import java.util.Optional; import java.util.Set; +import org.apache.commons.lang3.StringUtils; + import software.amazon.awssdk.services.qbusiness.model.CreateApplicationRequest; import software.amazon.awssdk.services.qbusiness.model.DeleteApplicationRequest; import software.amazon.awssdk.services.qbusiness.model.GetApplicationRequest; @@ -41,12 +43,16 @@ static CreateApplicationRequest translateToCreateRequest(final String idempotent .clientToken(idempotentToken) .displayName(model.getDisplayName()) .roleArn(model.getRoleArn()) + .identityType(model.getIdentityType()) + .iamIdentityProviderArn(model.getIamIdentityProviderArn()) + .clientIdsForOIDC(model.getClientIdsForOIDC()) .identityCenterInstanceArn(model.getIdentityCenterInstanceArn()) .description(model.getDescription()) .encryptionConfiguration(toServiceEncryptionConfig(model.getEncryptionConfiguration())) .attachmentsConfiguration(toServiceAttachmentConfiguration(model.getAttachmentsConfiguration())) .tags(TagHelper.serviceTagsFromCfnTags(model.getTags())) .qAppsConfiguration(toServiceQAppsConfiguration(model.getQAppsConfiguration())) + .personalizationConfiguration(toServicePersonalizationConfiguration(model.getPersonalizationConfiguration())) .build(); } @@ -77,11 +83,14 @@ static ListTagsForResourceRequest translateToListTagsRequest(final ResourceHandl * @return model resource model */ static ResourceModel translateFromReadResponse(final GetApplicationResponse awsResponse) { - return ResourceModel.builder() + var response = ResourceModel.builder() .displayName(awsResponse.displayName()) .applicationId(awsResponse.applicationId()) .applicationArn(awsResponse.applicationArn()) .roleArn(awsResponse.roleArn()) + .identityType(awsResponse.identityTypeAsString()) + .iamIdentityProviderArn(awsResponse.iamIdentityProviderArn()) + .clientIdsForOIDC(awsResponse.clientIdsForOIDC()) .identityCenterApplicationArn(awsResponse.identityCenterApplicationArn()) .status(awsResponse.statusAsString()) .description(awsResponse.description()) @@ -90,7 +99,16 @@ static ResourceModel translateFromReadResponse(final GetApplicationResponse awsR .encryptionConfiguration(fromServiceEncryptionConfig(awsResponse.encryptionConfiguration())) .attachmentsConfiguration(fromServiceAttachmentConfiguration(awsResponse.attachmentsConfiguration())) .qAppsConfiguration(fromServiceQAppsConfiguration(awsResponse.qAppsConfiguration())) + .personalizationConfiguration(fromServicePersonalizationConfiguration(awsResponse.personalizationConfiguration())) + .autoSubscriptionConfiguration(fromServiceAutoSubscriptionConfiguration(awsResponse.autoSubscriptionConfiguration())) .build(); + // TODO: Workaround. This is a readonly field. But it is only returned if customer is using IDC + // When that's not the case, let's fill it in with N/A + // Contract test require readonly fields are returned. + if (StringUtils.isEmpty(response.getIdentityCenterApplicationArn())) { + response.setIdentityCenterApplicationArn("N/A"); + } + return response; } static String instantToString(Instant instant) { @@ -171,6 +189,60 @@ static software.amazon.awssdk.services.qbusiness.model.QAppsConfiguration toServ .build(); } + static PersonalizationConfiguration fromServicePersonalizationConfiguration( + software.amazon.awssdk.services.qbusiness.model.PersonalizationConfiguration serviceConfig + ) { + if (serviceConfig == null) { + return null; + } + + return PersonalizationConfiguration.builder() + .personalizationControlMode(serviceConfig.personalizationControlModeAsString()) + .build(); + } + + static software.amazon.awssdk.services.qbusiness.model.PersonalizationConfiguration toServicePersonalizationConfiguration( + PersonalizationConfiguration modelConfig + ) { + if (modelConfig == null) { + return null; + } + + return software.amazon.awssdk.services.qbusiness.model.PersonalizationConfiguration.builder() + .personalizationControlMode(modelConfig.getPersonalizationControlMode()) + .build(); + } + + static AutoSubscriptionConfiguration fromServiceAutoSubscriptionConfiguration( + software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration serviceConfig + ) { + if (serviceConfig == null) { + return null; + } + + if (serviceConfig.autoSubscribe() == null && serviceConfig.defaultSubscriptionType() == null) { + return null; + } + + return AutoSubscriptionConfiguration.builder() + .autoSubscribe(serviceConfig.autoSubscribeAsString()) + .defaultSubscriptionType(serviceConfig.defaultSubscriptionTypeAsString()) + .build(); + } + + static software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration toServiceAutoSubscriptionConfiguration( + AutoSubscriptionConfiguration modelConfig + ) { + if (modelConfig == null) { + return null; + } + + return software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration.builder() + .autoSubscribe(modelConfig.getAutoSubscribe()) + .defaultSubscriptionType(modelConfig.getDefaultSubscriptionType()) + .build(); + } + static ResourceModel translateFromReadResponseWithTags(final ListTagsForResourceResponse listTagsResponse, final ResourceModel model) { if (listTagsResponse == null || !listTagsResponse.hasTags()) { return model; @@ -208,9 +280,18 @@ static UpdateApplicationRequest translateToUpdateRequest(final ResourceModel mod .identityCenterInstanceArn(model.getIdentityCenterInstanceArn()) .attachmentsConfiguration(toServiceAttachmentConfiguration(model.getAttachmentsConfiguration())) .qAppsConfiguration(toServiceQAppsConfiguration(model.getQAppsConfiguration())) + .personalizationConfiguration(toServicePersonalizationConfiguration(model.getPersonalizationConfiguration())) + .autoSubscriptionConfiguration(toServiceAutoSubscriptionConfiguration(model.getAutoSubscriptionConfiguration())) .build(); } + static UpdateApplicationRequest translateToPostCreateUpdateRequest(final ResourceModel model) { + return UpdateApplicationRequest.builder() + .applicationId(model.getApplicationId()) + .autoSubscriptionConfiguration(toServiceAutoSubscriptionConfiguration(model.getAutoSubscriptionConfiguration())) + .build(); + } + /** * Request to list resources * @@ -238,6 +319,7 @@ static List translateFromListResponse(final ListApplicationsRespo .createdAt(instantToString(application.createdAt())) .updatedAt(instantToString(application.updatedAt())) .status(application.statusAsString()) + .identityType(application.identityTypeAsString()) .build() ) .toList(); diff --git a/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/CreateHandlerTest.java b/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/CreateHandlerTest.java index 3a3e4a5..68523a5 100644 --- a/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/CreateHandlerTest.java +++ b/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/CreateHandlerTest.java @@ -12,6 +12,7 @@ import static org.mockito.Mockito.when; import java.time.Duration; +import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; @@ -42,6 +43,9 @@ import software.amazon.awssdk.services.qbusiness.model.ServiceQuotaExceededException; import software.amazon.awssdk.services.qbusiness.model.ThrottlingException; import software.amazon.awssdk.services.qbusiness.model.ValidationException; +import software.amazon.awssdk.services.qbusiness.model.UpdateApplicationRequest; +import software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionStatus; +import software.amazon.awssdk.services.qbusiness.model.SubscriptionType; import software.amazon.cloudformation.exceptions.CfnNotStabilizedException; import software.amazon.cloudformation.proxy.AmazonWebServicesClientProxy; import software.amazon.cloudformation.proxy.HandlerErrorCode; @@ -91,6 +95,13 @@ public void setup() { .description("A Description") .roleArn("such role, very arn") .identityCenterInstanceArn("arn:aws:sso:::instance/ssoins") + .identityType("AWS_IAM_IDP_OIDC") + .autoSubscriptionConfiguration(AutoSubscriptionConfiguration.builder() + .autoSubscribe(AutoSubscriptionStatus.ENABLED.toString()) + .defaultSubscriptionType(SubscriptionType.Q_BUSINESS.toString()) + .build()) + .iamIdentityProviderArn("arn:aws:iam::123456:oidc-provider/trial-123456.okta.com") + .clientIdsForOIDC(List.of("0oaglq4vdnaWau7hW697")) .build(); testRequest = ResourceHandlerRequest.builder() @@ -145,9 +156,12 @@ proxy, testRequest, new CallbackContext(), proxyClient, logger assertThat(model.getStatus()).isEqualTo(ApplicationStatus.ACTIVE.toString()); verify(sdkClient).createApplication(any(CreateApplicationRequest.class)); - verify(sdkClient, times(2)).getApplication( + verify(sdkClient, times(3)).getApplication( argThat((ArgumentMatcher) t -> t.applicationId().equals(APP_ID)) ); + verify(sdkClient, times(1)).updateApplication( + argThat((ArgumentMatcher) t -> t.applicationId().equals(APP_ID)) + ); verify(sdkClient).listTagsForResource(any(ListTagsForResourceRequest.class)); } @@ -180,9 +194,12 @@ proxy, testRequest, new CallbackContext(), proxyClient, logger assertThat(resultProgress).isNotNull(); assertThat(resultProgress.isSuccess()).isTrue(); verify(sdkClient).createApplication(any(CreateApplicationRequest.class)); - verify(sdkClient, times(3)).getApplication( + verify(sdkClient, times(4)).getApplication( argThat((ArgumentMatcher) t -> t.applicationId().equals(APP_ID)) ); + verify(sdkClient, times(1)).updateApplication( + argThat((ArgumentMatcher) t -> t.applicationId().equals(APP_ID)) + ); verify(sdkClient).listTagsForResource(any(ListTagsForResourceRequest.class)); } diff --git a/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/ReadHandlerTest.java b/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/ReadHandlerTest.java index bdf09bc..3248447 100644 --- a/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/ReadHandlerTest.java +++ b/aws-qbusiness-application/src/test/java/software/amazon/qbusiness/application/ReadHandlerTest.java @@ -28,6 +28,9 @@ import software.amazon.awssdk.services.qbusiness.model.ApplicationStatus; import software.amazon.awssdk.services.qbusiness.model.AppliedAttachmentsConfiguration; import software.amazon.awssdk.services.qbusiness.model.AttachmentsControlMode; +import software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionConfiguration; +import software.amazon.awssdk.services.qbusiness.model.AutoSubscriptionStatus; +import software.amazon.awssdk.services.qbusiness.model.SubscriptionType; import software.amazon.awssdk.services.qbusiness.model.EncryptionConfiguration; import software.amazon.awssdk.services.qbusiness.model.QBusinessException; import software.amazon.awssdk.services.qbusiness.model.GetApplicationRequest; @@ -107,6 +110,9 @@ public void handleRequest_SimpleSuccess() { .description("this is a description, there are many like it but this one is mine.") .displayName("Foobar") .identityCenterApplicationArn("arn:aws:sso::123456789012:application/ssoins/apl") + .identityType("AWS_IAM_IDP_OIDC") + .iamIdentityProviderArn("arn:aws:iam::123456:oidc-provider/trial-123456.okta.com") + .clientIdsForOIDC(List.of("0oaglq4vdnaWau7hW697")) .status(ApplicationStatus.ACTIVE) .encryptionConfiguration(EncryptionConfiguration.builder() .kmsKeyId("keyblade") @@ -114,6 +120,10 @@ public void handleRequest_SimpleSuccess() { .attachmentsConfiguration(AppliedAttachmentsConfiguration.builder() .attachmentsControlMode(AttachmentsControlMode.ENABLED) .build()) + .autoSubscriptionConfiguration(AutoSubscriptionConfiguration.builder() + .autoSubscribe(AutoSubscriptionStatus.ENABLED.toString()) + .defaultSubscriptionType(SubscriptionType.Q_BUSINESS.toString()) + .build()) .build()); when(proxyClient.client().listTagsForResource(any(ListTagsForResourceRequest.class))) .thenReturn(ListTagsForResourceResponse.builder() @@ -149,6 +159,9 @@ proxy, testRequest, new CallbackContext(), proxyClient, logger assertThat(resultModel.getStatus()).isEqualTo(ApplicationStatus.ACTIVE.toString()); assertThat(resultModel.getEncryptionConfiguration().getKmsKeyId()).isEqualTo("keyblade"); assertThat(resultModel.getAttachmentsConfiguration().getAttachmentsControlMode()).isEqualTo(AttachmentsControlMode.ENABLED.toString()); + assertThat(resultModel.getAutoSubscriptionConfiguration().getAutoSubscribe()).isEqualTo(AutoSubscriptionStatus.ENABLED.toString()); + assertThat(resultModel.getAutoSubscriptionConfiguration().getDefaultSubscriptionType()).isEqualTo(SubscriptionType.Q_BUSINESS.toString()); + var tags = resultModel.getTags().stream().map(tag -> Map.entry(tag.getKey(), tag.getValue())).toList(); assertThat(tags).isEqualTo(List.of( diff --git a/aws-qbusiness-datasource/pom.xml b/aws-qbusiness-datasource/pom.xml index 914334e..25d88f8 100644 --- a/aws-qbusiness-datasource/pom.xml +++ b/aws-qbusiness-datasource/pom.xml @@ -30,7 +30,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-index/pom.xml b/aws-qbusiness-index/pom.xml index dc9a38d..ade4958 100644 --- a/aws-qbusiness-index/pom.xml +++ b/aws-qbusiness-index/pom.xml @@ -37,7 +37,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-plugin/pom.xml b/aws-qbusiness-plugin/pom.xml index 29e83d6..1e74c63 100644 --- a/aws-qbusiness-plugin/pom.xml +++ b/aws-qbusiness-plugin/pom.xml @@ -43,7 +43,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-retriever/pom.xml b/aws-qbusiness-retriever/pom.xml index f659ef7..3557d54 100644 --- a/aws-qbusiness-retriever/pom.xml +++ b/aws-qbusiness-retriever/pom.xml @@ -37,7 +37,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-webexperience/aws-qbusiness-webexperience.json b/aws-qbusiness-webexperience/aws-qbusiness-webexperience.json index 7a5d244..91d00ae 100755 --- a/aws-qbusiness-webexperience/aws-qbusiness-webexperience.json +++ b/aws-qbusiness-webexperience/aws-qbusiness-webexperience.json @@ -2,6 +2,73 @@ "typeName": "AWS::QBusiness::WebExperience", "description": "Definition of AWS::QBusiness::WebExperience Resource Type", "definitions": { + "IdentityProviderConfiguration": { + "oneOf": [ + { + "type": "object", + "title": "SamlConfiguration", + "properties": { + "SamlConfiguration": { + "$ref": "#/definitions/SamlProviderConfiguration" + } + }, + "required": [ + "SamlConfiguration" + ], + "additionalProperties": false + }, + { + "type": "object", + "title": "OpenIDConnectConfiguration", + "properties": { + "OpenIDConnectConfiguration": { + "$ref": "#/definitions/OpenIDConnectProviderConfiguration" + } + }, + "required": [ + "OpenIDConnectConfiguration" + ], + "additionalProperties": false + } + ] + }, + "OpenIDConnectProviderConfiguration": { + "type": "object", + "properties": { + "SecretsArn": { + "type": "string", + "maxLength": 1284, + "minLength": 0, + "pattern": "^arn:[a-z0-9-\\.]{1,63}:[a-z0-9-\\.]{0,63}:[a-z0-9-\\.]{0,63}:[a-z0-9-\\.]{0,63}:[^/].{0,1023}$" + }, + "SecretsRole": { + "type": "string", + "maxLength": 1284, + "minLength": 0, + "pattern": "^arn:[a-z0-9-\\.]{1,63}:[a-z0-9-\\.]{0,63}:[a-z0-9-\\.]{0,63}:[a-z0-9-\\.]{0,63}:[^/].{0,1023}$" + } + }, + "required": [ + "SecretsArn", + "SecretsRole" + ], + "additionalProperties": false + }, + "SamlProviderConfiguration": { + "type": "object", + "properties": { + "AuthenticationUrl": { + "type": "string", + "maxLength": 1284, + "minLength": 1, + "pattern": "^https://.*$" + } + }, + "required": [ + "AuthenticationUrl" + ], + "additionalProperties": false + }, "Tag": { "type": "object", "properties": { @@ -57,6 +124,9 @@ "minLength": 1, "pattern": "^(https?|ftp|file)://([^\\s]*)$" }, + "IdentityProviderConfiguration": { + "$ref": "#/definitions/IdentityProviderConfiguration" + }, "RoleArn": { "type": "string", "maxLength": 1284, diff --git a/aws-qbusiness-webexperience/pom.xml b/aws-qbusiness-webexperience/pom.xml index 51eef7e..dfea136 100644 --- a/aws-qbusiness-webexperience/pom.xml +++ b/aws-qbusiness-webexperience/pom.xml @@ -37,7 +37,7 @@ software.amazon.awssdk qbusiness - 2.26.14 + 2.27.17 diff --git a/aws-qbusiness-webexperience/src/main/java/software/amazon/qbusiness/webexperience/Translator.java b/aws-qbusiness-webexperience/src/main/java/software/amazon/qbusiness/webexperience/Translator.java index 200d8b1..eda5095 100644 --- a/aws-qbusiness-webexperience/src/main/java/software/amazon/qbusiness/webexperience/Translator.java +++ b/aws-qbusiness-webexperience/src/main/java/software/amazon/qbusiness/webexperience/Translator.java @@ -44,6 +44,7 @@ static CreateWebExperienceRequest translateToCreateRequest(final String idempote .clientToken(idempotentToken) .applicationId(model.getApplicationId()) .roleArn(model.getRoleArn()) + .identityProviderConfiguration(toIdentityProviderConfiguration(model.getIdentityProviderConfiguration())) .title(model.getTitle()) .subtitle(model.getSubtitle()) .welcomeMessage(model.getWelcomeMessage()) @@ -96,6 +97,7 @@ static ResourceModel translateFromReadResponse(final GetWebExperienceResponse aw .welcomeMessage(awsResponse.welcomeMessage()) .samplePromptsControlMode(awsResponse.samplePromptsControlModeAsString()) .roleArn(awsResponse.roleArn()) + .identityProviderConfiguration(fromIdentityProviderConfiguration(awsResponse.identityProviderConfiguration())) .defaultEndpoint(awsResponse.defaultEndpoint()) .createdAt(instantToString(awsResponse.createdAt())) .updatedAt(instantToString(awsResponse.updatedAt())) @@ -145,9 +147,87 @@ static UpdateWebExperienceRequest translateToUpdateRequest(final ResourceModel m .title(model.getTitle()) .subtitle(model.getSubtitle()) .roleArn(model.getRoleArn()) + .identityProviderConfiguration(toIdentityProviderConfiguration(model.getIdentityProviderConfiguration())) .build(); } + static IdentityProviderConfiguration fromIdentityProviderConfiguration( + software.amazon.awssdk.services.qbusiness.model.IdentityProviderConfiguration serviceConfig + ) { + if (serviceConfig == null) { + return null; + } + + return IdentityProviderConfiguration.builder() + .openIDConnectConfiguration(fromOpenIDConnectProviderConfiguration(serviceConfig.openIDConnectConfiguration())) + .samlConfiguration(fromSamlProviderConfiguration(serviceConfig.samlConfiguration())) + .build(); + } + + static OpenIDConnectProviderConfiguration fromOpenIDConnectProviderConfiguration( + software.amazon.awssdk.services.qbusiness.model.OpenIDConnectProviderConfiguration serviceConfig + ) { + if (serviceConfig == null) { + return null; + } + + return OpenIDConnectProviderConfiguration.builder() + .secretsArn(serviceConfig.secretsArn()) + .secretsRole(serviceConfig.secretsRole()) + .build(); + } + + static SamlProviderConfiguration fromSamlProviderConfiguration( + software.amazon.awssdk.services.qbusiness.model.SamlProviderConfiguration serviceConfig + ) { + if (serviceConfig == null) { + return null; + } + + return SamlProviderConfiguration.builder() + .authenticationUrl(serviceConfig.authenticationUrl()) + .build(); + } + + static software.amazon.awssdk.services.qbusiness.model.OpenIDConnectProviderConfiguration toOpenIDConnectProviderConfiguration( + OpenIDConnectProviderConfiguration modelConfig + ) { + if (modelConfig == null) { + return null; + } + + return software.amazon.awssdk.services.qbusiness.model.OpenIDConnectProviderConfiguration.builder() + .secretsArn(modelConfig.getSecretsArn()) + .secretsRole(modelConfig.getSecretsRole()) + .build(); + } + + + static software.amazon.awssdk.services.qbusiness.model.SamlProviderConfiguration toSamlProviderConfiguration( + SamlProviderConfiguration modelConfig + ) { + if (modelConfig == null) { + return null; + } + + return software.amazon.awssdk.services.qbusiness.model.SamlProviderConfiguration.builder() + .authenticationUrl(modelConfig.getAuthenticationUrl()) + .build(); + } + + static software.amazon.awssdk.services.qbusiness.model.IdentityProviderConfiguration toIdentityProviderConfiguration( + IdentityProviderConfiguration modelConfig + ) { + if (modelConfig == null) { + return null; + } + + return software.amazon.awssdk.services.qbusiness.model.IdentityProviderConfiguration.builder() + .samlConfiguration(toSamlProviderConfiguration(modelConfig.getSamlConfiguration())) + .openIDConnectConfiguration(toOpenIDConnectProviderConfiguration(modelConfig.getOpenIDConnectConfiguration())) + .build(); + } + /** * Request to update properties of a previously created resource * diff --git a/aws-qbusiness-webexperience/src/test/java/software/amazon/qbusiness/webexperience/CreateHandlerTest.java b/aws-qbusiness-webexperience/src/test/java/software/amazon/qbusiness/webexperience/CreateHandlerTest.java index f0df558..5033389 100644 --- a/aws-qbusiness-webexperience/src/test/java/software/amazon/qbusiness/webexperience/CreateHandlerTest.java +++ b/aws-qbusiness-webexperience/src/test/java/software/amazon/qbusiness/webexperience/CreateHandlerTest.java @@ -65,6 +65,7 @@ public class CreateHandlerTest extends AbstractTestBase { private Constant testBackOff; private CreateHandler underTest; + private IdentityProviderConfiguration identityProviderConfiguration; private AutoCloseable testMocks; @@ -84,11 +85,16 @@ public void setup() { underTest = new CreateHandler(testBackOff); + identityProviderConfiguration = IdentityProviderConfiguration.builder() + .samlConfiguration(SamlProviderConfiguration.builder().authenticationUrl("https://someTestAuthenticationUrl").build()) + .build(); + createModel = ResourceModel.builder() .applicationId(APP_ID) .title("This is a title of the web experience.") .subtitle("This is a subtitle of the web experience.") .roleArn("RoleArn") + .identityProviderConfiguration(identityProviderConfiguration) .build(); testRequest = ResourceHandlerRequest.builder()