Skip to content

Commit 5b5d822

Browse files
author
Moary Chen
authored
Optimization of Spring AAD B2C configuration condition (Azure#21089)
1 parent 6df89d4 commit 5b5d822

File tree

8 files changed

+196
-39
lines changed

8 files changed

+196
-39
lines changed

sdk/spring/azure-spring-boot-starter-active-directory-b2c/CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
### New Features
55
- Upgrade to [spring-boot-dependencies:2.4.5](https://repo.maven.apache.org/maven2/org/springframework/boot/spring-boot-dependencies/2.4.5/spring-boot-dependencies-2.4.5.pom).
66
- Upgrade to [spring-cloud-dependencies:2020.0.2](https://repo.maven.apache.org/maven2/org/springframework/cloud/spring-cloud-dependencies/2020.0.2/spring-cloud-dependencies-2020.0.2.pom).
7-
7+
- Support OAuth 2.0 Client Credentials Flow.
88

99

1010
## 3.4.0 (2021-04-19)

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import com.azure.spring.telemetry.TelemetrySender;
66
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
7+
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
78
import org.springframework.boot.context.properties.EnableConfigurationProperties;
89
import org.springframework.context.annotation.Bean;
910
import org.springframework.context.annotation.Conditional;
@@ -27,6 +28,7 @@
2728
* and import {@link AADB2COAuth2ClientConfiguration} class for AAD B2C OAuth2 client support.
2829
*/
2930
@Configuration
31+
@ConditionalOnResource(resources = "classpath:aadb2c.enable.config")
3032
@Conditional({ AADB2CConditions.CommonCondition.class, AADB2CConditions.UserFlowCondition.class })
3133
@EnableConfigurationProperties(AADB2CProperties.class)
3234
@Import(AADB2COAuth2ClientConfiguration.class)

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CConditions.java

Lines changed: 59 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,18 @@
33
package com.azure.spring.autoconfigure.b2c;
44

55
import org.springframework.boot.autoconfigure.condition.AnyNestedCondition;
6+
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
67
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
78
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
8-
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
99
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
1010
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
1111
import org.springframework.boot.context.properties.bind.Binder;
1212
import org.springframework.context.annotation.ConditionContext;
1313
import org.springframework.core.type.AnnotatedTypeMetadata;
1414
import org.springframework.util.CollectionUtils;
1515

16+
import java.util.Map;
17+
1618
/**
1719
* Conditions for activating AAD B2C beans.
1820
*/
@@ -30,7 +32,6 @@ static final class CommonCondition extends AnyNestedCondition {
3032
* Web application scenario condition.
3133
*/
3234
@ConditionalOnWebApplication
33-
@ConditionalOnResource(resources = "classpath:aadb2c.enable.config")
3435
@ConditionalOnProperty(
3536
prefix = AADB2CProperties.PREFIX,
3637
value = {
@@ -46,7 +47,6 @@ static class WebAppMode {
4647
* Web resource server scenario condition.
4748
*/
4849
@ConditionalOnWebApplication
49-
@ConditionalOnResource(resources = "classpath:aadb2c.enable.config")
5050
@ConditionalOnProperty(prefix = AADB2CProperties.PREFIX, value = { "tenant-id" })
5151
static class WebApiMode {
5252

@@ -61,12 +61,28 @@ static final class ClientRegistrationCondition extends SpringBootCondition {
6161
@Override
6262
public ConditionOutcome getMatchOutcome(final ConditionContext context,
6363
final AnnotatedTypeMetadata metadata) {
64-
AADB2CProperties aadb2CProperties = Binder.get(context.getEnvironment())
65-
.bind("azure.activedirectory.b2c", AADB2CProperties.class)
66-
.orElseGet(AADB2CProperties::new);
67-
return new ConditionOutcome(!CollectionUtils.isEmpty(aadb2CProperties.getUserFlows())
68-
|| !CollectionUtils.isEmpty(aadb2CProperties.getAuthorizationClients()),
69-
"Configure at least one attribute 'user-flow' or 'authorization-clients'.");
64+
ConditionMessage.Builder message = ConditionMessage.forCondition(
65+
"AAD B2C OAuth 2.0 Clients Configured Condition");
66+
AADB2CProperties aadb2CProperties = getAADB2CProperties(context);
67+
if (aadb2CProperties == null) {
68+
return ConditionOutcome.noMatch(message.notAvailable("aad b2c properties"));
69+
}
70+
71+
if (CollectionUtils.isEmpty(aadb2CProperties.getUserFlows())
72+
&& CollectionUtils.isEmpty(aadb2CProperties.getAuthorizationClients())) {
73+
return ConditionOutcome.noMatch(message.didNotFind("registered clients")
74+
.items("user-flows", "authorization-clients"));
75+
}
76+
77+
StringBuilder details = new StringBuilder();
78+
if (!CollectionUtils.isEmpty(aadb2CProperties.getUserFlows())) {
79+
details.append(getConditionResult("user-flows", aadb2CProperties.getUserFlows()));
80+
}
81+
if (!CollectionUtils.isEmpty(aadb2CProperties.getAuthorizationClients())) {
82+
details.append(getConditionResult("authorization-clients",
83+
aadb2CProperties.getAuthorizationClients()));
84+
}
85+
return ConditionOutcome.match(message.foundExactly(details.toString()));
7086
}
7187
}
7288

@@ -78,11 +94,40 @@ static final class UserFlowCondition extends SpringBootCondition {
7894
@Override
7995
public ConditionOutcome getMatchOutcome(final ConditionContext context,
8096
final AnnotatedTypeMetadata metadata) {
81-
AADB2CProperties aadb2CProperties = Binder.get(context.getEnvironment())
82-
.bind("azure.activedirectory.b2c", AADB2CProperties.class)
83-
.orElseGet(AADB2CProperties::new);
84-
return new ConditionOutcome(!CollectionUtils.isEmpty(aadb2CProperties.getUserFlows()),
85-
"Configure at least one attribute 'user-flow'.");
97+
ConditionMessage.Builder message = ConditionMessage.forCondition(
98+
"AAD B2C User Flow Clients Configured Condition");
99+
AADB2CProperties aadb2CProperties = getAADB2CProperties(context);
100+
if (aadb2CProperties == null) {
101+
return ConditionOutcome.noMatch(message.notAvailable("aad b2c properties"));
102+
}
103+
104+
if (CollectionUtils.isEmpty(aadb2CProperties.getUserFlows())) {
105+
return ConditionOutcome.noMatch(message.didNotFind("user flows").atAll());
106+
}
107+
108+
return ConditionOutcome.match(message.foundExactly(
109+
getConditionResult("user-flows", aadb2CProperties.getUserFlows())));
86110
}
87111
}
112+
113+
/**
114+
* Return the bound AADB2CProperties instance.
115+
* @param context Condition context
116+
* @return AADB2CProperties instance
117+
*/
118+
private static AADB2CProperties getAADB2CProperties(ConditionContext context) {
119+
return Binder.get(context.getEnvironment())
120+
.bind("azure.activedirectory.b2c", AADB2CProperties.class)
121+
.orElse(null);
122+
}
123+
124+
/**
125+
* Return combined name and the string of the keys of the map which concatenated with ','.
126+
* @param name name to concatenate
127+
* @param map Map to concatenate
128+
* @return the concatenated string.
129+
*/
130+
private static String getConditionResult(String name, Map<String, ?> map) {
131+
return name + ": " + String.join(", ", map.keySet()) + " ";
132+
}
88133
}

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2COAuth2ClientConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import org.slf4j.LoggerFactory;
88
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
99
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
10+
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
1011
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1112
import org.springframework.context.annotation.Bean;
1213
import org.springframework.context.annotation.Conditional;
@@ -31,6 +32,7 @@
3132
* Configuration for AAD B2C OAuth2 client support, when depends on the Spring OAuth2 Client module.
3233
*/
3334
@Configuration
35+
@ConditionalOnResource(resources = "classpath:aadb2c.enable.config")
3436
@Conditional({ AADB2CConditions.CommonCondition.class, AADB2CConditions.ClientRegistrationCondition.class })
3537
@EnableConfigurationProperties(AADB2CProperties.class)
3638
@ConditionalOnClass({ OAuth2LoginAuthenticationFilter.class })

sdk/spring/azure-spring-boot/src/main/java/com/azure/spring/autoconfigure/b2c/AADB2CResourceServerAutoConfiguration.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
import com.nimbusds.jwt.proc.JWTProcessor;
1414
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
1515
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
16+
import org.springframework.boot.autoconfigure.condition.ConditionalOnResource;
1617
import org.springframework.boot.context.properties.EnableConfigurationProperties;
1718
import org.springframework.context.annotation.Bean;
1819
import org.springframework.context.annotation.Conditional;
@@ -38,6 +39,7 @@
3839
* and import {@link AADB2COAuth2ClientConfiguration} class for AAD B2C OAuth2 client support.
3940
*/
4041
@Configuration
42+
@ConditionalOnResource(resources = "classpath:aadb2c.enable.config")
4143
@Conditional(AADB2CConditions.CommonCondition.class)
4244
@ConditionalOnClass(BearerTokenAuthenticationToken.class)
4345
@EnableConfigurationProperties(AADB2CProperties.class)

sdk/spring/azure-spring-boot/src/test/java/com/azure/spring/autoconfigure/b2c/AADB2CAutoConfigurationTest.java

Lines changed: 75 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,42 +2,57 @@
22
// Licensed under the MIT License.
33
package com.azure.spring.autoconfigure.b2c;
44

5+
import org.jetbrains.annotations.NotNull;
56
import org.junit.jupiter.api.Assertions;
67
import org.junit.jupiter.api.Test;
8+
import org.mockito.MockedStatic;
9+
import org.mockito.Mockito;
10+
import org.springframework.beans.BeanUtils;
711
import org.springframework.boot.autoconfigure.AutoConfigurations;
812
import org.springframework.boot.test.context.FilteredClassLoader;
913
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
14+
import org.springframework.core.io.ClassPathResource;
1015
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
1116

1217
import java.util.Arrays;
1318
import java.util.HashSet;
1419
import java.util.Map;
1520
import java.util.Set;
1621

22+
import static org.mockito.ArgumentMatchers.any;
23+
import static org.mockito.Mockito.atLeastOnce;
24+
import static org.mockito.Mockito.mock;
25+
import static org.mockito.Mockito.mockStatic;
26+
import static org.mockito.Mockito.never;
27+
import static org.mockito.Mockito.spy;
28+
import static org.mockito.Mockito.verify;
29+
1730
public class AADB2CAutoConfigurationTest extends AbstractAADB2COAuth2ClientTestConfiguration {
1831

1932
public AADB2CAutoConfigurationTest() {
2033
contextRunner = new WebApplicationContextRunner()
2134
.withConfiguration(AutoConfigurations.of(WebOAuth2ClientApp.class, AADB2CAutoConfiguration.class))
2235
.withClassLoader(new FilteredClassLoader(BearerTokenAuthenticationToken.class))
23-
.withPropertyValues(
24-
String.format("%s=%s", AADB2CConstants.BASE_URI, AADB2CConstants.TEST_BASE_URI),
25-
String.format("%s=%s", AADB2CConstants.TENANT_ID, AADB2CConstants.TEST_TENANT_ID),
26-
String.format("%s=%s", AADB2CConstants.CLIENT_ID, AADB2CConstants.TEST_CLIENT_ID),
27-
String.format("%s=%s", AADB2CConstants.CLIENT_SECRET, AADB2CConstants.TEST_CLIENT_SECRET),
28-
String.format("%s=%s", AADB2CConstants.LOGOUT_SUCCESS_URL, AADB2CConstants.TEST_LOGOUT_SUCCESS_URL),
29-
String.format("%s=%s", AADB2CConstants.LOGIN_FLOW, AADB2CConstants.TEST_KEY_SIGN_UP_OR_IN),
30-
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
31-
AADB2CConstants.TEST_KEY_SIGN_UP_OR_IN, AADB2CConstants.TEST_SIGN_UP_OR_IN_NAME),
32-
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
33-
AADB2CConstants.TEST_KEY_SIGN_IN, AADB2CConstants.TEST_SIGN_IN_NAME),
34-
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
35-
AADB2CConstants.TEST_KEY_SIGN_UP, AADB2CConstants.TEST_SIGN_UP_NAME),
36-
String.format("%s=%s", AADB2CConstants.CONFIG_PROMPT, AADB2CConstants.TEST_PROMPT),
37-
String.format("%s=%s", AADB2CConstants.CONFIG_LOGIN_HINT, AADB2CConstants.TEST_LOGIN_HINT),
38-
String.format("%s=%s", AADB2CConstants.USER_NAME_ATTRIBUTE_NAME, AADB2CConstants.TEST_ATTRIBUTE_NAME),
39-
String.format("%s=%s", AADB2CConstants.USER_NAME_ATTRIBUTE_NAME, AADB2CConstants.TEST_ATTRIBUTE_NAME)
40-
);
36+
.withPropertyValues(getWebappCommonPropertyValues());
37+
}
38+
39+
@NotNull
40+
private String[] getWebappCommonPropertyValues() {
41+
return new String[] { String.format("%s=%s", AADB2CConstants.BASE_URI, AADB2CConstants.TEST_BASE_URI),
42+
String.format("%s=%s", AADB2CConstants.TENANT_ID, AADB2CConstants.TEST_TENANT_ID),
43+
String.format("%s=%s", AADB2CConstants.CLIENT_ID, AADB2CConstants.TEST_CLIENT_ID),
44+
String.format("%s=%s", AADB2CConstants.CLIENT_SECRET, AADB2CConstants.TEST_CLIENT_SECRET),
45+
String.format("%s=%s", AADB2CConstants.LOGOUT_SUCCESS_URL, AADB2CConstants.TEST_LOGOUT_SUCCESS_URL),
46+
String.format("%s=%s", AADB2CConstants.LOGIN_FLOW, AADB2CConstants.TEST_KEY_SIGN_UP_OR_IN),
47+
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
48+
AADB2CConstants.TEST_KEY_SIGN_UP_OR_IN, AADB2CConstants.TEST_SIGN_UP_OR_IN_NAME),
49+
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
50+
AADB2CConstants.TEST_KEY_SIGN_IN, AADB2CConstants.TEST_SIGN_IN_NAME),
51+
String.format("%s.%s=%s", AADB2CConstants.USER_FLOWS,
52+
AADB2CConstants.TEST_KEY_SIGN_UP, AADB2CConstants.TEST_SIGN_UP_NAME),
53+
String.format("%s=%s", AADB2CConstants.CONFIG_PROMPT, AADB2CConstants.TEST_PROMPT),
54+
String.format("%s=%s", AADB2CConstants.CONFIG_LOGIN_HINT, AADB2CConstants.TEST_LOGIN_HINT),
55+
String.format("%s=%s", AADB2CConstants.USER_NAME_ATTRIBUTE_NAME, AADB2CConstants.TEST_ATTRIBUTE_NAME) };
4156
}
4257

4358
@Test
@@ -88,4 +103,46 @@ public void testLogoutSuccessHandlerBean() {
88103
Assertions.assertNotNull(handler);
89104
});
90105
}
106+
107+
@Test
108+
public void testWebappConditionsIsInvokedWhenAADB2CEnableFileExists() {
109+
try (MockedStatic<BeanUtils> beanUtils = mockStatic(BeanUtils.class, Mockito.CALLS_REAL_METHODS)) {
110+
AADB2CConditions.UserFlowCondition userFlowCondition = spy(AADB2CConditions.UserFlowCondition.class);
111+
AADB2CConditions.ClientRegistrationCondition clientRegistrationCondition =
112+
spy(AADB2CConditions.ClientRegistrationCondition.class);
113+
beanUtils.when(() -> BeanUtils.instantiateClass(AADB2CConditions.UserFlowCondition.class))
114+
.thenReturn(userFlowCondition);
115+
beanUtils.when(() -> BeanUtils.instantiateClass(AADB2CConditions.ClientRegistrationCondition.class))
116+
.thenReturn(clientRegistrationCondition);
117+
this.contextRunner
118+
.run(c -> {
119+
Assertions.assertTrue(c.getResource(AAD_B2C_ENABLE_CONFIG_FILE_NAME).exists());
120+
verify(userFlowCondition, atLeastOnce()).getMatchOutcome(any(), any());
121+
verify(clientRegistrationCondition, atLeastOnce()).getMatchOutcome(any(), any());
122+
});
123+
}
124+
}
125+
126+
@Test
127+
public void testWebappConditionsIsNotInvokedWhenAADB2CEnableFileDoesNotExists() {
128+
try (MockedStatic<BeanUtils> beanUtils = mockStatic(BeanUtils.class, Mockito.CALLS_REAL_METHODS)) {
129+
AADB2CConditions.UserFlowCondition userFlowCondition = mock(AADB2CConditions.UserFlowCondition.class);
130+
AADB2CConditions.ClientRegistrationCondition clientRegistrationCondition =
131+
spy(AADB2CConditions.ClientRegistrationCondition.class);
132+
beanUtils.when(() -> BeanUtils.instantiateClass(AADB2CConditions.UserFlowCondition.class))
133+
.thenReturn(userFlowCondition);
134+
beanUtils.when(() -> BeanUtils.instantiateClass(AADB2CConditions.ClientRegistrationCondition.class))
135+
.thenReturn(clientRegistrationCondition);
136+
new WebApplicationContextRunner()
137+
.withClassLoader(new FilteredClassLoader(new ClassPathResource(AAD_B2C_ENABLE_CONFIG_FILE_NAME)))
138+
.withConfiguration(AutoConfigurations.of(WebResourceServerApp.class,
139+
AADB2CResourceServerAutoConfiguration.class))
140+
.withPropertyValues(getWebappCommonPropertyValues())
141+
.run(c -> {
142+
Assertions.assertFalse(c.getResource(AAD_B2C_ENABLE_CONFIG_FILE_NAME).exists());
143+
verify(userFlowCondition, never()).getMatchOutcome(any(), any());
144+
verify(clientRegistrationCondition, never()).getMatchOutcome(any(), any());
145+
});
146+
}
147+
}
91148
}

0 commit comments

Comments
 (0)