diff --git a/gate-saml/src/main/java/com/opsmx/spinnaker/gate/security/saml/SamlSsoEventPublishConfig.java b/gate-saml/src/main/java/com/opsmx/spinnaker/gate/security/saml/SamlSsoEventPublishConfig.java new file mode 100644 index 0000000000..9e0b85a591 --- /dev/null +++ b/gate-saml/src/main/java/com/opsmx/spinnaker/gate/security/saml/SamlSsoEventPublishConfig.java @@ -0,0 +1,56 @@ +package com.opsmx.spinnaker.gate.security.saml; + +import java.util.ArrayList; +import java.util.List; +import javax.servlet.Filter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; +import org.springframework.security.saml.SAMLProcessingFilter; +import org.springframework.security.web.FilterChainProxy; +import org.springframework.security.web.SecurityFilterChain; + +@ConditionalOnExpression("${saml.enabled:false}") +@Configuration +@Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) +public class SamlSsoEventPublishConfig { + + private ApplicationEventPublisher applicationEventPublisher; + + @Autowired + @Qualifier("springSecurityFilterChain") + private Filter springSecurityFilterChain; + + @Autowired + public void setApplicationEventPublisher(ApplicationEventPublisher applicationEventPublisher) { + this.applicationEventPublisher = applicationEventPublisher; + } + + @Bean + public FilterChainProxy getFilters() { + FilterChainProxy filterChainProxy = (FilterChainProxy) springSecurityFilterChain; + List list = filterChainProxy.getFilterChains(); + + list.stream() + .flatMap(chain -> chain.getFilters().stream()) + .filter(filter -> filter.getClass() == FilterChainProxy.class) + .findAny() + .map(FilterChainProxy.class::cast) + .map(FilterChainProxy::getFilterChains) + .orElse(new ArrayList<>()) + .stream() + .flatMap(chin -> chin.getFilters().stream()) + .filter(filter -> filter.getClass() == SAMLProcessingFilter.class) + .findAny() + .map(SAMLProcessingFilter.class::cast) + .ifPresent(filter -> filter.setApplicationEventPublisher(applicationEventPublisher)); + return filterChainProxy; + } +} diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java index d2c3da76bb..c1a5d57352 100644 --- a/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/audit/AuthenticationAuditListener.java @@ -17,11 +17,13 @@ package com.opsmx.spinnaker.gate.audit; import com.opsmx.spinnaker.gate.enums.AuditEventType; +import com.opsmx.spinnaker.gate.model.AuditData; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.actuate.security.AbstractAuthenticationAuditListener; import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.authentication.event.*; import org.springframework.stereotype.Component; @@ -38,15 +40,24 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) { try { log.debug("Authentication audit events received : {}", event); + // OP-17106: If saml event handle differently + if (event.getAuthentication().isAuthenticated() + && event instanceof InteractiveAuthenticationSuccessEvent) { + log.debug("publishEvent InteractiveAuthenticationSuccessEvent"); + handleInteractiveAuthenticationSuccessEvent(event); + return; + } + if (event.getAuthentication().isAuthenticated() && event instanceof AuthenticationSuccessEvent) { + log.debug("publishEvent AuthenticationSuccessEvent"); auditHandler.publishEvent(AuditEventType.AUTHENTICATION_SUCCESSFUL_AUDIT, event); - } else if (!event.getAuthentication().isAuthenticated() && event instanceof AbstractAuthenticationFailureEvent) { + log.debug("publishEvent AbstractAuthenticationFailureEvent"); auditHandler.publishEvent(AuditEventType.AUTHENTICATION_FAILURE_AUDIT, event); - } else if (event instanceof LogoutSuccessEvent) { + log.debug("publishEvent LogoutSuccessEvent"); auditHandler.publishEvent(AuditEventType.SUCCESSFUL_USER_LOGOUT_AUDIT, event); } @@ -54,4 +65,11 @@ public void onApplicationEvent(AbstractAuthenticationEvent event) { log.error("Exception occured while capturing audit events : {}", e); } } + + private void handleInteractiveAuthenticationSuccessEvent(AbstractAuthenticationEvent event) { + AbstractAuthenticationToken auth = (AbstractAuthenticationToken) event.getAuthentication(); + String name = auth.getName(); + AuditData data = new AuditData(name); + auditHandler.publishEvent(AuditEventType.AUTHENTICATION_SUCCESSFUL_AUDIT, data); + } } diff --git a/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java b/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java new file mode 100644 index 0000000000..3c62212250 --- /dev/null +++ b/gate-web/src/main/java/com/opsmx/spinnaker/gate/model/AuditData.java @@ -0,0 +1,37 @@ +/* + * Copyright 2022 OpsMx + * + * 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 + * + * http://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 com.opsmx.spinnaker.gate.model; + +import lombok.Data; + +@Data +public class AuditData { + private Source source; + + public AuditData(String name) { + this.source = new Source(name); + } + + @Data + public class Source { + private String name; + + public Source(String name) { + this.name = name; + } + } +}