Skip to content

Commit

Permalink
Fix the resource group of spring cloud usage cannot be created automa…
Browse files Browse the repository at this point in the history
…tically issue (Azure#22039)
  • Loading branch information
Moary Chen committed Jun 28, 2021
1 parent 0b14153 commit 2279742
Show file tree
Hide file tree
Showing 28 changed files with 424 additions and 395 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ spring:

1. Verify in your app’s logs that a similar message was posted:

New message received: 'hello'
New message received: 'hello', partition key: 2002572479, sequence number: 4, offset: 768, enqueued time: 2021-06-03T01:47:36.859Z
Message 'hello' successfully checkpointed

1. Delete the resources on [Azure Portal][azure-portal] to avoid unexpected charges.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,9 +74,16 @@ Running this sample will be charged by Azure. You can check the usage and bill a

1. Delete the resources on [Azure Portal][azure-portal] to avoid unexpected charges.

## Troubleshooting

- Meet with `Creating topics with default partitions/replication factor are only supported in CreateTopicRequest version 4+` error.

```text
o.s.c.s.b.k.p.KafkaTopicProvisioner : Failed to create topics
org.apache.kafka.common.errors.UnsupportedVersionException: Creating topics with default partitions/replication factor are only supported in CreateTopicRequest version 4+. The following topics need values for partitions and replicas
```

## Troubleshooting
When this error is found, add this configuration item `spring.cloud.stream.kafka.binder.replicationFactor`, with the value set to at least 1. For more information, see [Spring Cloud Stream Kafka Binder Reference Guide](https://docs.spring.io/spring-cloud-stream-binder-kafka/docs/current/reference/html/spring-cloud-stream-binder-kafka.html).

## Next steps

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ public static void main(String[] args) {
public Consumer<Message<String>> consume() {
return message -> {
Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
LOGGER.info("New message received: '{}'", message);
LOGGER.info("New message received: '{}'", message.getPayload());
checkpointer.success().handle((r, ex) -> {
if (ex == null) {
LOGGER.info("Message '{}' successfully checkpointed", message);
LOGGER.info("Message '{}' successfully checkpointed", message.getPayload());
}
return null;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,10 @@ public static void main(String[] args) {
public Consumer<Message<String>> consume() {
return message -> {
Checkpointer checkpointer = (Checkpointer) message.getHeaders().get(CHECKPOINTER);
LOGGER.info("New message received: '{}'", message);
LOGGER.info("New message received: '{}'", message.getPayload());
checkpointer.success().handle((r, ex) -> {
if (ex == null) {
LOGGER.info("Message '{}' successfully checkpointed", message);
LOGGER.info("Message '{}' successfully checkpointed", message.getPayload());
}
return null;
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ protected void doFilterInternal(HttpServletRequest request, HttpServletResponse
*/
private Map<String, String> parseAuthParameters(String wwwAuthenticateHeader) {
return Stream.of(wwwAuthenticateHeader)
.filter(header -> !StringUtils.isEmpty(header))
.filter(header -> StringUtils.hasText(header))
.filter(header -> header.startsWith(Constants.BEARER_PREFIX))
.map(str -> str.substring(Constants.BEARER_PREFIX.length() + 1, str.length() - 1))
.map(str -> str.split(", "))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,7 @@ public TokenCredential getCredentials(String normalizedName) {
// Use certificate to authenticate
// Password can be empty
if (clientId != null && tenantId != null && certificatePath != null) {
if (StringUtils.isEmpty(certificatePassword)) {
if (!StringUtils.hasText(certificatePassword)) {
return new ClientCertificateCredentialBuilder()
.tenantId(tenantId)
.clientId(clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@

import com.azure.spring.autoconfigure.aad.AADAuthenticationProperties;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestInstance;
Expand All @@ -14,7 +13,6 @@
import org.springframework.security.oauth2.core.OAuth2AccessToken;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
Expand All @@ -28,13 +26,11 @@ public class AADAccessTokenGroupRolesExtractionTest {
private static final String GROUP_ID_1 = "d07c0bd6-4aab-45ac-b87c-23e8d00194ab";
private static final String GROUP_ID_2 = "6eddcc22-a24a-4459-b036-b9d9fc0f0bc7";

private final AADAuthenticationProperties properties = new AADAuthenticationProperties();
private final AADAuthenticationProperties.UserGroupProperties userGroup =
new AADAuthenticationProperties.UserGroupProperties();
private AutoCloseable autoCloseable;

@Mock
private OAuth2AccessToken accessToken;

@Mock
private GraphClient graphClient;

Expand All @@ -50,31 +46,34 @@ public void setup() {
groupIdsFromGraph.add(GROUP_ID_2);
groupInformationFromGraph.setGroupsIds(groupIdsFromGraph);
groupInformationFromGraph.setGroupsNames(groupNamesFromGraph);
properties.setUserGroup(userGroup);
properties.setGraphMembershipUri("https://graph.microsoft.com/v1.0/me/memberOf");
Mockito.lenient().when(accessToken.getTokenValue())
.thenReturn("fake-access-token");
Mockito.lenient().when(graphClient.getGroupInformation(accessToken.getTokenValue()))
.thenReturn(groupInformationFromGraph);
}

@AfterEach
public void reset() {
userGroup.setAllowedGroupNames(Collections.emptyList());
userGroup.setAllowedGroupIds(Collections.emptySet());
userGroup.setEnableFullList(false);
}

@AfterAll
public void close() throws Exception {
this.autoCloseable.close();
}

private AADAuthenticationProperties getProperties() {
AADAuthenticationProperties properties = new AADAuthenticationProperties();
AADAuthenticationProperties.UserGroupProperties userGroup =
new AADAuthenticationProperties.UserGroupProperties();
properties.setUserGroup(userGroup);
properties.setGraphMembershipUri("https://graph.microsoft.com/v1.0/me/memberOf");
return properties;
}

@Test
public void testAllowedGroupsNames() {
List<String> allowedGroupNames = new ArrayList<>();
allowedGroupNames.add("group1");
userGroup.setAllowedGroupNames(allowedGroupNames);

AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setAllowedGroupNames(allowedGroupNames);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(1);
Expand All @@ -86,7 +85,10 @@ public void testAllowedGroupsNames() {
public void testAllowedGroupsIds() {
Set<String> allowedGroupIds = new HashSet<>();
allowedGroupIds.add(GROUP_ID_1);
userGroup.setAllowedGroupIds(allowedGroupIds);

AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setAllowedGroupIds(allowedGroupIds);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(1);
Expand All @@ -100,8 +102,12 @@ public void testAllowedGroupsNamesAndAllowedGroupsIds() {
allowedGroupIds.add(GROUP_ID_1);
List<String> allowedGroupNames = new ArrayList<>();
allowedGroupNames.add("group1");
userGroup.setAllowedGroupIds(allowedGroupIds);
userGroup.setAllowedGroupNames(allowedGroupNames);


AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setAllowedGroupIds(allowedGroupIds);
properties.getUserGroup().setAllowedGroupNames(allowedGroupNames);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(2);
Expand All @@ -117,9 +123,12 @@ public void testWithEnableFullList() {
allowedGroupIds.add(GROUP_ID_1);
List<String> allowedGroupNames = new ArrayList<>();
allowedGroupNames.add("group1");
userGroup.setAllowedGroupIds(allowedGroupIds);
userGroup.setAllowedGroupNames(allowedGroupNames);
userGroup.setEnableFullList(true);

AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setAllowedGroupIds(allowedGroupIds);
properties.getUserGroup().setAllowedGroupNames(allowedGroupNames);
properties.getUserGroup().setEnableFullList(true);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(3);
Expand All @@ -134,9 +143,12 @@ public void testWithoutEnableFullList() {
Set<String> allowedGroupIds = new HashSet<>();
allowedGroupIds.add(GROUP_ID_1);
allowedGroupNames.add("group1");
userGroup.setEnableFullList(false);
userGroup.setAllowedGroupIds(allowedGroupIds);
userGroup.setAllowedGroupNames(allowedGroupNames);

AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setEnableFullList(false);
properties.getUserGroup().setAllowedGroupIds(allowedGroupIds);
properties.getUserGroup().setAllowedGroupNames(allowedGroupNames);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(2);
Expand All @@ -152,9 +164,12 @@ public void testAllowedGroupIdsAllWithoutEnableFullList() {
allowedGroupIds.add("all");
List<String> allowedGroupNames = new ArrayList<>();
allowedGroupNames.add("group1");
userGroup.setAllowedGroupIds(allowedGroupIds);
userGroup.setAllowedGroupNames(allowedGroupNames);
userGroup.setEnableFullList(false);

AADAuthenticationProperties properties = getProperties();
properties.getUserGroup().setAllowedGroupIds(allowedGroupIds);
properties.getUserGroup().setAllowedGroupNames(allowedGroupNames);
properties.getUserGroup().setEnableFullList(false);

AADOAuth2UserService userService = new AADOAuth2UserService(properties, graphClient);
Set<String> groupRoles = userService.extractGroupRolesFromAccessToken(accessToken);
assertThat(groupRoles).hasSize(3);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,30 +18,34 @@

public class AADIdTokenRolesExtractionTest {

private OidcIdToken idToken = mock(OidcIdToken.class);
private AADAuthenticationProperties properties = mock(AADAuthenticationProperties.class);
private AADOAuth2UserService userService = new AADOAuth2UserService(properties);
private AADOAuth2UserService getUserService() {
AADAuthenticationProperties properties = mock(AADAuthenticationProperties.class);
return new AADOAuth2UserService(properties);
}

@Test
public void testNoRolesClaim() {
OidcIdToken idToken = mock(OidcIdToken.class);
when(idToken.getClaim(ROLES)).thenReturn(null);
Set<String> authorityStrings = userService.extractRolesFromIdToken(idToken);
Set<String> authorityStrings = getUserService().extractRolesFromIdToken(idToken);
assertThat(authorityStrings).hasSize(0);
}

@Test
public void testRolesClaimAsList() {
OidcIdToken idToken = mock(OidcIdToken.class);
JSONArray rolesClaim = new JSONArray().appendElement("Admin");
when(idToken.getClaim(ROLES)).thenReturn(rolesClaim);
Set<String> authorityStrings = userService.extractRolesFromIdToken(idToken);
Set<String> authorityStrings = getUserService().extractRolesFromIdToken(idToken);
assertThat(authorityStrings).hasSize(1);
}

@Test
public void testRolesClaimIllegal() {
OidcIdToken idToken = mock(OidcIdToken.class);
Set<String> rolesClaim = new HashSet<>(Arrays.asList("Admin"));
when(idToken.getClaim(ROLES)).thenReturn(rolesClaim);
Set<String> authorityStrings = userService.extractRolesFromIdToken(idToken);
Set<String> authorityStrings = getUserService().extractRolesFromIdToken(idToken);
assertThat(authorityStrings).hasSize(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import org.hamcrest.Matcher;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
Expand All @@ -25,27 +24,22 @@

public class AADOAuth2AuthorizationCodeGrantRequestEntityConverterTest {

private AADWebAppClientRegistrationRepository clientRepo;
private ClientRegistration azure;
private ClientRegistration arm;

private final WebApplicationContextRunner contextRunner = WebApplicationContextRunnerUtils
.getContextRunnerWithRequiredProperties().withPropertyValues(
"azure.activedirectory.base-uri = fake-uri",
"azure.activedirectory.authorization-clients.arm.scopes = Calendars.Read",
"azure.activedirectory.authorization-clients.arm.on-demand=true");

private void getBeans(AssertableWebApplicationContext context) {
clientRepo = context.getBean(AADWebAppClientRegistrationRepository.class);
azure = clientRepo.findByRegistrationId("azure");
arm = clientRepo.findByRegistrationId("arm");
private WebApplicationContextRunner getContextRunner() {
return WebApplicationContextRunnerUtils
.getContextRunnerWithRequiredProperties()
.withPropertyValues(
"azure.activedirectory.base-uri = fake-uri",
"azure.activedirectory.authorization-clients.arm.scopes = Calendars.Read",
"azure.activedirectory.authorization-clients.arm.on-demand=true");
}

@Test
public void addScopeForDefaultClient() {
contextRunner.run(context -> {
getBeans(context);
MultiValueMap<String, String> body = convertedBodyOf(createCodeGrantRequest(azure));
getContextRunner().run(context -> {
AADWebAppClientRegistrationRepository clientRepo =
context.getBean(AADWebAppClientRegistrationRepository.class);
ClientRegistration azure = clientRepo.findByRegistrationId("azure");
MultiValueMap<String, String> body = convertedBodyOf(clientRepo, createCodeGrantRequest(azure));
assertEquals(
"openid profile offline_access",
body.getFirst("scope")
Expand All @@ -55,34 +49,41 @@ public void addScopeForDefaultClient() {

@Test
public void addScopeForOnDemandClient() {
contextRunner.run(context -> {
getBeans(context);
MultiValueMap<String, String> body = convertedBodyOf(createCodeGrantRequest(arm));
getContextRunner().run(context -> {
AADWebAppClientRegistrationRepository clientRepo =
context.getBean(AADWebAppClientRegistrationRepository.class);
ClientRegistration arm = clientRepo.findByRegistrationId("arm");
MultiValueMap<String, String> body = convertedBodyOf(clientRepo, createCodeGrantRequest(arm));
assertEquals("Calendars.Read openid profile", body.getFirst("scope"));
});
}

@Test
@SuppressWarnings("unchecked")
public void addHeadersForDefaultClient() {
contextRunner.run(context -> {
getBeans(context);
HttpHeaders httpHeaders = convertedHeaderOf(createCodeGrantRequest(azure));
assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders()));
getContextRunner().run(context -> {
AADWebAppClientRegistrationRepository clientRepo =
context.getBean(AADWebAppClientRegistrationRepository.class);
ClientRegistration azure = clientRepo.findByRegistrationId("azure");
HttpHeaders httpHeaders = convertedHeaderOf(clientRepo, createCodeGrantRequest(azure));
assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders(clientRepo)));
});
}

@Test
@SuppressWarnings("unchecked")
public void addHeadersForOnDemandClient() {
contextRunner.run(context -> {
getBeans(context);
HttpHeaders httpHeaders = convertedHeaderOf(createCodeGrantRequest(arm));
assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders()));
getContextRunner().run(context -> {
AADWebAppClientRegistrationRepository clientRepo =
context.getBean(AADWebAppClientRegistrationRepository.class);
ClientRegistration arm = clientRepo.findByRegistrationId("arm");
HttpHeaders httpHeaders = convertedHeaderOf(clientRepo, createCodeGrantRequest(arm));
assertThat(httpHeaders.entrySet(), (Matcher) hasItems(expectedHeaders(clientRepo)));
});
}

private HttpHeaders convertedHeaderOf(OAuth2AuthorizationCodeGrantRequest request) {
private HttpHeaders convertedHeaderOf(AADWebAppClientRegistrationRepository clientRepo,
OAuth2AuthorizationCodeGrantRequest request) {
AADOAuth2AuthorizationCodeGrantRequestEntityConverter converter =
new AADOAuth2AuthorizationCodeGrantRequestEntityConverter(clientRepo.getAzureClient());
RequestEntity<?> entity = converter.convert(request);
Expand All @@ -91,7 +92,7 @@ private HttpHeaders convertedHeaderOf(OAuth2AuthorizationCodeGrantRequest reques
.orElse(null);
}

private Object[] expectedHeaders() {
private Object[] expectedHeaders(AADWebAppClientRegistrationRepository clientRepo) {
return new AADOAuth2AuthorizationCodeGrantRequestEntityConverter(clientRepo.getAzureClient())
.getHttpHeaders()
.entrySet()
Expand All @@ -100,7 +101,8 @@ private Object[] expectedHeaders() {
.toArray();
}

private MultiValueMap<String, String> convertedBodyOf(OAuth2AuthorizationCodeGrantRequest request) {
private MultiValueMap<String, String> convertedBodyOf(AADWebAppClientRegistrationRepository clientRepo,
OAuth2AuthorizationCodeGrantRequest request) {
AADOAuth2AuthorizationCodeGrantRequestEntityConverter converter =
new AADOAuth2AuthorizationCodeGrantRequestEntityConverter(clientRepo.getAzureClient());
RequestEntity<?> entity = converter.convert(request);
Expand Down
Loading

0 comments on commit 2279742

Please sign in to comment.