package org.apereo.cas.audit.spi.config; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.ToStringBuilder; import org.apache.commons.lang3.builder.ToStringStyle; import org.apereo.cas.audit.spi.CredentialsAsFirstParameterResourceResolver; import org.apereo.cas.audit.spi.DefaultDelegatingAuditTrailManager; import org.apereo.cas.audit.spi.DelegatingAuditTrailManager; import org.apereo.cas.audit.spi.MessageBundleAwareResourceResolver; import org.apereo.cas.audit.spi.PrincipalIdProvider; import org.apereo.cas.audit.spi.ServiceResourceResolver; import org.apereo.cas.audit.spi.ThreadLocalPrincipalResolver; import org.apereo.cas.audit.spi.TicketAsFirstParameterResourceResolver; import org.apereo.cas.configuration.CasConfigurationProperties; import org.apereo.cas.configuration.model.core.audit.AuditProperties; import org.apereo.inspektr.audit.AuditTrailManagementAspect; import org.apereo.inspektr.audit.AuditTrailManager; import org.apereo.inspektr.audit.spi.AuditActionResolver; import org.apereo.inspektr.audit.spi.AuditResourceResolver; import org.apereo.inspektr.audit.spi.support.DefaultAuditActionResolver; import org.apereo.inspektr.audit.spi.support.ReturnValueAsStringResourceResolver; import org.apereo.inspektr.audit.support.Slf4jLoggingAuditTrailManager; import org.apereo.inspektr.common.spi.PrincipalResolver; import org.apereo.inspektr.common.web.ClientInfoThreadLocalFilter; import org.aspectj.lang.JoinPoint; 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.boot.web.servlet.FilterRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.EnableAspectJAutoProxy; import org.springframework.core.Ordered; import org.springframework.webflow.execution.Event; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.Map; /** * This is {@link CasCoreAuditConfiguration}. * * @author Misagh Moayyed * @since 5.0.0 */ @Configuration("casCoreAuditConfiguration") @EnableAspectJAutoProxy @EnableConfigurationProperties(CasConfigurationProperties.class) public class CasCoreAuditConfiguration { private static final String AUDIT_ACTION_SUFFIX_FAILED = "_FAILED"; @Autowired private CasConfigurationProperties casProperties; @Bean public AuditTrailManagementAspect auditTrailManagementAspect(@Qualifier("auditTrailManager") final AuditTrailManager auditTrailManager) { final AuditTrailManagementAspect aspect = new AuditTrailManagementAspect( casProperties.getAudit().getAppCode(), auditablePrincipalResolver(principalIdProvider()), Collections.singletonList(auditTrailManager), auditActionResolverMap(), auditResourceResolverMap()); aspect.setFailOnAuditFailures(!casProperties.getAudit().isIgnoreAuditFailures()); return aspect; } @ConditionalOnMissingBean(name = "auditTrailManager") @Bean public DelegatingAuditTrailManager auditTrailManager() { final Slf4jLoggingAuditTrailManager mgmr = new Slf4jLoggingAuditTrailManager(); mgmr.setUseSingleLine(casProperties.getAudit().isUseSingleLine()); mgmr.setEntrySeparator(casProperties.getAudit().getSinglelineSeparator()); mgmr.setAuditFormat(casProperties.getAudit().getAuditFormat()); return new DefaultDelegatingAuditTrailManager(mgmr); } @Bean public FilterRegistrationBean casClientInfoLoggingFilter() { final AuditProperties audit = casProperties.getAudit(); final FilterRegistrationBean bean = new FilterRegistrationBean(); bean.setFilter(new ClientInfoThreadLocalFilter()); bean.setUrlPatterns(Collections.singleton("/*")); bean.setName("CAS Client Info Logging Filter"); bean.setAsyncSupported(true); bean.setOrder(Ordered.HIGHEST_PRECEDENCE); final Map<String, String> initParams = new HashMap<>(); if (StringUtils.isNotBlank(audit.getAlternateClientAddrHeaderName())) { initParams.put(ClientInfoThreadLocalFilter.CONST_IP_ADDRESS_HEADER, audit.getAlternateClientAddrHeaderName()); } if (StringUtils.isNotBlank(audit.getAlternateServerAddrHeaderName())) { initParams.put(ClientInfoThreadLocalFilter.CONST_SERVER_IP_ADDRESS_HEADER, audit.getAlternateServerAddrHeaderName()); } initParams.put(ClientInfoThreadLocalFilter.CONST_USE_SERVER_HOST_ADDRESS, String.valueOf(audit.isUseServerHostAddress())); bean.setInitParameters(initParams); return bean; } @ConditionalOnMissingBean(name = "authenticationActionResolver") @Bean public AuditActionResolver authenticationActionResolver() { return new DefaultAuditActionResolver("_SUCCESS", AUDIT_ACTION_SUFFIX_FAILED); } @ConditionalOnMissingBean(name = "ticketCreationActionResolver") @Bean public AuditActionResolver ticketCreationActionResolver() { return new DefaultAuditActionResolver("_CREATED", "_NOT_CREATED"); } @ConditionalOnMissingBean(name = "ticketValidationActionResolver") @Bean public AuditActionResolver ticketValidationActionResolver() { return new DefaultAuditActionResolver("D", AUDIT_ACTION_SUFFIX_FAILED); } @ConditionalOnMissingBean(name = "returnValueResourceResolver") @Bean public AuditResourceResolver returnValueResourceResolver() { return new ReturnValueAsStringResourceResolver(); } @ConditionalOnMissingBean(name = "nullableReturnValueResourceResolver") @Bean public AuditResourceResolver nullableReturnValueResourceResolver() { return new AuditResourceResolver() { @Override public String[] resolveFrom(final JoinPoint joinPoint, final Object o) { if (o == null) { return new String[0]; } if (o instanceof Event) { final Event event = Event.class.cast(o); final String sourceName = event.getSource().getClass().getSimpleName(); final String result = new ToStringBuilder(event, ToStringStyle.NO_CLASS_NAME_STYLE) .append("event", event.getId()) .append("timestamp", new Date(event.getTimestamp())) .append("source", sourceName) .toString(); return new String[]{result}; } return returnValueResourceResolver().resolveFrom(joinPoint, o); } @Override public String[] resolveFrom(final JoinPoint joinPoint, final Exception e) { return returnValueResourceResolver().resolveFrom(joinPoint, e); } }; } @ConditionalOnMissingBean(name = "auditActionResolverMap") @Bean public Map<String, AuditActionResolver> auditActionResolverMap() { final Map<String, AuditActionResolver> map = new HashMap<>(); final AuditActionResolver resolver = authenticationActionResolver(); map.put("AUTHENTICATION_RESOLVER", resolver); map.put("SAVE_SERVICE_ACTION_RESOLVER", resolver); map.put("CHANGE_PASSWORD_ACTION_RESOLVER", resolver); final AuditActionResolver defResolver = new DefaultAuditActionResolver(); map.put("DESTROY_TICKET_GRANTING_TICKET_RESOLVER", defResolver); map.put("DESTROY_PROXY_GRANTING_TICKET_RESOLVER", defResolver); final AuditActionResolver cResolver = ticketCreationActionResolver(); map.put("CREATE_PROXY_GRANTING_TICKET_RESOLVER", cResolver); map.put("GRANT_SERVICE_TICKET_RESOLVER", cResolver); map.put("GRANT_PROXY_TICKET_RESOLVER", cResolver); map.put("CREATE_TICKET_GRANTING_TICKET_RESOLVER", cResolver); map.put("TRUSTED_AUTHENTICATION_ACTION_RESOLVER", cResolver); map.put("AUTHENTICATION_EVENT_ACTION_RESOLVER", new DefaultAuditActionResolver("_TRIGGERED", StringUtils.EMPTY)); final AuditActionResolver adResolver = new DefaultAuditActionResolver(); map.put("ADAPTIVE_RISKY_AUTHENTICATION_ACTION_RESOLVER", adResolver); map.put("VALIDATE_SERVICE_TICKET_RESOLVER", ticketValidationActionResolver()); return map; } @ConditionalOnMissingBean(name = "auditResourceResolverMap") @Bean public Map<String, AuditResourceResolver> auditResourceResolverMap() { final Map<String, AuditResourceResolver> map = new HashMap<>(); map.put("AUTHENTICATION_RESOURCE_RESOLVER", new CredentialsAsFirstParameterResourceResolver()); map.put("CREATE_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER", this.messageBundleAwareResourceResolver()); map.put("CREATE_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER", this.messageBundleAwareResourceResolver()); map.put("DESTROY_TICKET_GRANTING_TICKET_RESOURCE_RESOLVER", this.ticketResourceResolver()); map.put("DESTROY_PROXY_GRANTING_TICKET_RESOURCE_RESOLVER", this.ticketResourceResolver()); map.put("GRANT_SERVICE_TICKET_RESOURCE_RESOLVER", new ServiceResourceResolver()); map.put("GRANT_PROXY_TICKET_RESOURCE_RESOLVER", new ServiceResourceResolver()); map.put("VALIDATE_SERVICE_TICKET_RESOURCE_RESOLVER", this.ticketResourceResolver()); map.put("SAVE_SERVICE_RESOURCE_RESOLVER", returnValueResourceResolver()); map.put("CHANGE_PASSWORD_RESOURCE_RESOLVER", returnValueResourceResolver()); map.put("TRUSTED_AUTHENTICATION_RESOURCE_RESOLVER", returnValueResourceResolver()); map.put("ADAPTIVE_RISKY_AUTHENTICATION_RESOURCE_RESOLVER", returnValueResourceResolver()); map.put("AUTHENTICATION_EVENT_RESOURCE_RESOLVER", nullableReturnValueResourceResolver()); return map; } @ConditionalOnMissingBean(name = "auditablePrincipalResolver") @Bean public PrincipalResolver auditablePrincipalResolver(@Qualifier("principalIdProvider") final PrincipalIdProvider principalIdProvider) { return new ThreadLocalPrincipalResolver(principalIdProvider); } @ConditionalOnMissingBean(name = "ticketResourceResolver") @Bean public AuditResourceResolver ticketResourceResolver() { return new TicketAsFirstParameterResourceResolver(); } @ConditionalOnMissingBean(name = "messageBundleAwareResourceResolver") @Bean public AuditResourceResolver messageBundleAwareResourceResolver() { return new MessageBundleAwareResourceResolver(); } @ConditionalOnMissingBean(name = "principalIdProvider") @Bean public PrincipalIdProvider principalIdProvider() { return new PrincipalIdProvider() { }; } }