diff --git a/eng/versioning/external_dependencies.txt b/eng/versioning/external_dependencies.txt index 89f9243f9a00..1502121e62df 100644 --- a/eng/versioning/external_dependencies.txt +++ b/eng/versioning/external_dependencies.txt @@ -132,6 +132,7 @@ org.springframework:spring-messaging;5.2.10.RELEASE org.springframework:spring-tx;5.2.10.RELEASE org.springframework:spring-web;5.2.10.RELEASE org.springframework:spring-webmvc;5.2.10.RELEASE +org.springframework:spring-webflux;5.2.10.RELEASE # spring-boot-starter-parent is not managed by spring-boot-dependencies or spring-cloud-dependencies. org.springframework.boot:spring-boot-starter-parent;2.3.7.RELEASE diff --git a/sdk/confluent/azure-resourcemanager-confluent/CHANGELOG.md b/sdk/confluent/azure-resourcemanager-confluent/CHANGELOG.md index 414377a51a56..8b1f9fd55ad5 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/CHANGELOG.md +++ b/sdk/confluent/azure-resourcemanager-confluent/CHANGELOG.md @@ -1,7 +1,8 @@ # Release History -## 1.0.0-beta.2 (Unreleased) +## 1.0.0-beta.1 (2021-02-26) +- Azure Resource Manager Confluent client library for Java. This package contains Microsoft Azure SDK for Confluent Management SDK. Package tag package-2020-03-01. For documentation on how to use this package, please see [Azure Management Libraries for Java](https://aka.ms/azsdk/java/mgmt). ## 1.0.0-beta.1 (2021-01-14) diff --git a/sdk/confluent/azure-resourcemanager-confluent/README.md b/sdk/confluent/azure-resourcemanager-confluent/README.md index a9a1ee92f0d8..e0fd8e1e56f3 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/README.md +++ b/sdk/confluent/azure-resourcemanager-confluent/README.md @@ -4,6 +4,20 @@ Azure Resource Manager Confluent client library for Java. This package contains Microsoft Azure SDK for Confluent Management SDK. Package tag package-2020-03-01. For documentation on how to use this package, please see [Azure Management Libraries for Java](https://aka.ms/azsdk/java/mgmt). +## We'd love to hear your feedback + +We're always working on improving our products and the way we communicate with our users. So we'd love to learn what's working and how we can do better. + +If you haven't already, please take a few minutes to [complete this short survey][survey] we have put together. + +Thank you in advance for your collaboration. We really appreciate your time! + +## Documentation + +Various documentation is available to help you get started + +- [API reference documentation][docs] + ## Getting started ### Prerequisites @@ -18,7 +32,7 @@ This package contains Microsoft Azure SDK for Confluent Management SDK. Package com.azure.resourcemanager azure-resourcemanager-confluent - 1.0.0-beta.1 + 1.0.0-beta.2 ``` [//]: # ({x-version-update-end}) @@ -75,6 +89,8 @@ For details on contributing to this repository, see the [contributing guide](htt 1. Create new Pull Request +[survey]: https://microsoft.qualtrics.com/jfe/form/SV_ehN0lIk2FKEBkwd?Q_CHL=DOCS +[docs]: https://azure.github.io/azure-sdk-for-java/ [jdk]: https://docs.microsoft.com/java/azure/jdk/ [azure_subscription]: https://azure.microsoft.com/free/ [azure_identity]: https://github.com/Azure/azure-sdk-for-java/blob/master/sdk/identity/azure-identity diff --git a/sdk/confluent/azure-resourcemanager-confluent/pom.xml b/sdk/confluent/azure-resourcemanager-confluent/pom.xml index 3e0168f80a4b..5014ed6555fa 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/pom.xml +++ b/sdk/confluent/azure-resourcemanager-confluent/pom.xml @@ -41,6 +41,11 @@ + + com.azure + azure-core + 1.13.0 + com.azure azure-core-management diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/ConfluentManager.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/ConfluentManager.java index f83ab7100a8a..de2e84612287 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/ConfluentManager.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/ConfluentManager.java @@ -160,17 +160,31 @@ public ConfluentManager authenticate(TokenCredential credential, AzureProfile pr Objects.requireNonNull(credential, "'credential' cannot be null."); Objects.requireNonNull(profile, "'profile' cannot be null."); + StringBuilder userAgentBuilder = new StringBuilder(); + userAgentBuilder + .append("azsdk-java") + .append("-") + .append("com.azure.resourcemanager.confluent") + .append("/") + .append("1.0.0-beta.1"); + if (!Configuration.getGlobalConfiguration().get("AZURE_TELEMETRY_DISABLED", false)) { + userAgentBuilder + .append(" (") + .append(Configuration.getGlobalConfiguration().get("java.version")) + .append("; ") + .append(Configuration.getGlobalConfiguration().get("os.name")) + .append("; ") + .append(Configuration.getGlobalConfiguration().get("os.version")) + .append("; auto-generated)"); + } else { + userAgentBuilder.append(" (auto-generated)"); + } + if (retryPolicy == null) { retryPolicy = new RetryPolicy("Retry-After", ChronoUnit.SECONDS); } List policies = new ArrayList<>(); - policies - .add( - new UserAgentPolicy( - null, - "com.azure.resourcemanager.confluent", - "1.0.0-beta.1", - Configuration.getGlobalConfiguration())); + policies.add(new UserAgentPolicy(userAgentBuilder.toString())); policies.add(new RequestIdPolicy()); HttpPolicyProviders.addBeforeRetryPolicies(policies); policies.add(retryPolicy); diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/fluent/models/OrganizationResourceInner.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/fluent/models/OrganizationResourceInner.java index b118fa126b80..c356323a91fd 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/fluent/models/OrganizationResourceInner.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/fluent/models/OrganizationResourceInner.java @@ -31,7 +31,7 @@ public class OrganizationResourceInner extends Resource { /* * Provision states for confluent RP */ - @JsonProperty(value = "properties.provisioningState") + @JsonProperty(value = "properties.provisioningState", access = JsonProperty.Access.WRITE_ONLY) private ProvisionState provisioningState; /* @@ -76,17 +76,6 @@ public ProvisionState provisioningState() { return this.provisioningState; } - /** - * Set the provisioningState property: Provision states for confluent RP. - * - * @param provisioningState the provisioningState value to set. - * @return the OrganizationResourceInner object itself. - */ - public OrganizationResourceInner withProvisioningState(ProvisionState provisioningState) { - this.provisioningState = provisioningState; - return this; - } - /** * Get the organizationId property: Id of the Confluent organization. * diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/ConfluentManagementClientImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/ConfluentManagementClientImpl.java index 0f8a5e484733..862786982b7c 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/ConfluentManagementClientImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/ConfluentManagementClientImpl.java @@ -258,7 +258,7 @@ public Mono getLroFinalResultOrError(AsyncPollResponse, if (managementError.getCode() == null || managementError.getMessage() == null) { managementError = null; } - } catch (IOException ioe) { + } catch (IOException | RuntimeException ioe) { logger.logThrowableAsWarning(ioe); } } @@ -287,7 +287,7 @@ private static final class HttpResponseImpl extends HttpResponse { super(null); this.statusCode = statusCode; this.httpHeaders = httpHeaders; - this.responseBody = responseBody.getBytes(StandardCharsets.UTF_8); + this.responseBody = responseBody == null ? null : responseBody.getBytes(StandardCharsets.UTF_8); } public int getStatusCode() { diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsClientImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsClientImpl.java index 05ec7061f9f2..cf9e00b954fc 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsClientImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsClientImpl.java @@ -13,6 +13,7 @@ import com.azure.core.annotation.HostParam; import com.azure.core.annotation.PathParam; import com.azure.core.annotation.Put; +import com.azure.core.annotation.QueryParam; import com.azure.core.annotation.ReturnType; import com.azure.core.annotation.ServiceInterface; import com.azure.core.annotation.ServiceMethod; @@ -67,6 +68,7 @@ private interface MarketplaceAgreementsService { @UnexpectedResponseExceptionType(ManagementException.class) Mono> list( @HostParam("$host") String endpoint, + @QueryParam("api-version") String apiVersion, @PathParam("subscriptionId") String subscriptionId, @HeaderParam("Accept") String accept, Context context); @@ -77,6 +79,7 @@ Mono> list( @UnexpectedResponseExceptionType(ManagementException.class) Mono> create( @HostParam("$host") String endpoint, + @QueryParam("api-version") String apiVersion, @PathParam("subscriptionId") String subscriptionId, @BodyParam("application/json") ConfluentAgreementResourceInner body, @HeaderParam("Accept") String accept, @@ -117,7 +120,14 @@ private Mono> listSinglePageAsync final String accept = "application/json"; return FluxUtil .withContext( - context -> service.list(this.client.getEndpoint(), this.client.getSubscriptionId(), accept, context)) + context -> + service + .list( + this.client.getEndpoint(), + this.client.getApiVersion(), + this.client.getSubscriptionId(), + accept, + context)) .>map( res -> new PagedResponseBase<>( @@ -156,7 +166,12 @@ private Mono> listSinglePageAsync final String accept = "application/json"; context = this.client.mergeContext(context); return service - .list(this.client.getEndpoint(), this.client.getSubscriptionId(), accept, context) + .list( + this.client.getEndpoint(), + this.client.getApiVersion(), + this.client.getSubscriptionId(), + accept, + context) .map( res -> new PagedResponseBase<>( @@ -252,7 +267,14 @@ private Mono> createWithResponseAsync( return FluxUtil .withContext( context -> - service.create(this.client.getEndpoint(), this.client.getSubscriptionId(), body, accept, context)) + service + .create( + this.client.getEndpoint(), + this.client.getApiVersion(), + this.client.getSubscriptionId(), + body, + accept, + context)) .subscriberContext(context -> context.putAll(FluxUtil.toReactorContext(this.client.getContext()))); } @@ -286,7 +308,14 @@ private Mono> createWithResponseAsync( } final String accept = "application/json"; context = this.client.mergeContext(context); - return service.create(this.client.getEndpoint(), this.client.getSubscriptionId(), body, accept, context); + return service + .create( + this.client.getEndpoint(), + this.client.getApiVersion(), + this.client.getSubscriptionId(), + body, + accept, + context); } /** diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsImpl.java index e1ca8e98d931..8a452376d10a 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/MarketplaceAgreementsImpl.java @@ -30,12 +30,12 @@ public MarketplaceAgreementsImpl(MarketplaceAgreementsClient innerClient, Conflu public PagedIterable list() { PagedIterable inner = this.serviceClient().list(); - return inner.mapPage(inner1 -> new ConfluentAgreementResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new ConfluentAgreementResourceImpl(inner1, this.manager())); } public PagedIterable list(Context context) { PagedIterable inner = this.serviceClient().list(context); - return inner.mapPage(inner1 -> new ConfluentAgreementResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new ConfluentAgreementResourceImpl(inner1, this.manager())); } public ConfluentAgreementResource create() { diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationOperationsImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationOperationsImpl.java index c5ddf8ab6cfc..2e82c4d3b64e 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationOperationsImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationOperationsImpl.java @@ -28,12 +28,12 @@ public OrganizationOperationsImpl(OrganizationOperationsClient innerClient, Conf public PagedIterable list() { PagedIterable inner = this.serviceClient().list(); - return inner.mapPage(inner1 -> new OperationResultImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OperationResultImpl(inner1, this.manager())); } public PagedIterable list(Context context) { PagedIterable inner = this.serviceClient().list(context); - return inner.mapPage(inner1 -> new OperationResultImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OperationResultImpl(inner1, this.manager())); } private OrganizationOperationsClient serviceClient() { diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationResourceImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationResourceImpl.java index 6f96eec5e81f..63544c0c90be 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationResourceImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationResourceImpl.java @@ -195,11 +195,6 @@ public OrganizationResourceImpl withTags(Map tags) { } } - public OrganizationResourceImpl withProvisioningState(ProvisionState provisioningState) { - this.innerModel().withProvisioningState(provisioningState); - return this; - } - public OrganizationResourceImpl withOfferDetail(OfferDetail offerDetail) { this.innerModel().withOfferDetail(offerDetail); return this; diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationsImpl.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationsImpl.java index 1e4a81b684a2..9022095a0006 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationsImpl.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/OrganizationsImpl.java @@ -30,23 +30,23 @@ public OrganizationsImpl(OrganizationsClient innerClient, ConfluentManager servi public PagedIterable list() { PagedIterable inner = this.serviceClient().list(); - return inner.mapPage(inner1 -> new OrganizationResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OrganizationResourceImpl(inner1, this.manager())); } public PagedIterable list(Context context) { PagedIterable inner = this.serviceClient().list(context); - return inner.mapPage(inner1 -> new OrganizationResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OrganizationResourceImpl(inner1, this.manager())); } public PagedIterable listByResourceGroup(String resourceGroupName) { PagedIterable inner = this.serviceClient().listByResourceGroup(resourceGroupName); - return inner.mapPage(inner1 -> new OrganizationResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OrganizationResourceImpl(inner1, this.manager())); } public PagedIterable listByResourceGroup(String resourceGroupName, Context context) { PagedIterable inner = this.serviceClient().listByResourceGroup(resourceGroupName, context); - return inner.mapPage(inner1 -> new OrganizationResourceImpl(inner1, this.manager())); + return Utils.mapPage(inner, inner1 -> new OrganizationResourceImpl(inner1, this.manager())); } public OrganizationResource getByResourceGroup(String resourceGroupName, String organizationName) { diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/Utils.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/Utils.java index a66443cf089f..50fa4ce11cd4 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/Utils.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/implementation/Utils.java @@ -4,12 +4,20 @@ package com.azure.resourcemanager.confluent.implementation; +import com.azure.core.http.rest.PagedFlux; +import com.azure.core.http.rest.PagedIterable; +import com.azure.core.http.rest.PagedResponse; +import com.azure.core.http.rest.PagedResponseBase; import com.azure.core.util.CoreUtils; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import reactor.core.publisher.Mono; final class Utils { static String getValueFromIdByName(String id, String name) { @@ -64,4 +72,125 @@ static String getValueFromIdByParameterName(String id, String pathTemplate, Stri } return null; } + + static PagedIterable mapPage(PagedIterable pageIterable, Function mapper) { + return new PagedIterableImpl(pageIterable, mapper); + } + + private static final class PagedIterableImpl extends PagedIterable { + + private final PagedIterable pageIterable; + private final Function mapper; + private final Function, PagedResponse> pageMapper; + + private PagedIterableImpl(PagedIterable pageIterable, Function mapper) { + super(new PagedFlux(Mono::empty)); + this.pageIterable = pageIterable; + this.mapper = mapper; + this.pageMapper = + page -> + new PagedResponseBase( + page.getRequest(), + page.getStatusCode(), + page.getHeaders(), + page.getElements().stream().map(mapper).collect(Collectors.toList()), + page.getContinuationToken(), + null); + } + + @Override + public Stream stream() { + return pageIterable.stream().map(mapper); + } + + @Override + public Stream> streamByPage() { + return pageIterable.streamByPage().map(pageMapper); + } + + @Override + public Stream> streamByPage(String continuationToken) { + return pageIterable.streamByPage(continuationToken).map(pageMapper); + } + + @Override + public Stream> streamByPage(int preferredPageSize) { + return pageIterable.streamByPage(preferredPageSize).map(pageMapper); + } + + @Override + public Stream> streamByPage(String continuationToken, int preferredPageSize) { + return pageIterable.streamByPage(continuationToken, preferredPageSize).map(pageMapper); + } + + @Override + public Iterator iterator() { + return new IteratorImpl(pageIterable.iterator(), mapper); + } + + @Override + public Iterable> iterableByPage() { + return new IterableImpl, PagedResponse>(pageIterable.iterableByPage(), pageMapper); + } + + @Override + public Iterable> iterableByPage(String continuationToken) { + return new IterableImpl, PagedResponse>( + pageIterable.iterableByPage(continuationToken), pageMapper); + } + + @Override + public Iterable> iterableByPage(int preferredPageSize) { + return new IterableImpl, PagedResponse>( + pageIterable.iterableByPage(preferredPageSize), pageMapper); + } + + @Override + public Iterable> iterableByPage(String continuationToken, int preferredPageSize) { + return new IterableImpl, PagedResponse>( + pageIterable.iterableByPage(continuationToken, preferredPageSize), pageMapper); + } + } + + private static final class IteratorImpl implements Iterator { + + private final Iterator iterator; + private final Function mapper; + + private IteratorImpl(Iterator iterator, Function mapper) { + this.iterator = iterator; + this.mapper = mapper; + } + + @Override + public boolean hasNext() { + return iterator.hasNext(); + } + + @Override + public S next() { + return mapper.apply(iterator.next()); + } + + @Override + public void remove() { + iterator.remove(); + } + } + + private static final class IterableImpl implements Iterable { + + private final Iterable iterable; + private final Function mapper; + + private IterableImpl(Iterable iterable, Function mapper) { + this.iterable = iterable; + this.mapper = mapper; + } + + @Override + public Iterator iterator() { + return new IteratorImpl(iterable.iterator(), mapper); + } + } } diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResource.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResource.java index 10be35ce7d80..3eb5cd03f816 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResource.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResource.java @@ -155,10 +155,7 @@ interface WithResourceGroup { * resource to be created, but also allows for any other optional properties to be specified. */ interface WithCreate - extends DefinitionStages.WithTags, - DefinitionStages.WithProvisioningState, - DefinitionStages.WithOfferDetail, - DefinitionStages.WithUserDetail { + extends DefinitionStages.WithTags, DefinitionStages.WithOfferDetail, DefinitionStages.WithUserDetail { /** * Executes the create request. * @@ -184,16 +181,6 @@ interface WithTags { */ WithCreate withTags(Map tags); } - /** The stage of the OrganizationResource definition allowing to specify provisioningState. */ - interface WithProvisioningState { - /** - * Specifies the provisioningState property: Provision states for confluent RP. - * - * @param provisioningState Provision states for confluent RP. - * @return the next definition stage. - */ - WithCreate withProvisioningState(ProvisionState provisioningState); - } /** The stage of the OrganizationResource definition allowing to specify offerDetail. */ interface WithOfferDetail { /** diff --git a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResourceProperties.java b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResourceProperties.java index b25f6c0eddb8..8af21686833a 100644 --- a/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResourceProperties.java +++ b/sdk/confluent/azure-resourcemanager-confluent/src/main/java/com/azure/resourcemanager/confluent/models/OrganizationResourceProperties.java @@ -24,7 +24,7 @@ public class OrganizationResourceProperties { /* * Provision states for confluent RP */ - @JsonProperty(value = "provisioningState") + @JsonProperty(value = "provisioningState", access = JsonProperty.Access.WRITE_ONLY) private ProvisionState provisioningState; /* @@ -69,17 +69,6 @@ public ProvisionState provisioningState() { return this.provisioningState; } - /** - * Set the provisioningState property: Provision states for confluent RP. - * - * @param provisioningState the provisioningState value to set. - * @return the OrganizationResourceProperties object itself. - */ - public OrganizationResourceProperties withProvisioningState(ProvisionState provisioningState) { - this.provisioningState = provisioningState; - return this; - } - /** * Get the organizationId property: Id of the Confluent organization. * diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/configuration/AADSampleConfiguration.java b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/configuration/AADSampleConfiguration.java index c1f5d8b283bc..41ff9abd115e 100644 --- a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/configuration/AADSampleConfiguration.java +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/configuration/AADSampleConfiguration.java @@ -15,25 +15,13 @@ public class AADSampleConfiguration { @Bean - public OAuth2AuthorizedClientManager authorizedClientManager( - ClientRegistrationRepository clientRegistrationRepository, - OAuth2AuthorizedClientRepository authorizedClientRepository) { - OAuth2AuthorizedClientProvider authorizedClientProvider = - OAuth2AuthorizedClientProviderBuilder.builder() - .refreshToken() - .build(); - DefaultOAuth2AuthorizedClientManager authorizedClientManager = - new DefaultOAuth2AuthorizedClientManager(clientRegistrationRepository, authorizedClientRepository); - authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider); - return authorizedClientManager; - } - - @Bean - public WebClient webClient(OAuth2AuthorizedClientManager authorizedClientManager) { - ServletOAuth2AuthorizedClientExchangeFilterFunction oauth2Client = - new ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager); + public static WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository) { + ServletOAuth2AuthorizedClientExchangeFilterFunction function = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, + authorizedClientRepository); return WebClient.builder() - .apply(oauth2Client.oauth2Configuration()) + .apply(function.oauth2Configuration()) .build(); } } diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/controller/SampleController.java b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/controller/SampleController.java index 0ca6afc267e1..4c90780e4101 100644 --- a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/controller/SampleController.java +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server-obo/src/main/java/com/azure/spring/sample/aad/controller/SampleController.java @@ -28,7 +28,7 @@ public class SampleController { private static final String GRAPH_ME_ENDPOINT = "https://graph.microsoft.com/v1.0/me"; - private static final String CUSTOM_LOCAL_FILE_ENDPOINT = "http://localhost:8080/file"; + private static final String CUSTOM_LOCAL_FILE_ENDPOINT = "http://localhost:8082/file"; @Autowired private WebClient webClient; diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server/src/main/resources/application.yml b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server/src/main/resources/application.yml index e0beedb9ab07..bb6453e448e4 100644 --- a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server/src/main/resources/application.yml +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-resource-server/src/main/resources/application.yml @@ -2,6 +2,9 @@ # In v2.0 tokens, this is always the client ID of the API, while in v1.0 tokens it can be the client ID or the resource URI used in the request. # If you are using v1.0 tokens, configure both to properly complete the audience validation. +server: + port: 8082 + #azure: # activedirectory: # client-id: diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/config/WebClientConfig.java b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/config/WebClientConfig.java new file mode 100644 index 000000000000..846c28b5c15c --- /dev/null +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/config/WebClientConfig.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.sample.aad.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction; +import org.springframework.web.reactive.function.client.WebClient; + + +@Configuration +public class WebClientConfig { + + @Bean + public static WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, + OAuth2AuthorizedClientRepository authorizedClientRepository) { + ServletOAuth2AuthorizedClientExchangeFilterFunction function = + new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, + authorizedClientRepository); + return WebClient.builder() + .apply(function.oauth2Configuration()) + .build(); + } +} diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/controller/CallOboServerController.java b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/controller/CallOboServerController.java new file mode 100644 index 000000000000..1a5f1564fd19 --- /dev/null +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/java/com/azure/spring/sample/aad/controller/CallOboServerController.java @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.sample.aad.controller; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; +import org.springframework.security.oauth2.client.annotation.RegisteredOAuth2AuthorizedClient; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.reactive.function.client.WebClient; + + +import static org.springframework.security.oauth2.client.web.reactive.function.client.ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient; + +@Controller +public class CallOboServerController { + + private static final Logger LOGGER = LoggerFactory.getLogger(CallOboServerController.class); + + private static final String CUSTOM_LOCAL_FILE_ENDPOINT = "http://localhost:8081/call-custom"; + + @Autowired + private WebClient webClient; + + /** + * Call obo server, combine all the response and return. + * @param obo authorized client for Custom + * @return Response Graph and Custom data. + */ + @GetMapping("/obo") + @ResponseBody + public String callOboServer(@RegisteredOAuth2AuthorizedClient("obo") OAuth2AuthorizedClient obo) { + return callOboEndpoint(obo); + } + + /** + * Call obo local file endpoint + * @param obo Authorized Client + * @return Response string data. + */ + private String callOboEndpoint(OAuth2AuthorizedClient obo) { + if (null != obo) { + String body = webClient + .get() + .uri(CUSTOM_LOCAL_FILE_ENDPOINT) + .attributes(oauth2AuthorizedClient(obo)) + .retrieve() + .bodyToMono(String.class) + .block(); + LOGGER.info("Response from obo server: {}", body); + return "Obo server response " + (null != body ? "success." : "failed."); + } else { + return "Obo server response failed."; + } + } +} diff --git a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/resources/templates/index.html b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/resources/templates/index.html index 7fe764b65dcd..edacd62d1799 100644 --- a/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/resources/templates/index.html +++ b/sdk/spring/azure-spring-boot-samples/azure-spring-boot-sample-active-directory-webapp/src/main/resources/templates/index.html @@ -34,6 +34,7 @@

