Skip to content

Commit

Permalink
backport: allow webauthn registration to go through; update metadata
Browse files Browse the repository at this point in the history
  • Loading branch information
mmoayyed committed May 6, 2023
1 parent fcea4a2 commit b9233b0
Show file tree
Hide file tree
Showing 8 changed files with 108 additions and 21 deletions.
Expand Up @@ -5,13 +5,16 @@
import org.apereo.cas.web.flow.actions.BaseCasWebflowAction;
import org.apereo.cas.web.support.WebUtils;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import lombok.val;
import org.springframework.context.ApplicationContext;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.webflow.execution.Event;
import org.springframework.webflow.execution.RequestContext;

Expand All @@ -24,21 +27,31 @@
* @since 6.4.0
*/
@Slf4j
@RequiredArgsConstructor
public class PopulateSpringSecurityContextAction extends BaseCasWebflowAction {
private final ApplicationContext applicationContext;

@Override
protected Event doExecute(final RequestContext requestContext) {
val authn = WebUtils.getAuthentication(requestContext);
val principal = resolvePrincipal(authn.getPrincipal());
val request = WebUtils.getHttpServletRequestFromExternalWebflowContext(requestContext);
val response = WebUtils.getHttpServletResponseFromExternalWebflowContext(requestContext);

val authorities = principal.getAttributes().keySet().stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList());
authorities.add(new SimpleGrantedAuthority("ROLE_USER"));
val secAuth = new PreAuthenticatedAuthenticationToken(principal, authn.getCredentials(), authorities);
secAuth.setAuthenticated(true);
secAuth.setDetails(new PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails(request, authorities));
val context = SecurityContextHolder.getContext();
context.setAuthentication(secAuth);
val session = request.getSession(true);
LOGGER.trace("Storing security context in session [{}] for [{}]", session.getId(), principal);
session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, context);

LOGGER.debug("Storing security context in session [{}] for [{}]", session.getId(), principal);
val securityContextRepository = applicationContext.getBean(SecurityContextRepository.class);
securityContextRepository.saveContext(context, request, response);
SecurityContextHolder.setContext(context);
return null;
}

