From aba437d4698c0b30559021e3f41b85607fee8ce0 Mon Sep 17 00:00:00 2001 From: Max Batischev Date: Fri, 16 May 2025 14:07:10 +0300 Subject: [PATCH] Add Support SubjectX500PrincipalExtractor Closes gh-16980 Signed-off-by: Max Batischev --- .../web/configurers/X509Configurer.java | 28 +++++- .../http/AuthenticationConfigBuilder.java | 16 +++- .../config/web/server/ServerHttpSecurity.java | 12 +-- .../security/config/spring-security-7.0.rnc | 3 + .../security/config/spring-security-7.0.xsd | 6 ++ .../web/configurers/X509ConfigurerTests.java | 39 +++++++- config/src/test/resources/max.cer | Bin 0 -> 973 bytes .../servlet/appendix/namespace/http.adoc | 3 + .../x509/SubjectDnX509PrincipalExtractor.java | 6 +- .../x509/SubjectX500PrincipalExtractor.java | 88 ++++++++++++++++++ .../x509/X509AuthenticationFilter.java | 4 +- .../SubjectX500PrincipalExtractorTests.java | 66 +++++++++++++ 12 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 config/src/test/resources/max.cer create mode 100644 web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java create mode 100644 web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index a3818e2a9a..b9b766a14d 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,6 +33,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; @@ -74,6 +75,7 @@ import org.springframework.security.web.context.RequestAttributeSecurityContextR * * @author Rob Winch * @author Ngoc Nhan + * @author Max Batischev * @since 3.2 */ public final class X509Configurer> @@ -161,14 +163,38 @@ public final class X509Configurer> * @param subjectPrincipalRegex the regex to extract the user principal from the * certificate (i.e. "CN=(.*?)(?:,|$)"). * @return the {@link X509Configurer} for further customizations + * @deprecated Please use {{@link #extractPrincipalNameFromEmail(boolean)}} instead */ + @Deprecated public X509Configurer subjectPrincipalRegex(String subjectPrincipalRegex) { + if (this.x509PrincipalExtractor instanceof SubjectX500PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); principalExtractor.setSubjectDnRegex(subjectPrincipalRegex); this.x509PrincipalExtractor = principalExtractor; return this; } + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + * @since 7.0 + */ + public X509Configurer extractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + if (this.x509PrincipalExtractor instanceof SubjectDnX509PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } + SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + extractor.setExtractPrincipalNameFromEmail(extractPrincipalNameFromEmail); + this.x509PrincipalExtractor = extractor; + return this; + } + @Override public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index f997e5fa5e..9b6ee210ac 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedG import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; @@ -522,12 +523,25 @@ final class AuthenticationConfigBuilder { filterBuilder.addPropertyValue("securityContextHolderStrategy", authenticationFilterSecurityContextHolderStrategyRef); String regex = x509Elt.getAttribute("subject-principal-regex"); + String extractPrincipalNameFromEmail = x509Elt.getAttribute("extract-principal-name-from-email"); + if (StringUtils.hasText(regex) && StringUtils.hasText(extractPrincipalNameFromEmail)) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } if (StringUtils.hasText(regex)) { BeanDefinitionBuilder extractor = BeanDefinitionBuilder .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); extractor.addPropertyValue("subjectDnRegex", regex); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); } + if (StringUtils.hasText(extractPrincipalNameFromEmail)) { + BeanDefinitionBuilder extractor = BeanDefinitionBuilder + .rootBeanDefinition(SubjectX500PrincipalExtractor.class); + extractor.addPropertyValue("extractPrincipalNameFromEmail", + Boolean.parseBoolean(extractPrincipalNameFromEmail)); + filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); + } injectAuthenticationDetailsSource(x509Elt, filterBuilder); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); createPrauthEntryPoint(x509Elt); diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index a4063fced4..f0760bc64e 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -119,7 +119,7 @@ import org.springframework.security.oauth2.server.resource.web.server.BearerToke import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; import org.springframework.security.web.PortMapper; import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; @@ -944,8 +944,8 @@ public class ServerHttpSecurity { * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @return the {@link X509Spec} to customize * @since 5.2 @@ -979,8 +979,8 @@ public class ServerHttpSecurity { * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @param x509Customizer the {@link Customizer} to provide more options for the * {@link X509Spec} @@ -4181,7 +4181,7 @@ public class ServerHttpSecurity { if (this.principalExtractor != null) { return this.principalExtractor; } - return new SubjectDnX509PrincipalExtractor(); + return new SubjectX500PrincipalExtractor(); } private ReactiveAuthenticationManager getAuthenticationManager() { diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index bbf8622dfe..1236a2aec5 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -1053,6 +1053,9 @@ x509.attlist &= x509.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? +x509.attlist &= + ## If true then DN will be extracted from EMAIlADDRESS + attribute extract-principal-name-from-email {xsd:token}? jee = ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 2e3d6cf275..ed82f3468b 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -2917,6 +2917,12 @@ + + + If true then DN will be extracted from EMAIlADDRESS + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index 206c0b7ece..38983691e1 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,6 +123,16 @@ public class X509ConfigurerTests { // @formatter:on } + @Test + public void x509WhenExtractPrincipalNameFromEmailIsTrueThenUsesEmailAddressToExtractPrincipal() throws Exception { + this.spring.register(EmailPrincipalConfig.class).autowire(); + X509Certificate certificate = loadCert("max.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect(authenticated().withUsername("maxbatischev@gmail.com")); + // @formatter:on + } + @Test public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); @@ -277,6 +287,33 @@ public class X509ConfigurerTests { } + @Configuration + @EnableWebSecurity + static class EmailPrincipalConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509((x509) -> + x509.extractPrincipalNameFromEmail(true) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("maxbatischev@gmail.com") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { diff --git a/config/src/test/resources/max.cer b/config/src/test/resources/max.cer new file mode 100644 index 0000000000000000000000000000000000000000..bd79b1f096b29fb62a456f63a9ae7eeecc2fcdd0 GIT binary patch literal 973 zcmd6mNspUA5QXpjirmr27`(u-dNBsOfrbVHw=p(g%y@}e=hw&MJ z>DAlYvk)~c=NqQJuvQiZLBGT|iCGrDRv1Ik7SLM=if0pWP;tJO=aXsl3IeXIfS{J~ zM~`_zBF;_P+v7n{Phy~$Fbye&-V{6IGf@qValI!$!3}{_WNZ7j|Hy__1tBV&2mxax zoR&w7u$099EmNIq8YhD-p!$6?=5)9Ec!H)~9?beVysQsrblLA#w>_R$dsXSBb(uM= z3VTOTg^6r*37hX;TJon>{kJC+>B}&Cb4{(X%N*boAr7QS6kwKV!_`EvjZ$?pIk}c| zT&J@bB8`7Iei6tgBt^ZF@04ZxnVj)8NW*FEB2JaFF6RfP%CP~DqR)LywI)pOtp_){ z882ua8NsoX_rBC+#(TB&+6x;U`LpuFMZBD#trp$4`FDF8WYhVczX#6rR@w_k$VkMV zXiwfgt`8S`i={rgF1+Y;>~(ESHEj+BLjYzK667-U%E*BfeD}Po9Cihwb-<`eU|0MD{>8Ev>obZ35DEBt@|8v$+|9t+YvgGn;UU26X`W?S z_HcU$uL%48_X?2AMnI)aRIM3Wc*A_(C z@WyhQLK_!Ecja|3)@4*ohsHf^mbFCdIP#A-q?=i+b-A&c>z(nC$4PTb|i mn&q8==RqDeEScF}InkJSjJ57cO)-c~)w$B^IjKYbe%K$#hcR#f literal 0 HcmV?d00001 diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 8979d5ad29..812706ae1d 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2229,6 +2229,9 @@ Defines a regular expression which will be used to extract the username from the Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured. If not set, an attempt will be made to locate a suitable instance automatically and use that. +[[nsa-x509-extract-principal-name-from-email]] +* **extract-principal-name-from-email** +If true then DN will be extracted from EMAIlADDRESS. [[nsa-filter-chain-map]] == diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java index b690af59c0..ffa217d8d5 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.web.authentication.preauth.x509; +import java.security.Principal; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,7 +44,9 @@ import org.springframework.util.Assert; * "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org" * * @author Luke Taylor + * @deprecated Please use {@link SubjectX500PrincipalExtractor} instead */ +@Deprecated public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); @@ -59,6 +62,7 @@ public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, @Override public Object extractPrincipal(X509Certificate clientCert) { // String subjectDN = clientCert.getSubjectX500Principal().getName(); + Principal principal = clientCert.getSubjectDN(); String subjectDN = clientCert.getSubjectDN().getName(); this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); Matcher matcher = this.subjectDnPattern.matcher(subjectDN); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java new file mode 100644 index 0000000000..4b735c0528 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import java.security.cert.X509Certificate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.log.LogMessage; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.util.Assert; + +/** + * Obtains the principal from a certificate using RFC2253 and RFC1779 formats. By default, + * RFC2253 is used: DN is extracted from CN. If extractPrincipalNameFromEmail is true then + * format RFC1779 will be used: DN is extracted from EMAIlADDRESS. + * + * @author Max Batischev + * @since 7.0 + */ +public final class SubjectX500PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { + + private final Log logger = LogFactory.getLog(getClass()); + + private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + private boolean extractPrincipalNameFromEmail = false; + + private final Pattern cnSubjectDnPattern = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE); + + private final Pattern emailSubjectDnPattern = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)", + Pattern.CASE_INSENSITIVE); + + @Override + public Object extractPrincipal(X509Certificate clientCert) { + Assert.notNull(clientCert, "clientCert cannot be null"); + X500Principal principal = clientCert.getSubjectX500Principal(); + String subjectDN = this.extractPrincipalNameFromEmail ? principal.getName("RFC1779") : principal.getName(); + this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); + Matcher matcher = this.extractPrincipalNameFromEmail ? this.emailSubjectDnPattern.matcher(subjectDN) + : this.cnSubjectDnPattern.matcher(subjectDN); + if (!matcher.find()) { + throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching", + new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}")); + } + String principalName = matcher.group(1); + this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", principalName)); + return principalName; + } + + @Override + public void setMessageSource(MessageSource messageSource) { + Assert.notNull(messageSource, "messageSource cannot be null"); + this.messages = new MessageSourceAccessor(messageSource); + } + + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + */ + public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + this.extractPrincipalNameFromEmail = extractPrincipalNameFromEmail; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java index c49cc6ab2f..02618d126d 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import org.springframework.security.web.authentication.preauth.AbstractPreAuthen */ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { - private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private X509PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java new file mode 100644 index 0000000000..ebc1064fdc --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link SubjectX500PrincipalExtractor}. + * + * @author Max Batischev + */ +public class SubjectX500PrincipalExtractorTests { + + private final SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + + @Test + void extractWhenCnPatternSetThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("Luke Taylor"); + } + + @Test + void extractWhenEmailPatternSetThenExtractsPrincipalName() throws Exception { + this.extractor.setExtractPrincipalNameFromEmail(true); + + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("luke@monkeymachine"); + } + + @Test + void extractWhenCnAtEndThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithCnAtEnd()); + + assertThat(principal).isEqualTo("Duke"); + } + + @Test + void setMessageSourceWhenNullThenThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null)); + } + + @Test + void extractWhenCertificateIsNullThenFails() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.extractPrincipal(null)); + } + +}