Azure Active Directory OAuth 2.0 Login with Spring Security

Group2 Message | Graph Client | Arm Client | + Obo Client | diff --git a/sdk/spring/azure-spring-boot/pom.xml b/sdk/spring/azure-spring-boot/pom.xml index bb4d8f5460e7..8a8aaa9ab0f9 100644 --- a/sdk/spring/azure-spring-boot/pom.xml +++ b/sdk/spring/azure-spring-boot/pom.xml @@ -270,6 +270,12 @@ spring-core 5.2.10.RELEASE
+ + org.springframework + spring-webflux + 5.2.10.RELEASE + true + org.apache.httpcomponents httpclient @@ -302,6 +308,7 @@ org.springframework:spring-core:[5.2.10.RELEASE] org.springframework:spring-web:[5.2.10.RELEASE] org.springframework:spring-jms:[5.2.10.RELEASE] + org.springframework:spring-webflux:[5.2.10.RELEASE] org.springframework.boot:spring-boot-actuator-autoconfigure:[2.3.5.RELEASE] org.springframework.boot:spring-boot-autoconfigure-processor:[2.3.5.RELEASE] org.springframework.boot:spring-boot-autoconfigure:[2.3.5.RELEASE] diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2OboAuthorizedClientRepository.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2OboAuthorizedClientRepository.java index 82c74fbaa251..5e649b9265b6 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2OboAuthorizedClientRepository.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapi/AADOAuth2OboAuthorizedClientRepository.java @@ -3,28 +3,42 @@ package com.azure.spring.aad.webapi; +import com.azure.spring.autoconfigure.aad.Constants; import com.microsoft.aad.msal4j.ClientCredentialFactory; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.IClientSecret; +import com.microsoft.aad.msal4j.MsalInteractionRequiredException; import com.microsoft.aad.msal4j.OnBehalfOfParameters; import com.microsoft.aad.msal4j.UserAssertion; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTParser; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatus; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.OAuth2AuthorizedClient; import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; import org.springframework.security.oauth2.core.OAuth2AccessToken; +import org.springframework.security.oauth2.core.OAuth2ErrorCodes; +import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; +import org.springframework.util.Assert; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.net.MalformedURLException; +import java.text.ParseException; import java.time.Instant; import java.util.Date; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ExecutionException; /** *

@@ -86,8 +100,18 @@ public T loadAuthorizedClient(String registra oAuth2AccessToken); request.setAttribute(oboAuthorizedClientAttributeName, (T) oAuth2AuthorizedClient); return (T) oAuth2AuthorizedClient; - } catch (Throwable throwable) { - LOGGER.error("Failed to load authorized client.", throwable); + } catch (ExecutionException exception) { + // Handle conditional access policy for obo flow. + // A user interaction is required, but we are in a web API, and therefore, we need to report back to the + // client through a 'WWW-Authenticate' header https://tools.ietf.org/html/rfc6750#section-3.1 + Optional.of(exception) + .map(Throwable::getCause) + .filter(e -> e instanceof MsalInteractionRequiredException) + .map(e -> (MsalInteractionRequiredException) e) + .ifPresent(this::replyForbiddenWithWwwAuthenticateHeader); + LOGGER.error("Failed to load authorized client.", exception); + } catch (InterruptedException | ParseException exception) { + LOGGER.error("Failed to load authorized client.", exception); } return null; } @@ -130,4 +154,18 @@ private String interceptAuthorizationUri(String authorizationUri) { } return null; } + + void replyForbiddenWithWwwAuthenticateHeader(MsalInteractionRequiredException exception) { + ServletRequestAttributes attr = + (ServletRequestAttributes) RequestContextHolder.currentRequestAttributes(); + HttpServletResponse response = attr.getResponse(); + Assert.notNull(response, "HttpServletResponse should not be null."); + response.setStatus(HttpStatus.FORBIDDEN.value()); + Map parameters = new LinkedHashMap<>(); + parameters.put(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS, exception.claims()); + parameters.put(OAuth2ParameterNames.ERROR, OAuth2ErrorCodes.INVALID_TOKEN); + parameters.put(OAuth2ParameterNames.ERROR_DESCRIPTION, "The resource server requires higher privileges than " + + "provided by the access token"); + response.addHeader(HttpHeaders.WWW_AUTHENTICATE, Constants.BEARER_PREFIX + parameters.toString()); + } } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADAuthenticationFailureHandler.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADAuthenticationFailureHandler.java deleted file mode 100644 index 2a9ddaeb78b9..000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADAuthenticationFailureHandler.java +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.aad.webapp; - -import com.azure.spring.autoconfigure.aad.Constants; -import org.springframework.security.core.AuthenticationException; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; -import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; -import org.springframework.security.web.savedrequest.DefaultSavedRequest; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; -import java.io.IOException; -import java.util.Optional; - -/** - * Redirect URL for handling OAuthentication failure - */ -public class AADAuthenticationFailureHandler implements AuthenticationFailureHandler { - private static final String DEFAULT_FAILURE_URL = "/login?error"; - private final AuthenticationFailureHandler defaultHandler; - - public AADAuthenticationFailureHandler() { - this.defaultHandler = new SimpleUrlAuthenticationFailureHandler(DEFAULT_FAILURE_URL); - } - - @Override - public void onAuthenticationFailure(HttpServletRequest request, - HttpServletResponse response, - AuthenticationException exception) throws IOException, ServletException { - String claims = Optional.of(exception) - .filter(e -> e instanceof OAuth2AuthenticationException) - .map(e -> (OAuth2AuthenticationException) e) - .map(OAuth2AuthenticationException::getError) - .filter(e -> e instanceof AADOAuth2Error) - .map(e -> (AADOAuth2Error) e) - .map(AADOAuth2Error::getClaims) - .orElse(null); - - if (claims == null) { - // Default handle logic - defaultHandler.onAuthenticationFailure(request, response, exception); - } else { - // Handle conditional access policy, step 2. - response.setStatus(302); - request.getSession().setAttribute(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS, claims); - String redirectUrl = Optional.of(request) - .map(HttpServletRequest::getSession) - .map(s -> s.getAttribute(Constants.SAVED_REQUEST)) - .map(r -> (DefaultSavedRequest) r) - .map(DefaultSavedRequest::getRedirectUrl) - .orElse(null); - response.sendRedirect(redirectUrl); - } - } -} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADHandleConditionalAccessFilter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADHandleConditionalAccessFilter.java new file mode 100644 index 000000000000..8a5a8fff81d7 --- /dev/null +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADHandleConditionalAccessFilter.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.azure.spring.aad.webapp; + +import com.azure.spring.autoconfigure.aad.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpHeaders; +import org.springframework.util.StringUtils; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.web.reactive.function.client.WebClientResponseException; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +/** + * Handle the {@link WebClientResponseException} in On-Behalf-Of flow. + * + *

+ * When the resource-server needs re-acquire token(The request requires higher privileges than provided by the access + * token in On-Behalf-Of flow.), it can sent a 403 with information in the WWW-Authenticate header to web client ,web + * client will throw {@link WebClientResponseException}, web-application can handle this exception to challenge the + * user. + */ +public class AADHandleConditionalAccessFilter extends OncePerRequestFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(AADHandleConditionalAccessFilter.class); + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws IOException, ServletException { + try { + filterChain.doFilter(request, response); + } catch (Exception exception) { + Map authParameters = + Optional.of(exception) + .map(Throwable::getCause) + .filter(e -> e instanceof WebClientResponseException) + .map(e -> (WebClientResponseException) e) + .map(WebClientResponseException::getHeaders) + .map(httpHeaders -> httpHeaders.get(HttpHeaders.WWW_AUTHENTICATE)) + .map(list -> list.get(0)) + .map(this::parseAuthParameters) + .orElse(null); + if (authParameters != null && authParameters.containsKey(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS)) { + request.getSession().setAttribute(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS, + authParameters.get(Constants.CONDITIONAL_ACCESS_POLICY_CLAIMS)); + response.setStatus(302); + try { + response.sendRedirect(Constants.DEFAULT_AUTHORITY_ENDPOINT_URI); + } catch (IOException e) { + LOGGER.error("Failed to redirect at this response.", exception); + } + return; + } + throw exception; + } + } + + private Map parseAuthParameters(String wwwAuthenticateHeader) { + return Stream.of(wwwAuthenticateHeader) + .filter(header -> !StringUtils.isEmpty(header)) + .filter(header -> header.startsWith(Constants.BEARER_PREFIX)) + .map(str -> str.substring(Constants.BEARER_PREFIX.length() + 1, str.length() - 1)) + .map(str -> str.split(", ")) + .flatMap(Stream::of) + .map(parameter -> parameter.split("=")) + .filter(parameter -> parameter.length > 1) + .collect(Collectors.toMap( + parameters -> parameters[0], + parameters -> parameters[1])); + } +} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2Error.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2Error.java deleted file mode 100644 index bdad533007d8..000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADOAuth2Error.java +++ /dev/null @@ -1,76 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.aad.webapp; - -import org.springframework.security.core.SpringSecurityCoreVersion; -import org.springframework.security.oauth2.core.OAuth2Error; - -/** - * Custom error with the error code returned by aad - */ -public class AADOAuth2Error extends OAuth2Error { - private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID; - - private final String errorCodes; - - private final String timestamp; - - private final String traceId; - - private final String correlationId; - - private final String subError; - - private final String claims; - - public AADOAuth2Error(String error, String errorDescription, String errorCodes, String timestamp, - String traceId, String correlationId, String errorUri, String subError, String claims) { - super(error, errorDescription, errorUri); - this.errorCodes = errorCodes; - this.timestamp = timestamp; - this.traceId = traceId; - this.correlationId = correlationId; - this.subError = subError; - this.claims = claims; - } - - - public final String getErrorCodes() { - return errorCodes; - } - - public final String getTimestamp() { - return timestamp; - } - - public final String getTraceId() { - return traceId; - } - - public final String getCorrelationId() { - return correlationId; - } - - public final String getSubError() { - return subError; - } - - public final String getClaims() { - return claims; - } - - - @Override - public String toString() { - - return "AADAuthenticationException{" - + ", error_codes='" + errorCodes + '\'' - + ", timestamp='" + timestamp + '\'' - + ", trace_id='" + traceId + '\'' - + ", correlation_id='" + correlationId + '\'' - + ", suberror='" + subError + '\'' - + ", claims='" + claims + '\'' - + '}'; - } -} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebAppConfiguration.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebAppConfiguration.java index 5d81dd95429b..e24505eef3c9 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebAppConfiguration.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebAppConfiguration.java @@ -121,6 +121,7 @@ private Set accessTokenScopes() { return result; } + private Set openidScopes() { Set result = new HashSet<>(); result.add("openid"); @@ -198,5 +199,4 @@ protected void configure(HttpSecurity http) throws Exception { .anyRequest().authenticated(); } } - } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebSecurityConfigurerAdapter.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebSecurityConfigurerAdapter.java index 09d00f40a062..b29a4f63ef28 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebSecurityConfigurerAdapter.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/AADWebSecurityConfigurerAdapter.java @@ -5,7 +5,6 @@ import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.converter.FormHttpMessageConverter; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.client.endpoint.DefaultAuthorizationCodeTokenResponseClient; @@ -15,19 +14,16 @@ import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler; import org.springframework.security.oauth2.client.userinfo.OAuth2UserService; import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver; -import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter; import org.springframework.security.oauth2.core.oidc.user.OidcUser; -import org.springframework.security.web.authentication.AuthenticationFailureHandler; +import org.springframework.security.web.access.ExceptionTranslationFilter; import org.springframework.security.web.authentication.logout.LogoutSuccessHandler; import org.springframework.util.StringUtils; -import org.springframework.web.client.RestTemplate; import java.net.URI; -import java.util.Arrays; /** - * Abstract configuration class, used to make AzureClientRegistrationRepository - * and AuthzCodeGrantRequestEntityConverter take effect. + * Abstract configuration class, used to make AzureClientRegistrationRepository and AuthzCodeGrantRequestEntityConverter + * take effect. */ public abstract class AADWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter { @@ -42,6 +38,9 @@ public abstract class AADWebSecurityConfigurerAdapter extends WebSecurityConfigu protected void configure(HttpSecurity http) throws Exception { // @formatter:off http.oauth2Login() + .authorizationEndpoint() + .authorizationRequestResolver(requestResolver()) + .and() .tokenEndpoint() .accessTokenResponseClient(accessTokenResponseClient()) .and() @@ -51,7 +50,8 @@ protected void configure(HttpSecurity http) throws Exception { .and() .logout() .logoutSuccessHandler(oidcLogoutSuccessHandler()) - .and(); + .and() + .addFilterBefore(new AADHandleConditionalAccessFilter(), ExceptionTranslationFilter.class); // @formatter:off } @@ -68,10 +68,6 @@ protected LogoutSuccessHandler oidcLogoutSuccessHandler() { protected OAuth2AccessTokenResponseClient accessTokenResponseClient() { DefaultAuthorizationCodeTokenResponseClient result = new DefaultAuthorizationCodeTokenResponseClient(); - RestTemplate restTemplate = new RestTemplate(Arrays.asList( - new FormHttpMessageConverter(), new OAuth2AccessTokenResponseHttpMessageConverter())); - restTemplate.setErrorHandler(new ConditionalAccessResponseErrorHandler()); - result.setRestOperations(restTemplate); result.setRequestEntityConverter( new AADOAuth2AuthorizationCodeGrantRequestEntityConverter(repo.getAzureClient())); return result; @@ -80,8 +76,4 @@ protected OAuth2AccessTokenResponseClient a protected OAuth2AuthorizationRequestResolver requestResolver() { return new AADOAuth2AuthorizationRequestResolver(this.repo); } - - protected AuthenticationFailureHandler failureHandler() { - return new AADAuthenticationFailureHandler(); - } } diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandler.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandler.java deleted file mode 100644 index 1acce08fb812..000000000000 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandler.java +++ /dev/null @@ -1,100 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.aad.webapp; - -import com.nimbusds.oauth2.sdk.token.BearerTokenError; -import org.springframework.core.convert.converter.Converter; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.security.oauth2.core.OAuth2Error; -import org.springframework.security.oauth2.core.OAuth2ErrorCodes; -import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMessageConverter; -import org.springframework.util.StringUtils; -import org.springframework.web.client.DefaultResponseErrorHandler; -import org.springframework.web.client.ResponseErrorHandler; - -import java.io.IOException; -import java.util.Map; - -/** - * Handle conditional access. - */ -public class ConditionalAccessResponseErrorHandler implements ResponseErrorHandler { - - private final OAuth2ErrorHttpMessageConverter oauth2ErrorConverter = new OAuth2ErrorHttpMessageConverter(); - - private final ResponseErrorHandler defaultErrorHandler = new DefaultResponseErrorHandler(); - - protected ConditionalAccessResponseErrorHandler() { - this.oauth2ErrorConverter.setErrorConverter(new AADOAuth2ErrorConverter()); - } - - @Override - public boolean hasError(ClientHttpResponse response) throws IOException { - return this.defaultErrorHandler.hasError(response); - } - - @Override - public void handleError(ClientHttpResponse response) throws IOException { - - if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) { - this.defaultErrorHandler.handleError(response); - } - - // A Bearer Token Error may be in the WWW-Authenticate response header - OAuth2Error oauth2Error = this.readErrorFromWwwAuthenticate(response.getHeaders()); - if (oauth2Error == null) { - oauth2Error = this.oauth2ErrorConverter.read(OAuth2Error.class, response); - } - /** - * Handle conditional access policy, step 1. - * https://docs.microsoft.com/en-us/azure/active-directory/conditional-access/howto-conditional-access-policy-all-users-mfa - */ - throw new OAuth2AuthorizationException(oauth2Error); - } - - private OAuth2Error readErrorFromWwwAuthenticate(HttpHeaders headers) { - String wwwAuthenticateHeader = headers.getFirst(HttpHeaders.WWW_AUTHENTICATE); - if (!StringUtils.hasText(wwwAuthenticateHeader)) { - return null; - } - - BearerTokenError bearerTokenError; - try { - bearerTokenError = BearerTokenError.parse(wwwAuthenticateHeader); - } catch (Exception ex) { - return null; - } - - String errorCode = bearerTokenError.getCode() != null - ? bearerTokenError.getCode() : OAuth2ErrorCodes.SERVER_ERROR; - String errorDescription = bearerTokenError.getDescription(); - - String errorUri = bearerTokenError.getURI() != null - ? bearerTokenError.getURI().toString() : null; - - return new OAuth2Error(errorCode, errorDescription, errorUri); - } - - - private static class AADOAuth2ErrorConverter implements Converter, OAuth2Error> { - @Override - public OAuth2Error convert(Map parameters) { - String errorCode = parameters.get("error"); - String description = parameters.get("error_description"); - String errorCodes = parameters.get("error_codes"); - String timestamp = parameters.get("timestamp"); - String traceId = parameters.get("trace_id"); - String correlationId = parameters.get("correlation_id"); - String uri = parameters.get("error_uri"); - String subError = parameters.get("suberror"); - String claims = parameters.get("claims"); - - return new AADOAuth2Error(errorCode, description, errorCodes, timestamp, traceId, correlationId, - uri, subError, claims); - } - } -} diff --git a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/Constants.java b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/Constants.java index 87be1cd2f4e2..e37205c1ca84 100644 --- a/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/Constants.java +++ b/sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/aad/Constants.java @@ -19,6 +19,7 @@ public class Constants { public static final Set DEFAULT_AUTHORITY_SET; public static final String ROLE_PREFIX = "ROLE_"; public static final String SAVED_REQUEST = "SPRING_SECURITY_SAVED_REQUEST"; + public static final String DEFAULT_AUTHORITY_ENDPOINT_URI = "/oauth2/authorization/azure"; static { Set authoritySet = new HashSet<>(); diff --git a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandlerTest.java b/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandlerTest.java deleted file mode 100644 index 8922efb79d57..000000000000 --- a/sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/aad/webapp/ConditionalAccessResponseErrorHandlerTest.java +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.azure.spring.aad.webapp; - -import org.junit.Assert; -import org.junit.Test; -import org.springframework.http.HttpStatus; -import org.springframework.http.client.ClientHttpResponse; -import org.springframework.mock.http.client.MockClientHttpResponse; -import org.springframework.security.oauth2.core.OAuth2AuthorizationException; -import org.springframework.web.client.HttpClientErrorException; -import org.springframework.web.client.ResponseErrorHandler; - -import java.io.IOException; -import java.util.Optional; - -public class ConditionalAccessResponseErrorHandlerTest { - private ResponseErrorHandler azureHandler = new ConditionalAccessResponseErrorHandler(); - private ClientHttpResponse clientHttpResponse = new MockClientHttpResponse(("{\n" - + " \"error\": \"fake_error\",\n" - + " \"error_description\": \" fake_error_description\",\n" - + " \"error_codes\": [\n" - + " 53001\n" - + " ],\n" - + " \"timestamp\": \"fake_timestamp\",\n" - + " \"trace_id\": \"fake_trace_id\",\n" - + " \"correlation_id\": \"fake_correlation_id\",\n" - + " \"error_uri\": \"fake_error_uri\",\n" - + " \"suberror\": \"message_only\",\n" - + " \"claims\": \"{\\\"access_token\\\":{\\\"fake_token\\\":{\\\"essential\\\":true," - + "\\\"values\\\":[\\\"fake_values\\\"]}}}\"\n" - + "}").getBytes(), HttpStatus.BAD_REQUEST); - - @Test - public void azureResponseErrorHandleTest() throws IOException { - AADOAuth2Error error = null; - try { - azureHandler.handleError(clientHttpResponse); - } catch (OAuth2AuthorizationException exception) { - error = (AADOAuth2Error) Optional.of(exception) - .map(OAuth2AuthorizationException::getError).orElse(null); - } - Assert.assertNotNull(error); - } - - @Test - public void defaultErrorHandlerTest() throws IOException { - clientHttpResponse = new MockClientHttpResponse("".getBytes(), HttpStatus.UNAUTHORIZED); - try { - azureHandler.handleError(clientHttpResponse); - } catch (HttpClientErrorException exception) { - Assert.assertNotNull(exception); - } - } -}