Expand Down
Expand Up @@ -646,7 +646,7 @@ public Action populateSpringSecurityContextAction(final CasConfigurationProperti
return WebflowActionBeanSupplier.builder()
.withApplicationContext(applicationContext)
.withProperties(casProperties)
.withAction(PopulateSpringSecurityContextAction::new)
.withAction(() -> new PopulateSpringSecurityContextAction(applicationContext))
.withId(CasWebflowConstants.ACTION_ID_POPULATE_SECURITY_CONTEXT)
.build()
.get();
Expand Down
Expand Up @@ -8,7 +8,6 @@
import org.apereo.cas.ticket.TicketCatalog;
import org.apereo.cas.ticket.TicketGrantingTicket;
import org.apereo.cas.ticket.expiration.HardTimeoutExpirationPolicy;
import org.apereo.cas.util.crypto.CipherExecutor;

import com.hazelcast.core.HazelcastInstance;
import com.hazelcast.map.IMap;
Expand Down
Expand Up @@ -17,7 +17,6 @@
import org.jooq.lambda.Unchecked;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
Expand Down Expand Up @@ -102,7 +101,6 @@ private static ResponseEntity<Object> messagesJson(final ResponseEntity.BodyBuil
* @throws Exception the exception
*/
@PostMapping(value = WEBAUTHN_ENDPOINT_REGISTER, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public ResponseEntity<Object> startRegistration(
@NonNull
@RequestParam("username")
Expand Down Expand Up @@ -143,7 +141,6 @@ public ResponseEntity<Object> startRegistration(
* @throws Exception the exception
*/
@PostMapping(value = WEBAUTHN_ENDPOINT_REGISTER + WEBAUTHN_ENDPOINT_FINISH, produces = MediaType.APPLICATION_JSON_VALUE)
@PreAuthorize("isAuthenticated()")
public ResponseEntity<Object> finishRegistration(
@RequestBody
final String responseJson) throws Exception {
Expand Down
@@ -1,6 +1,6 @@
{
"identifier": "2fb54029-7613-4f1d-94f1-fb876c14a6fe",
"version": 17,
"version": 19,
"vendorInfo": {
"url": "https://yubico.com",
"imageUrl": "https://developers.yubico.com/U2F/Images/yubico.png",
Expand Down Expand Up @@ -40,6 +40,46 @@
]
},

{
"deviceId": "1.3.6.1.4.1.41482.1.1",
"displayName": "Security Key NFC by Yubico",
"transports": 12,
"deviceUrl": "https://support.yubico.com/support/solutions/articles/15000019469-security-key-nfc",
"imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
"selectors": [
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "a4e9fc6d4cbe4758b8ba37598bb5bbaa"
}
}
}
]
},

{
"deviceId": "1.3.6.1.4.1.41482.1.1",
"displayName": "Security Key NFC by Yubico - Enterprise Edition",
"transports": 12,
"deviceUrl": "https://support.yubico.com/support/solutions/articles/15000019469-security-key-nfc",
"imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
"selectors": [
{
"type": "x509Extension",
"parameters": {
"key": "1.3.6.1.4.1.45724.1.1.4",
"value": {
"type": "hex",
"value": "0bb43545fd2c418587ddfeb0b2916ace"
}
}
}
]
},

{
"deviceId": "1.3.6.1.4.1.41482.1.1",
"displayName": "Security Key by Yubico",
Expand Down Expand Up @@ -178,7 +218,7 @@
"displayName": "YubiKey 5/5C NFC",
"transports": 12,
"deviceUrl": "https://support.yubico.com/support/solutions/articles/15000014174--yubikey-5-nfc",
"imageUrl": "https://developers.yubico.com/U2F/Images/YK5.png",
"imageUrl": "https://developers.yubico.com/U2F/Images/YK5NFC-CNFC.png",
"selectors": [
{
"type": "x509Extension",
Expand Down Expand Up @@ -308,6 +348,8 @@
"deviceId": "1.3.6.1.4.1.41482.1.9",
"displayName": "YubiKey Bio - FIDO Edition",
"transports": 4,
"deviceUrl": "https://support.yubico.com/hc/en-us/articles/4407743521810-YubiKey-Bio-FIDO-Edition",
"imageUrl": "https://developers.yubico.com/U2F/Images/BIO.png",
"selectors": [
{
"type": "x509Extension",
Expand Down
Expand Up @@ -430,19 +430,22 @@ public ProtocolEndpointWebSecurityConfigurer<HttpSecurity> webAuthnProtocolEndpo
@Qualifier("webAuthnCsrfTokenRepository")
final ObjectProvider<CsrfTokenRepository> webAuthnCsrfTokenRepository) {
return new ProtocolEndpointWebSecurityConfigurer<>() {
@Override
public List<String> getIgnoredEndpoints() {
return List.of(WebAuthnController.BASE_ENDPOINT_WEBAUTHN + WebAuthnController.WEBAUTHN_ENDPOINT_AUTHENTICATE + "/**");
}


@Override
@CanIgnoreReturnValue
public ProtocolEndpointWebSecurityConfigurer<HttpSecurity> configure(final HttpSecurity http) {
Unchecked.consumer(sec -> http.csrf(customizer -> {
Unchecked.consumer(__ -> http.csrf(customizer -> {
val pattern = new AntPathRequestMatcher(WebAuthnController.BASE_ENDPOINT_WEBAUTHN + "/**");
webAuthnCsrfTokenRepository.ifAvailable(
repository -> customizer.requireCsrfProtectionMatcher(pattern).csrfTokenRepository(repository));
webAuthnCsrfTokenRepository.ifAvailable(repository -> customizer.requireCsrfProtectionMatcher(pattern).csrfTokenRepository(repository));
})).accept(http);

Unchecked.consumer(__ -> {
val patterns = new String[]{
WebAuthnController.BASE_ENDPOINT_WEBAUTHN + WebAuthnController.WEBAUTHN_ENDPOINT_REGISTER + "/**",
WebAuthnController.BASE_ENDPOINT_WEBAUTHN + WebAuthnController.WEBAUTHN_ENDPOINT_AUTHENTICATE + "/**"
};
http.authorizeHttpRequests().antMatchers(patterns).authenticated();
}).accept(http);
return this;
}
};
Expand Down
Expand Up @@ -4,6 +4,7 @@
import org.apereo.cas.configuration.CasConfigurationProperties;
import org.apereo.cas.configuration.features.CasFeatureModule;
import org.apereo.cas.configuration.support.JpaBeans;
import org.apereo.cas.util.CollectionUtils;
import org.apereo.cas.util.spring.boot.ConditionalOnFeatureEnabled;
import org.apereo.cas.web.ProtocolEndpointWebSecurityConfigurer;
import org.apereo.cas.web.flow.CasWebflowConstants;
Expand All @@ -12,12 +13,14 @@
import lombok.val;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.actuate.endpoint.web.PathMappedEndpoints;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
Expand All @@ -32,6 +35,9 @@
import org.springframework.security.provisioning.JdbcUserDetailsManager;
import org.springframework.security.provisioning.UserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextHolderFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

Expand All @@ -58,6 +64,26 @@ public InitializingBean securityContextHolderInitialization() {
@Configuration(value = "CasWebAppSecurityMvcConfiguration", proxyBeanMethods = false)
@EnableConfigurationProperties(CasConfigurationProperties.class)
public static class CasWebAppSecurityMvcConfiguration {

@Bean
@ConditionalOnMissingBean(name = "securityContextRepository")
public SecurityContextRepository securityContextRepository() {
return new HttpSessionSecurityContextRepository();
}

@Bean
public FilterRegistrationBean<SecurityContextHolderFilter> securityContextHolderFilter(
@Qualifier("securityContextRepository")
final SecurityContextRepository securityContextRepository) {
val bean = new FilterRegistrationBean<SecurityContextHolderFilter>();
bean.setFilter(new SecurityContextHolderFilter(securityContextRepository));
bean.setUrlPatterns(CollectionUtils.wrap("/*"));
bean.setName("Spring Security Context Holder Filter");
bean.setAsyncSupported(true);
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}

@Bean
@ConditionalOnMissingBean(name = "casWebAppSecurityWebMvcConfigurer")
public WebMvcConfigurer casWebAppSecurityWebMvcConfigurer() {
Expand All @@ -78,25 +104,29 @@ public static class CasWebappCoreSecurityConfiguration {
@Bean
@ConditionalOnMissingBean(name = "casWebSecurityCustomizer")
public WebSecurityCustomizer casWebSecurityCustomizer(
@Qualifier("securityContextRepository")
final SecurityContextRepository securityContextRepository,
final ObjectProvider<PathMappedEndpoints> pathMappedEndpoints,
final List<ProtocolEndpointWebSecurityConfigurer> configurersList,
final SecurityProperties securityProperties,
final CasConfigurationProperties casProperties) {
val adapter = new CasWebSecurityConfigurerAdapter(casProperties, securityProperties,
pathMappedEndpoints, configurersList);
pathMappedEndpoints, configurersList, securityContextRepository);
return adapter::configureWebSecurity;
}

@Bean
@ConditionalOnMissingBean(name = "casWebSecurityConfigurerAdapter")
public SecurityFilterChain casWebSecurityConfigurerAdapter(
@Qualifier("securityContextRepository")
final SecurityContextRepository securityContextRepository,
final HttpSecurity http,
final ObjectProvider<PathMappedEndpoints> pathMappedEndpoints,
final List<ProtocolEndpointWebSecurityConfigurer> configurersList,
final SecurityProperties securityProperties,
final CasConfigurationProperties casProperties) throws Exception {
val adapter = new CasWebSecurityConfigurerAdapter(casProperties, securityProperties,
pathMappedEndpoints, configurersList);
pathMappedEndpoints, configurersList, securityContextRepository);
return adapter.configureHttpSecurity(http).build();
}
}
Expand Down
Expand Up @@ -30,6 +30,7 @@
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.security.web.util.matcher.IpAddressMatcher;

import java.util.List;
Expand Down Expand Up @@ -59,6 +60,8 @@ public class CasWebSecurityConfigurerAdapter implements DisposableBean {

private final List<ProtocolEndpointWebSecurityConfigurer> protocolEndpointWebSecurityConfigurers;

private final SecurityContextRepository securityContextRepository;

private EndpointLdapAuthenticationProvider endpointLdapAuthenticationProvider;

@Override
Expand Down Expand Up @@ -114,7 +117,8 @@ public HttpSecurity configureHttpSecurity(final HttpSecurity http) throws Except
.requiresChannel()
.requestMatchers(r -> r.getHeader("X-Forwarded-Proto") != null)
.requiresSecure()
.and();
.and()
.securityContext(customizer -> customizer.securityContextRepository(securityContextRepository));

var requests = http.authorizeHttpRequests();
val patterns = protocolEndpointWebSecurityConfigurers.stream()
Expand All @@ -135,8 +139,7 @@ public HttpSecurity configureHttpSecurity(final HttpSecurity http) throws Except
requests.antMatchers(patterns.toArray(String[]::new))
.permitAll()
.and()
.securityContext()
.disable()
.securityContext(customizer -> customizer.securityContextRepository(securityContextRepository))
.sessionManagement()
.disable()
.requestCache()
Expand Down

0 comments on commit b9233b0

Please sign in to comment.