/* * Copyright 2015 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 * * 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 org.springframework.statemachine.security; import java.util.ArrayList; import java.util.Collection; import java.util.List; import org.springframework.expression.ExpressionParser; import org.springframework.expression.spel.SpelCompilerMode; import org.springframework.expression.spel.SpelParserConfiguration; import org.springframework.expression.spel.standard.SpelExpressionParser; import org.springframework.messaging.Message; import org.springframework.security.access.AccessDecisionManager; import org.springframework.security.access.AccessDecisionVoter; import org.springframework.security.access.ConfigAttribute; import org.springframework.security.access.SecurityConfig; import org.springframework.security.access.vote.AbstractAccessDecisionManager; import org.springframework.security.access.vote.AffirmativeBased; import org.springframework.security.access.vote.ConsensusBased; import org.springframework.security.access.vote.RoleVoter; import org.springframework.security.access.vote.UnanimousBased; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.statemachine.StateContext; import org.springframework.statemachine.StateMachine; import org.springframework.statemachine.support.StateMachineInterceptor; import org.springframework.statemachine.support.StateMachineInterceptorAdapter; import org.springframework.statemachine.transition.Transition; import org.springframework.util.StringUtils; /** * {@link StateMachineInterceptor} which can be registered into a {@link StateMachine} * order to intercept a various security related checks. * * @author Janne Valkealahti * * @param <S> the type of state * @param <E> the type of event */ public class StateMachineSecurityInterceptor<S, E> extends StateMachineInterceptorAdapter<S, E> { private AccessDecisionManager transitionAccessDecisionManager; private AccessDecisionManager eventAccessDecisionManager; private final ExpressionParser expressionParser = new SpelExpressionParser(new SpelParserConfiguration(SpelCompilerMode.OFF, null)); private SecurityRule eventSecurityRule; /** * Instantiates a new state machine security interceptor. */ public StateMachineSecurityInterceptor() { this(null, null); } /** * Instantiates a new state machine security interceptor with * a custom {@link AccessDecisionManager} for both transitions * and events. * * @param transitionAccessDecisionManager the transition access decision manager * @param eventAccessDecisionManager the event access decision manager */ public StateMachineSecurityInterceptor(AccessDecisionManager transitionAccessDecisionManager, AccessDecisionManager eventAccessDecisionManager) { this(transitionAccessDecisionManager, eventAccessDecisionManager, null); } /** * Instantiates a new state machine security interceptor with * a custom {@link AccessDecisionManager} for both transitions * and events and a {@link SecurityRule} for events; * * @param transitionAccessDecisionManager the transition access decision manager * @param eventAccessDecisionManager the event access decision manager * @param eventSecurityRule the event security rule */ public StateMachineSecurityInterceptor(AccessDecisionManager transitionAccessDecisionManager, AccessDecisionManager eventAccessDecisionManager, SecurityRule eventSecurityRule) { this.transitionAccessDecisionManager = transitionAccessDecisionManager; this.eventAccessDecisionManager = eventAccessDecisionManager; this.eventSecurityRule = eventSecurityRule; } @Override public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) { if (eventSecurityRule != null) { decide(eventSecurityRule, message); } return super.preEvent(message, stateMachine); } @Override public StateContext<S, E> preTransition(StateContext<S, E> stateContext) { Transition<S, E> transition = stateContext.getTransition(); SecurityRule rule = transition.getSecurityRule(); if (rule != null) { decide(rule, transition); } return super.preTransition(stateContext); } /** * Sets the event access decision manager. * * @param eventAccessDecisionManager the new event access decision manager */ public void setEventAccessDecisionManager(AccessDecisionManager eventAccessDecisionManager) { this.eventAccessDecisionManager = eventAccessDecisionManager; } /** * Sets the transition access decision manager. * * @param transitionAccessDecisionManager the new transition access decision manager */ public void setTransitionAccessDecisionManager(AccessDecisionManager transitionAccessDecisionManager) { this.transitionAccessDecisionManager = transitionAccessDecisionManager; } /** * Sets the event security rule. * * @param eventSecurityRule the new event security rule */ public void setEventSecurityRule(SecurityRule eventSecurityRule) { this.eventSecurityRule = eventSecurityRule; } private void decide(SecurityRule rule, Message<E> object) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Collection<ConfigAttribute> configAttributes = getEentConfigAttributes(rule); if (eventAccessDecisionManager != null) { decide(eventAccessDecisionManager, authentication, object, configAttributes); } else { decide(createDefaultEventManager(rule), authentication, object, configAttributes); } } private void decide(SecurityRule rule, Transition<S, E> object) { Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); Collection<ConfigAttribute> configAttributes = getTransitionConfigAttributes(rule); if (transitionAccessDecisionManager != null) { decide(transitionAccessDecisionManager, authentication, object, configAttributes); } else { decide(createDefaultTransitionManager(rule), authentication, object, configAttributes); } } private Collection<ConfigAttribute> getTransitionConfigAttributes(SecurityRule rule) { List<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); if (rule.getAttributes() != null) { for (String attribute : rule.getAttributes()) { configAttributes.add(new SecurityConfig(attribute)); } } if (StringUtils.hasText(rule.getExpression())) { configAttributes.add(new TransitionExpressionConfigAttribute(expressionParser.parseExpression(rule.getExpression()))); } return configAttributes; } private Collection<ConfigAttribute> getEentConfigAttributes(SecurityRule rule) { List<ConfigAttribute> configAttributes = new ArrayList<ConfigAttribute>(); if (rule.getAttributes() != null) { for (String attribute : rule.getAttributes()) { configAttributes.add(new SecurityConfig(attribute)); } } if (StringUtils.hasText(rule.getExpression())) { configAttributes.add(new EventExpressionConfigAttribute(expressionParser.parseExpression(rule.getExpression()))); } return configAttributes; } private void decide(AccessDecisionManager manager, Authentication authentication, Transition<S, E> object, Collection<ConfigAttribute> configAttributes) { if (manager.supports(object.getClass())) { manager.decide(authentication, object, configAttributes); } } private void decide(AccessDecisionManager manager, Authentication authentication, Message<E> object, Collection<ConfigAttribute> configAttributes) { if (manager.supports(object.getClass())) { manager.decide(authentication, object, configAttributes); } } private AbstractAccessDecisionManager createDefaultTransitionManager(SecurityRule rule) { List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>(); voters.add(new TransitionExpressionVoter()); voters.add(new TransitionVoter<Object, Object>()); voters.add(new RoleVoter()); if (rule.getComparisonType() == SecurityRule.ComparisonType.ANY) { return new AffirmativeBased(voters); } else if (rule.getComparisonType() == SecurityRule.ComparisonType.ALL) { return new UnanimousBased(voters); } else if (rule.getComparisonType() == SecurityRule.ComparisonType.MAJORITY) { return new ConsensusBased(voters); } else { throw new IllegalStateException("Unknown SecurityRule match type: " + rule.getComparisonType()); } } private AbstractAccessDecisionManager createDefaultEventManager(SecurityRule rule) { List<AccessDecisionVoter<? extends Object>> voters = new ArrayList<AccessDecisionVoter<? extends Object>>(); voters.add(new EventExpressionVoter<Object>()); voters.add(new EventVoter<Object>()); voters.add(new RoleVoter()); if (rule.getComparisonType() == SecurityRule.ComparisonType.ANY) { return new AffirmativeBased(voters); } else if (rule.getComparisonType() == SecurityRule.ComparisonType.ALL) { return new UnanimousBased(voters); } else if (rule.getComparisonType() == SecurityRule.ComparisonType.MAJORITY) { return new ConsensusBased(voters); } else { throw new IllegalStateException("Unknown SecurityRule match type: " + rule.getComparisonType()); } } @Override public String toString() { return "StateMachineSecurityInterceptor [transitionAccessDecisionManager=" + transitionAccessDecisionManager + ", eventAccessDecisionManager=" + eventAccessDecisionManager + ", eventSecurityRule=" + eventSecurityRule + "]"; } }