package org.apereo.cas.oidc.config; import com.google.common.cache.CacheBuilder; import com.google.common.cache.LoadingCache; import org.apache.commons.lang3.StringUtils; import org.apereo.cas.CentralAuthenticationService; import org.apereo.cas.authentication.AuthenticationServiceSelectionPlan; import org.apereo.cas.authentication.AuthenticationServiceSelectionStrategy; import org.apereo.cas.authentication.AuthenticationSystemSupport; import org.apereo.cas.authentication.principal.DefaultPrincipalFactory; import org.apereo.cas.authentication.principal.PrincipalFactory; import org.apereo.cas.authentication.principal.ServiceFactory; import org.apereo.cas.authentication.principal.WebApplicationService; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.support.oidc.OidcProperties; import org.apereo.cas.oidc.OidcConstants; import org.apereo.cas.oidc.claims.BaseOidcScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.mapping.DefaultOidcAttributeToScopeClaimMapper; import org.apereo.cas.oidc.claims.OidcCustomScopeAttributeReleasePolicy; import org.apereo.cas.oidc.claims.mapping.OidcAttributeToScopeClaimMapper; import org.apereo.cas.oidc.discovery.OidcServerDiscoverySettings; import org.apereo.cas.oidc.discovery.OidcServerDiscoverySettingsFactory; import org.apereo.cas.oidc.dynareg.OidcClientRegistrationRequest; import org.apereo.cas.oidc.dynareg.OidcClientRegistrationRequestSerializer; import org.apereo.cas.oidc.jwks.OidcDefaultJsonWebKeystoreCacheLoader; import org.apereo.cas.oidc.jwks.OidcJsonWebKeystoreGeneratorService; import org.apereo.cas.oidc.jwks.OidcServiceJsonWebKeystoreCacheLoader; import org.apereo.cas.oidc.profile.OidcProfileScopeToAttributesFilter; import org.apereo.cas.oidc.token.OidcIdTokenGeneratorService; import org.apereo.cas.oidc.token.OidcIdTokenSigningAndEncryptionService; import org.apereo.cas.oidc.util.OidcAuthorizationRequestSupport; import org.apereo.cas.oidc.web.OidcAccessTokenResponseGenerator; import org.apereo.cas.oidc.web.OidcCallbackAuthorizeViewResolver; import org.apereo.cas.oidc.web.OidcCasClientRedirectActionBuilder; import org.apereo.cas.oidc.web.OidcConsentApprovalViewResolver; import org.apereo.cas.oidc.web.OidcHandlerInterceptorAdapter; import org.apereo.cas.oidc.web.OidcSecurityInterceptor; import org.apereo.cas.oidc.web.controllers.OidcAccessTokenEndpointController; import org.apereo.cas.oidc.web.controllers.OidcAuthorizeEndpointController; import org.apereo.cas.oidc.web.controllers.OidcDynamicClientRegistrationEndpointController; import org.apereo.cas.oidc.web.controllers.OidcJwksEndpointController; import org.apereo.cas.oidc.web.controllers.OidcProfileEndpointController; import org.apereo.cas.oidc.web.controllers.OidcWellKnownEndpointController; import org.apereo.cas.oidc.web.flow.OidcAuthenticationContextWebflowEventEventResolver; import org.apereo.cas.oidc.web.flow.OidcRegisteredServiceUIAction; import org.apereo.cas.oidc.web.flow.OidcWebflowConfigurer; import org.apereo.cas.services.MultifactorAuthenticationProviderSelector; import org.apereo.cas.services.OidcRegisteredService; import org.apereo.cas.services.ServicesManager; import org.apereo.cas.support.oauth.authenticator.Authenticators; import org.apereo.cas.support.oauth.authenticator.OAuth20CasAuthenticationBuilder; import org.apereo.cas.support.oauth.profile.OAuth20ProfileScopeToAttributesFilter; import org.apereo.cas.support.oauth.validator.OAuth20Validator; import org.apereo.cas.support.oauth.web.response.OAuth20CasClientRedirectActionBuilder; import org.apereo.cas.support.oauth.web.response.accesstoken.AccessTokenResponseGenerator; import org.apereo.cas.support.oauth.web.views.ConsentApprovalViewResolver; import org.apereo.cas.support.oauth.web.views.OAuth20CallbackAuthorizeViewResolver; import org.apereo.cas.ticket.accesstoken.AccessTokenFactory; import org.apereo.cas.ticket.code.OAuthCodeFactory; import org.apereo.cas.ticket.refreshtoken.RefreshTokenFactory; import org.apereo.cas.ticket.registry.TicketRegistry; import org.apereo.cas.ticket.registry.TicketRegistrySupport; import org.apereo.cas.util.gen.DefaultRandomStringGenerator; import org.apereo.cas.util.serialization.StringSerializer; import org.apereo.cas.web.flow.CasWebflowConfigurer; import org.apereo.cas.web.flow.authentication.RankedMultifactorAuthenticationProviderSelector; import org.apereo.cas.web.flow.resolver.CasDelegatingWebflowEventResolver; import org.apereo.cas.web.flow.resolver.CasWebflowEventResolver; import org.apereo.cas.web.support.CookieRetrievingCookieGenerator; import org.jose4j.jwk.RsaJsonWebKey; import org.pac4j.cas.client.CasClient; import org.pac4j.core.config.Config; import org.pac4j.springframework.web.SecurityInterceptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.cloud.context.config.annotation.RefreshScope; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; import org.springframework.web.util.CookieGenerator; import org.springframework.webflow.definition.registry.FlowDefinitionRegistry; import org.springframework.webflow.engine.builder.support.FlowBuilderServices; import org.springframework.webflow.execution.Action; import java.util.Arrays; import java.util.Collection; import java.util.Map; import java.util.Optional; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.Stream; /** * This is {@link OidcConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("oidcConfiguration") @EnableConfigurationProperties(CasConfigurationProperties.class) public class OidcConfiguration extends WebMvcConfigurerAdapter { @Autowired @Qualifier("webApplicationServiceFactory") private ServiceFactory<WebApplicationService> webApplicationServiceFactory; @Autowired @Qualifier("requiresAuthenticationAccessTokenInterceptor") private SecurityInterceptor requiresAuthenticationAccessTokenInterceptor; @Autowired(required = false) @Qualifier("multifactorAuthenticationProviderSelector") private MultifactorAuthenticationProviderSelector multifactorAuthenticationProviderSelector = new RankedMultifactorAuthenticationProviderSelector(); @Autowired @Qualifier("oauthCasAuthenticationBuilder") private OAuth20CasAuthenticationBuilder authenticationBuilder; @Autowired @Qualifier("warnCookieGenerator") private CookieGenerator warnCookieGenerator; @Autowired @Qualifier("loginFlowRegistry") private FlowDefinitionRegistry loginFlowDefinitionRegistry; @Autowired @Qualifier("logoutFlowRegistry") private FlowDefinitionRegistry logoutFlowDefinitionRegistry; @Autowired private FlowBuilderServices flowBuilderServices; @Autowired @Qualifier("initialAuthenticationAttemptWebflowEventResolver") private CasDelegatingWebflowEventResolver initialAuthenticationAttemptWebflowEventResolver; @Autowired @Qualifier("centralAuthenticationService") private CentralAuthenticationService centralAuthenticationService; @Autowired @Qualifier("oauth20AuthenticationRequestServiceSelectionStrategy") private AuthenticationServiceSelectionStrategy oauth20AuthenticationServiceSelectionStrategy; @Autowired private CasConfigurationProperties casProperties; @Autowired @Qualifier("oauthSecConfig") private Config oauthSecConfig; @Autowired @Qualifier("ticketGrantingTicketCookieGenerator") private CookieRetrievingCookieGenerator ticketGrantingTicketCookieGenerator; @Autowired @Qualifier("defaultTicketRegistrySupport") private TicketRegistrySupport ticketRegistrySupport; @Autowired @Qualifier("defaultAccessTokenFactory") private AccessTokenFactory defaultAccessTokenFactory; @Autowired @Qualifier("defaultRefreshTokenFactory") private RefreshTokenFactory defaultRefreshTokenFactory; @Autowired @Qualifier("servicesManager") private ServicesManager servicesManager; @Autowired @Qualifier("ticketRegistry") private TicketRegistry ticketRegistry; @Autowired @Qualifier("oAuthValidator") private OAuth20Validator oAuth20Validator; @Autowired @Qualifier("defaultOAuthCodeFactory") private OAuthCodeFactory defaultOAuthCodeFactory; @Autowired @Qualifier("authenticationServiceSelectionPlan") private AuthenticationServiceSelectionPlan authenticationRequestServiceSelectionStrategies; @Override public void addInterceptors(final InterceptorRegistry registry) { registry.addInterceptor(oauthInterceptor()).addPathPatterns('/' + OidcConstants.BASE_OIDC_URL.concat("/").concat("*")); } @Bean public ConsentApprovalViewResolver consentApprovalViewResolver() { return new OidcConsentApprovalViewResolver(casProperties); } @Bean public OAuth20CallbackAuthorizeViewResolver callbackAuthorizeViewResolver() { return new OidcCallbackAuthorizeViewResolver(oidcAuthorizationRequestSupport()); } @Bean public OAuth20CasClientRedirectActionBuilder oauthCasClientRedirectActionBuilder() { return new OidcCasClientRedirectActionBuilder(oidcAuthorizationRequestSupport()); } @Bean public HandlerInterceptorAdapter requiresAuthenticationDynamicRegistrationInterceptor() { final String clients = Stream.of( Authenticators.CAS_OAUTH_CLIENT_BASIC_AUTHN, Authenticators.CAS_OAUTH_CLIENT_DIRECT_FORM, Authenticators.CAS_OAUTH_CLIENT_USER_FORM).collect(Collectors.joining(",")); return new SecurityInterceptor(oauthSecConfig, clients); } @Bean public HandlerInterceptorAdapter requiresAuthenticationAuthorizeInterceptor() { final String name = oauthSecConfig.getClients().findClient(CasClient.class).getName(); return new OidcSecurityInterceptor(oauthSecConfig, name, oidcAuthorizationRequestSupport()); } @Bean public OAuth20CasClientRedirectActionBuilder oidcCasClientRedirectActionBuilder() { return new OidcCasClientRedirectActionBuilder(oidcAuthorizationRequestSupport()); } @RefreshScope @Bean public OidcIdTokenGeneratorService oidcIdTokenGenerator() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); return new OidcIdTokenGeneratorService(oidc.getIssuer(), oidc.getSkew(), oidcTokenSigningAndEncryptionService()); } @Bean @RefreshScope public AccessTokenResponseGenerator oidcAccessTokenResponseGenerator() { return new OidcAccessTokenResponseGenerator(oidcIdTokenGenerator()); } @Bean public OidcAuthorizationRequestSupport oidcAuthorizationRequestSupport() { return new OidcAuthorizationRequestSupport(ticketGrantingTicketCookieGenerator, ticketRegistrySupport); } @ConditionalOnMissingBean(name = "oidcPrincipalFactory") @Bean public PrincipalFactory oidcPrincipalFactory() { return new DefaultPrincipalFactory(); } @Bean public OidcAttributeToScopeClaimMapper oidcAttributeToScopeClaimMapper() { final Map<String, String> mappings = casProperties.getAuthn().getOidc().getClaimsMap(); return new DefaultOidcAttributeToScopeClaimMapper(mappings); } @Bean public OAuth20ProfileScopeToAttributesFilter profileScopeToAttributesFilter() { return new OidcProfileScopeToAttributesFilter(oidcPrincipalFactory(), servicesManager, userDefinedScopeBasedAttributeReleasePolicies(), oidcAttributeToScopeClaimMapper()); } @RefreshScope @Bean public OidcAccessTokenEndpointController oidcAccessTokenController() { return new OidcAccessTokenEndpointController( servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, defaultRefreshTokenFactory, oidcAccessTokenResponseGenerator(), profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator, authenticationBuilder, centralAuthenticationService); } @Bean public StringSerializer<OidcClientRegistrationRequest> clientRegistrationRequestSerializer() { return new OidcClientRegistrationRequestSerializer(); } @RefreshScope @Bean public OidcDynamicClientRegistrationEndpointController oidcDynamicClientRegistrationEndpointController() { return new OidcDynamicClientRegistrationEndpointController( servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, clientRegistrationRequestSerializer(), new DefaultRandomStringGenerator(), new DefaultRandomStringGenerator(), profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator); } @RefreshScope @Bean public OidcJwksEndpointController oidcJwksController() { return new OidcJwksEndpointController(servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator); } @Autowired @RefreshScope @Bean public OidcWellKnownEndpointController oidcWellKnownController(@Qualifier("oidcServerDiscoverySettingsFactory") final OidcServerDiscoverySettings discoverySettings) { return new OidcWellKnownEndpointController(servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, discoverySettings, profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator); } @RefreshScope @Bean public OidcProfileEndpointController oidcProfileController() { return new OidcProfileEndpointController(servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator); } @RefreshScope @Bean public OidcAuthorizeEndpointController oidcAuthorizeController() { return new OidcAuthorizeEndpointController(servicesManager, ticketRegistry, oAuth20Validator, defaultAccessTokenFactory, oidcPrincipalFactory(), webApplicationServiceFactory, defaultOAuthCodeFactory, consentApprovalViewResolver(), oidcIdTokenGenerator(), profileScopeToAttributesFilter(), casProperties, ticketGrantingTicketCookieGenerator, authenticationBuilder); } @Autowired @RefreshScope @Bean public CasWebflowEventResolver oidcAuthenticationContextWebflowEventResolver(@Qualifier("defaultAuthenticationSystemSupport") final AuthenticationSystemSupport authenticationSystemSupport) { final CasWebflowEventResolver r = new OidcAuthenticationContextWebflowEventEventResolver(authenticationSystemSupport, centralAuthenticationService, servicesManager, ticketRegistrySupport, warnCookieGenerator, authenticationRequestServiceSelectionStrategies, multifactorAuthenticationProviderSelector); this.initialAuthenticationAttemptWebflowEventResolver.addDelegate(r); return r; } @Bean public CasWebflowConfigurer oidcWebflowConfigurer() { final OidcWebflowConfigurer cfg = new OidcWebflowConfigurer(flowBuilderServices, loginFlowDefinitionRegistry, oidcRegisteredServiceUIAction()); cfg.setLogoutFlowDefinitionRegistry(logoutFlowDefinitionRegistry); return cfg; } @ConditionalOnMissingBean(name = "oidcRegisteredServiceUIAction") @Bean public Action oidcRegisteredServiceUIAction() { return new OidcRegisteredServiceUIAction(this.servicesManager, oauth20AuthenticationServiceSelectionStrategy); } @Bean public OidcIdTokenSigningAndEncryptionService oidcTokenSigningAndEncryptionService() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); return new OidcIdTokenSigningAndEncryptionService(oidcDefaultJsonWebKeystoreCache(), oidcServiceJsonWebKeystoreCache(), oidc.getIssuer()); } @Bean public LoadingCache<OidcRegisteredService, Optional<RsaJsonWebKey>> oidcServiceJsonWebKeystoreCache() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); final LoadingCache<OidcRegisteredService, Optional<RsaJsonWebKey>> cache = CacheBuilder.newBuilder().maximumSize(1) .expireAfterWrite(oidc.getJwksCacheInMinutes(), TimeUnit.MINUTES) .build(oidcServiceJsonWebKeystoreCacheLoader()); return cache; } @Bean public LoadingCache<String, Optional<RsaJsonWebKey>> oidcDefaultJsonWebKeystoreCache() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); final LoadingCache<String, Optional<RsaJsonWebKey>> cache = CacheBuilder.newBuilder().maximumSize(1) .expireAfterWrite(oidc.getJwksCacheInMinutes(), TimeUnit.MINUTES) .build(oidcDefaultJsonWebKeystoreCacheLoader()); return cache; } @Bean public OidcDefaultJsonWebKeystoreCacheLoader oidcDefaultJsonWebKeystoreCacheLoader() { return new OidcDefaultJsonWebKeystoreCacheLoader(casProperties.getAuthn().getOidc().getJwksFile()); } @Bean public OidcServiceJsonWebKeystoreCacheLoader oidcServiceJsonWebKeystoreCacheLoader() { return new OidcServiceJsonWebKeystoreCacheLoader(); } @Bean public OidcServerDiscoverySettingsFactory oidcServerDiscoverySettingsFactory() { return new OidcServerDiscoverySettingsFactory(casProperties); } @Bean public OidcJsonWebKeystoreGeneratorService oidcJsonWebKeystoreGeneratorService() { return new OidcJsonWebKeystoreGeneratorService(casProperties.getAuthn().getOidc()); } @Bean public HandlerInterceptorAdapter oauthInterceptor() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); final OidcConstants.DynamicClientRegistrationMode mode = OidcConstants.DynamicClientRegistrationMode.valueOf(StringUtils.defaultIfBlank( oidc.getDynamicClientRegistrationMode(), OidcConstants.DynamicClientRegistrationMode.PROTECTED.name())); return new OidcHandlerInterceptorAdapter(requiresAuthenticationAccessTokenInterceptor, requiresAuthenticationAuthorizeInterceptor(), requiresAuthenticationDynamicRegistrationInterceptor(), mode); } @RefreshScope @Bean public Collection<BaseOidcScopeAttributeReleasePolicy> userDefinedScopeBasedAttributeReleasePolicies() { final OidcProperties oidc = casProperties.getAuthn().getOidc(); return oidc.getUserDefinedScopes().entrySet() .stream() .map((k) -> new OidcCustomScopeAttributeReleasePolicy(k.getKey(), Arrays.asList(k.getValue().split(",")))) .collect(Collectors.toSet()); } }