IN PROGRESS - issue SWF-93: Add support for securing flows

http://jira.springframework.org/browse/SWF-93

IN PROGRESS - issue SWF-486: Add a <secured/> element for defining security attributes at flow, state, and transition level
http://jira.springframework.org/browse/SWF-486
This commit is contained in:
Scott Andrews
2008-02-29 22:57:51 +00:00
parent b0bf8a3079
commit 859bf1eb14
13 changed files with 717 additions and 10 deletions

View File

@@ -95,5 +95,6 @@
<module organisation="org.springframework" name="spring-xml" resolver="spring-projects"/>
<module organisation="org.springframework" name="spring-ldap" resolver="spring-projects"/>
<module organisation="org.springframework" name="various" resolver="spring-projects"/>
<module organisation="org.springframework.security" name="spring-security-core" resolver="spring-projects"/>
</modules>
</ivysettings>

View File

@@ -47,5 +47,6 @@
<classpathentry kind="lib" path="lib/test/serp.jar"/>
<classpathentry kind="lib" path="lib/test/commons-collections.jar"/>
<classpathentry kind="lib" path="lib/test/jboss-el.jar"/>
<classpathentry kind="lib" path="lib/buildtime/spring-security-core.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@@ -56,6 +56,7 @@
<dependency org="javax.el" name="el-api" rev="1.0" conf="buildtime->default" />
<dependency org="org.springframework" name="spring-orm" rev="2.5.1" conf="buildtime->default" />
<dependency org="org.springframework" name="spring-tx" rev="2.5.1" conf="buildtime->default" />
<dependency org="org.springframework.security" name="spring-security-core" rev="2.0-M2" conf="buildtime->default" />
<dependency org="javax.persistence" name="persistence-api" rev="1.0b" conf="buildtime->default" />
<dependency org="concurrent" name="concurrent" rev="1.3.4" conf="buildtime->default" />

View File

@@ -85,6 +85,7 @@ import org.springframework.webflow.execution.Action;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.execution.ScopeType;
import org.springframework.webflow.execution.ViewFactory;
import org.springframework.webflow.security.SecurityRule;
import org.springframework.webflow.util.ResourceHolder;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -116,6 +117,7 @@ import org.xml.sax.SAXException;
*
* @author Erwin Vervaet
* @author Keith Donald
* @author Scott Andrews
*/
public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolder {
@@ -237,6 +239,12 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
private static final String VIEW_ATTRIBUTE = "view";
private static final String SECURED_ELEMENT = "secured";
private static final String AUTHORITIES_ATTRIBUTE = "authorities";
private static final String MATCH_ATTRIBUTE = "match";
/**
* Prefix used when the encoded view name wants to specify that a redirect to an external URL is required.
* ("externalRedirect:")
@@ -407,6 +415,7 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
AttributeMap externallyAssignedAttributes = getLocalContext().getFlowAttributes();
MutableAttributeMap flowAttributes = parseAttributes(flowElement);
parseAndSetPersistenceContextAttribute(flowElement, flowAttributes);
parseAndSetSecuredAttribute(flowElement, flowAttributes);
return getFlowArtifactFactory().createFlow(flowId, flowAttributes.union(externallyAssignedAttributes));
}
@@ -555,9 +564,11 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
}
private void parseAndAddActionState(Element element, Flow flow) {
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
getFlowArtifactFactory().createActionState(parseId(element), flow, parseEntryActions(element),
parseAnnotatedActions(element), parseTransitions(element), parseExceptionHandlers(element),
parseExitActions(element), parseAttributes(element));
parseExitActions(element), attributes);
}
private void parseAndAddViewState(Element element, Flow flow) {
@@ -570,28 +581,34 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
if (element.hasAttribute("popup")) {
popup = ((Boolean) fromStringTo(Boolean.class).execute(element.getAttribute("popup"))).booleanValue();
}
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
getFlowArtifactFactory().createViewState(parseId(element), flow, parseViewVariables(element),
parseEntryActions(element), viewFactory, redirect, popup, parseRenderActions(element),
parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element),
parseAttributes(element));
parseTransitions(element), parseExceptionHandlers(element), parseExitActions(element), attributes);
}
private void parseAndAddDecisionState(Element element, Flow flow) {
getFlowArtifactFactory()
.createDecisionState(parseId(element), flow, parseEntryActions(element), parseIfs(element),
parseExceptionHandlers(element), parseExitActions(element), parseAttributes(element));
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
getFlowArtifactFactory().createDecisionState(parseId(element), flow, parseEntryActions(element),
parseIfs(element), parseExceptionHandlers(element), parseExitActions(element), attributes);
}
private void parseAndAddSubflowState(Element element, Flow flow) {
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
getFlowArtifactFactory().createSubflowState(parseId(element), flow, parseEntryActions(element),
parseSubflow(element), parseFlowAttributeMapper(element), parseTransitions(element),
parseExceptionHandlers(element), parseExitActions(element), parseAttributes(element));
parseExceptionHandlers(element), parseExitActions(element), attributes);
}
private void parseAndAddEndState(Element element, Flow flow) {
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
getFlowArtifactFactory().createEndState(parseId(element), flow, parseEntryActions(element),
parseFinalResponseAction(element), parseOutputMapper(element), parseExceptionHandlers(element),
parseAttributes(element));
attributes);
}
private String parseId(Element element) {
@@ -691,8 +708,10 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
TargetStateResolver targetStateResolver = (TargetStateResolver) fromStringTo(TargetStateResolver.class)
.execute(element.getAttribute(TO_ATTRIBUTE));
TransitionCriteria executionCriteria = TransitionCriteriaChain.criteriaChainFor(parseAnnotatedActions(element));
MutableAttributeMap attributes = parseAttributes(element);
parseAndSetSecuredAttribute(element, attributes);
return getFlowArtifactFactory().createTransition(targetStateResolver, matchingCriteria, executionCriteria,
parseAttributes(element));
attributes);
}
private Flow parseSubflow(Element element) {
@@ -1133,6 +1152,25 @@ public class XmlFlowBuilder extends AbstractFlowBuilder implements ResourceHolde
element.getAttribute(BEAN_ATTRIBUTE), FlowExecutionExceptionHandler.class);
}
private void parseAndSetSecuredAttribute(Element element, MutableAttributeMap attributes) {
Element secured = DomUtils.getChildElementByTagName(element, SECURED_ELEMENT);
if (secured != null) {
SecurityRule rule = new SecurityRule();
rule.setRequiredAuthorities(SecurityRule.convertAuthoritiesFromCommaSeparatedString(secured
.getAttribute(AUTHORITIES_ATTRIBUTE)));
String comparisonType = secured.getAttribute(MATCH_ATTRIBUTE);
if ("any".equals(comparisonType)) {
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
} else if ("all".equals(comparisonType)) {
rule.setComparisonType(SecurityRule.COMPARISON_ALL);
} else {
// default to any
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
}
attributes.put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
}
}
private ConversionExecutor fromStringTo(Class targetType) throws ConversionException {
return getLocalContext().getConversionService().getConversionExecutor(String.class, targetType);
}

View File

@@ -202,6 +202,9 @@ A flow may also exhibit the following characteristics:
<li>Be annotated with attributes that define descriptive properties that may affect flow execution.
(See the &lt;attribute/&gt; element)
<li>Secured flow
(See the &lt;secured/&gt; element)
<li>Be a persistence context for managing persistent objects during the course of flow execution.
(See the &lt;persistence-context/&gt; element)
@@ -241,6 +244,15 @@ An attribute describing this flow.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this flow is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="persistence-context" minOccurs="0">
<xsd:annotation>
@@ -673,6 +685,15 @@ An attribute describing this state.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="entry-actions" minOccurs="0">
<xsd:annotation>
@@ -1218,6 +1239,15 @@ An attribute describing this transition.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this transition is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:group ref="actionTypes" minOccurs="0" maxOccurs="unbounded" />
</xsd:sequence>
@@ -1295,6 +1325,15 @@ An attribute describing this state.
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="var" minOccurs="0" maxOccurs="unbounded">
<xsd:annotation>
<xsd:documentation>
@@ -1304,7 +1343,7 @@ destroyed when the view-state exits. A view variable is stored in flow scope.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
</xsd:element>
<xsd:element ref="entry-actions" minOccurs="0">
<xsd:annotation>
<xsd:documentation>
@@ -1455,6 +1494,15 @@ An attribute describing this state.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="entry-actions" minOccurs="0">
<xsd:annotation>
@@ -1572,6 +1620,15 @@ An attribute describing this state.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="entry-actions" minOccurs="0">
<xsd:annotation>
@@ -1735,6 +1792,15 @@ An attribute describing this state.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="secured" minOccurs="0" maxOccurs="1">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Indicates this state is secured
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:element>
<xsd:element ref="entry-actions" minOccurs="0">
<xsd:annotation>
@@ -1914,5 +1980,58 @@ For example:
</xsd:attribute>
</xsd:complexType>
</xsd:element>
<xsd:element name="secured">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Applies attributes to Spring Secuirty based principal authorities upon enterance.
If the security requirement is not met, an AccessDeniedException is thrown.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:complexType>
<xsd:attribute name="authorities" type="xsd:string" use="required">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Comma separated list of security authorities.
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:attribute>
<xsd:attribute name="match" use="optional" default="any">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Method used to match authorities. Accepted values are 'any' and 'all'. Default is 'any'.
]]>
</xsd:documentation>
</xsd:annotation>
<xsd:simpleType>
<xsd:restriction base="xsd:string">
<xsd:enumeration value="any">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Matching any of the authorities will allow access
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
<xsd:enumeration value="all">
<xsd:annotation>
<xsd:documentation>
<![CDATA[
Matching all of the authorities will allow access
]]>
</xsd:documentation>
</xsd:annotation>
</xsd:enumeration>
</xsd:restriction>
</xsd:simpleType>
</xsd:attribute>
</xsd:complexType>
</xsd:element>
</xsd:schema>

View File

@@ -0,0 +1,84 @@
package org.springframework.webflow.security;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.definition.StateDefinition;
import org.springframework.webflow.definition.TransitionDefinition;
import org.springframework.webflow.execution.EnterStateVetoException;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;
/**
* Flow security integration with Spring Security
*
* @author Scott Andrews
*/
public class SecurityFlowExecutionListener extends FlowExecutionListenerAdapter {
/**
* Check security authorization when flow session starts
*/
public void sessionCreating(RequestContext context, FlowDefinition definition) {
SecurityRule rule = (SecurityRule) definition.getAttributes().get(
SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
if (rule != null) {
Collection principalAuthorities = getPrincipalAuthorities();
if (!rule.isAuthorized(principalAuthorities)) {
throw new AccessDeniedException("Required authority not found: "
+ SecurityRule.convertAuthoritiesToCommaSeparatedString(rule
.getNonGrantedAuthorities(principalAuthorities)));
}
}
}
/**
* Check security authorization when entering state
*/
public void stateEntering(RequestContext context, StateDefinition state) throws EnterStateVetoException {
SecurityRule rule = (SecurityRule) state.getAttributes().get(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
if (rule != null) {
Collection principalAuthorities = getPrincipalAuthorities();
if (!rule.isAuthorized(principalAuthorities)) {
throw new AccessDeniedException("Required authority not found: "
+ SecurityRule.convertAuthoritiesToCommaSeparatedString(rule
.getNonGrantedAuthorities(principalAuthorities)));
}
}
}
/**
* Check security authorization on transition
*/
public void transitionExecuting(RequestContext context, TransitionDefinition transition) {
SecurityRule rule = (SecurityRule) transition.getAttributes().get(
SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
if (rule != null) {
Collection principalAuthorities = getPrincipalAuthorities();
if (!rule.isAuthorized(principalAuthorities)) {
throw new AccessDeniedException("Required authority not found: "
+ SecurityRule.convertAuthoritiesToCommaSeparatedString(rule
.getNonGrantedAuthorities(principalAuthorities)));
}
}
}
/**
* Get Spring Security authorities for the principal
* @return granted authorities for the principal
*/
protected Collection getPrincipalAuthorities() {
Authentication currentUser = SecurityContextHolder.getContext().getAuthentication();
if ((null == currentUser) || (null == currentUser.getAuthorities())
|| (currentUser.getAuthorities().length < 1)) {
return Collections.EMPTY_LIST;
}
return Arrays.asList(currentUser.getAuthorities());
}
}

View File

@@ -0,0 +1,155 @@
package org.springframework.webflow.security;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
/**
* Encapsulates the rules for comparing security authorities
*
* @author Scott Andrews
*/
public class SecurityRule {
/**
* Attribute name for the location of the security rule
*/
public static final String SECURITY_AUTHORITY_ATTRIBUTE_NAME = "secured";
/**
* Compare method where any of the required authorities can match the principal's authorities
*/
public static final short COMPARISON_ANY = 1;
/**
* Compare method where all of the required authorities must match the principal's authorities
*/
public static final short COMPARISON_ALL = 2;
private Collection requiredAuthorities;
private short comparisonType;
/**
* Test the required authorities against the principal's authorities
* @param principalAuthorities the principal's granted authorities
* @return true if authorized
*/
public boolean isAuthorized(Collection principalAuthorities) {
if (getComparisonType() == COMPARISON_ANY) {
return isAuthorizedAny(principalAuthorities);
} else if (getComparisonType() == COMPARISON_ALL) {
return isAuthorizedAll(principalAuthorities);
} else {
throw new IllegalStateException("Unknow comparisonType");
}
}
/**
* Get authorities that are required but not granted
* @param principalAuthorities the principal's granted authorities
* @return non granted authorities
*/
public Collection getNonGrantedAuthorities(Collection principalAuthorities) {
Collection nonGrantedAuthorities = new HashSet();
Iterator authorityIt = getRequiredAuthorities().iterator();
while (authorityIt.hasNext()) {
String authority = (String) authorityIt.next();
if (!principalAuthorities.contains(authority)) {
nonGrantedAuthorities.add(authority);
}
}
return nonGrantedAuthorities;
}
/**
* Convert authorities to comma separated String
* @param authorities the authorities to convert
* @return comma separated String
*/
public static String convertAuthoritiesToCommaSeparatedString(Collection authorities) {
StringBuffer str = new StringBuffer();
Iterator authorityIt = authorities.iterator();
while (authorityIt.hasNext()) {
if (str.length() != 0) {
str.append(", ");
}
str.append(authorityIt.next());
}
return str.toString();
}
/**
* Convert authorities from comma separated String to Collection
* @param authorities the authorities to convert
* @return comma parsed Collection
*/
public static Collection convertAuthoritiesFromCommaSeparatedString(String authorities) {
Collection auths = new HashSet();
Iterator authorityIt = Arrays.asList(authorities.split(",")).iterator();
while (authorityIt.hasNext()) {
String authority = ((String) authorityIt.next()).trim();
if (!"".equals(authority)) {
auths.add(authority);
}
}
return auths;
}
/**
* Test that any of the required authorities match the principal's authorities
* @param principalAuthorities the principal's granted authorities
* @return true if authorized
*/
private boolean isAuthorizedAny(Collection principalAuthorities) {
boolean authorized = false;
Iterator authorityIt = principalAuthorities.iterator();
while (!authorized && authorityIt.hasNext()) {
if (getRequiredAuthorities().contains(authorityIt.next())) {
authorized = true;
}
}
return authorized;
}
/**
* Test that all of the required authorities match the principal's authorities
* @param principalAuthorities the principal's granted authorities
* @return true if authorized
*/
private boolean isAuthorizedAll(Collection principalAuthorities) {
return getRequiredAuthorities().containsAll(principalAuthorities);
}
/**
* Gets required authorities
* @return required authorities
*/
public Collection getRequiredAuthorities() {
return requiredAuthorities;
}
/**
* Sets required authorities
* @param requiredAuthorities required authorities
*/
public void setRequiredAuthorities(Collection requiredAuthorities) {
this.requiredAuthorities = requiredAuthorities;
}
/**
* Gets comparison type
* @return comparison type
*/
public short getComparisonType() {
return comparisonType;
}
/**
* Sets comparison type
* @param comparisonType comparison type
*/
public void setComparisonType(short comparisonType) {
this.comparisonType = comparisonType;
}
}

View File

@@ -8,6 +8,7 @@ import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.builder.FlowAssembler;
import org.springframework.webflow.engine.builder.FlowBuilderException;
import org.springframework.webflow.security.SecurityRule;
import org.springframework.webflow.test.MockFlowBuilderContext;
public class XmlFlowBuilderTests extends TestCase {
@@ -75,6 +76,44 @@ public class XmlFlowBuilderTests extends TestCase {
assertTrue(((Boolean) flow.getAttributes().get("persistenceContext")).booleanValue());
}
public void testFlowSecured() {
ClassPathResource resource = new ClassPathResource("flow-secured.xml", getClass());
builder = new XmlFlowBuilder(resource);
FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow"));
Flow flow = assembler.assembleFlow();
SecurityRule rule = (SecurityRule) flow.getAttributes().get(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
assertNotNull(rule);
assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType());
assertEquals(1, rule.getRequiredAuthorities().size());
assertTrue(rule.getRequiredAuthorities().contains("ROLE_USER"));
}
public void testFlowSecuredState() {
ClassPathResource resource = new ClassPathResource("flow-secured-state.xml", getClass());
builder = new XmlFlowBuilder(resource);
FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow"));
Flow flow = assembler.assembleFlow();
SecurityRule rule = (SecurityRule) flow.getState("end").getAttributes().get(
SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
assertNotNull(rule);
assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType());
assertEquals(1, rule.getRequiredAuthorities().size());
assertTrue(rule.getRequiredAuthorities().contains("ROLE_USER"));
}
public void testFlowSecuredTransition() {
ClassPathResource resource = new ClassPathResource("flow-secured-transition.xml", getClass());
builder = new XmlFlowBuilder(resource);
FlowAssembler assembler = new FlowAssembler(builder, new MockFlowBuilderContext("flow"));
Flow flow = assembler.assembleFlow();
SecurityRule rule = (SecurityRule) flow.getGlobalTransitionSet().toArray()[0].getAttributes().get(
SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME);
assertNotNull(rule);
assertEquals(SecurityRule.COMPARISON_ANY, rule.getComparisonType());
assertEquals(1, rule.getRequiredAuthorities().size());
assertTrue(rule.getRequiredAuthorities().contains("ROLE_USER"));
}
public void testFlowVariable() {
ClassPathResource resource = new ClassPathResource("flow-var.xml", getClass());
builder = new XmlFlowBuilder(resource);

View File

@@ -0,0 +1,9 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<end-state id="end">
<secured authorities="ROLE_USER"/>
</end-state>
</flow>

View File

@@ -0,0 +1,13 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<end-state id="end"/>
<global-transitions>
<transition to="end">
<secured authorities="ROLE_USER"/>
</transition>
</global-transitions>
</flow>

View File

@@ -0,0 +1,9 @@
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<secured authorities="ROLE_USER"/>
<end-state id="end"/>
</flow>

View File

@@ -0,0 +1,152 @@
package org.springframework.webflow.security;
import java.util.Collection;
import java.util.HashSet;
import junit.framework.TestCase;
import org.springframework.security.AccessDeniedException;
import org.springframework.security.Authentication;
import org.springframework.security.GrantedAuthority;
import org.springframework.security.GrantedAuthorityImpl;
import org.springframework.security.context.SecurityContext;
import org.springframework.security.context.SecurityContextHolder;
import org.springframework.security.context.SecurityContextImpl;
import org.springframework.security.providers.TestingAuthenticationToken;
import org.springframework.webflow.core.collection.LocalAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.engine.Flow;
import org.springframework.webflow.engine.StubViewFactory;
import org.springframework.webflow.engine.Transition;
import org.springframework.webflow.engine.ViewState;
import org.springframework.webflow.engine.support.DefaultTargetStateResolver;
import org.springframework.webflow.execution.RequestContext;
import org.springframework.webflow.test.MockRequestContext;
public class SecurityFlowExecutionListenerTests extends TestCase {
public void testSessionCreatingNoSecurity() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
FlowDefinition definition = new Flow("flow");
listener.sessionCreating(context, definition);
}
public void testSessionCreatingAuthorized() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Flow flow = new Flow("flow");
SecurityRule rule = getSecurityRuleAuthorized();
((LocalAttributeMap) flow.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
listener.sessionCreating(context, flow);
}
public void testSessionCreatingDenied() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Flow flow = new Flow("flow");
SecurityRule rule = getSecurityRuleDenied();
((LocalAttributeMap) flow.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
try {
listener.sessionCreating(context, flow);
fail("expected AccessDeniedException");
} catch (AccessDeniedException e) {
// success
}
}
public void testStateEnteringNoSecurity() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Flow flow = new Flow("flow");
ViewState state = new ViewState(flow, "view", new StubViewFactory());
listener.stateEntering(context, state);
}
public void testStateEnteringAuthorized() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Flow flow = new Flow("flow");
ViewState state = new ViewState(flow, "view", new StubViewFactory());
SecurityRule rule = getSecurityRuleAuthorized();
((LocalAttributeMap) state.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
listener.stateEntering(context, state);
}
public void testStateEnteringDenied() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Flow flow = new Flow("flow");
ViewState state = new ViewState(flow, "view", new StubViewFactory());
SecurityRule rule = getSecurityRuleDenied();
((LocalAttributeMap) state.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
try {
listener.stateEntering(context, state);
fail("expected AccessDeniedException");
} catch (AccessDeniedException e) {
// success
}
}
public void testTransitionExecutingNoSecurity() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Transition transition = new Transition(new DefaultTargetStateResolver("target"));
listener.transitionExecuting(context, transition);
}
public void testTransitionExecutingAuthorized() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Transition transition = new Transition(new DefaultTargetStateResolver("target"));
SecurityRule rule = getSecurityRuleAuthorized();
((LocalAttributeMap) transition.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
listener.transitionExecuting(context, transition);
}
public void testTransitionExecutingDenied() {
SecurityFlowExecutionListener listener = new SecurityFlowExecutionListener();
RequestContext context = new MockRequestContext();
Transition transition = new Transition(new DefaultTargetStateResolver("target"));
SecurityRule rule = getSecurityRuleDenied();
((LocalAttributeMap) transition.getAttributes()).put(SecurityRule.SECURITY_AUTHORITY_ATTRIBUTE_NAME, rule);
configureSecurityContext();
try {
listener.transitionExecuting(context, transition);
fail("expected AccessDeniedException");
} catch (AccessDeniedException e) {
// success
}
}
private SecurityRule getSecurityRuleAuthorized() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
Collection authorities = new HashSet();
authorities.add("ROLE_USER");
rule.setRequiredAuthorities(authorities);
return rule;
}
private SecurityRule getSecurityRuleDenied() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
Collection authorities = new HashSet();
authorities.add("ROLE_ANONYMOUS");
rule.setRequiredAuthorities(authorities);
return rule;
}
private void configureSecurityContext() {
GrantedAuthority[] authorities = { new GrantedAuthorityImpl("ROLE_USER") };
Authentication authentication = new TestingAuthenticationToken("test", "", authorities);
SecurityContext sc = new SecurityContextImpl();
sc.setAuthentication(authentication);
SecurityContextHolder.setContext(sc);
}
}

View File

@@ -0,0 +1,86 @@
package org.springframework.webflow.security;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import junit.framework.Assert;
import junit.framework.TestCase;
public class SecurityRuleTests extends TestCase {
public void testAuthorizedAll() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ALL);
Collection requiredAuthorities = new HashSet();
requiredAuthorities.add("ROLE_USER");
requiredAuthorities.add("ROLE_SUPERVISOR");
rule.setRequiredAuthorities(requiredAuthorities);
Assert.assertTrue(rule.isAuthorized(getPrincipalAuthorities()));
}
public void testAuthorizedAllFail() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ALL);
Collection requiredAuthorities = new HashSet();
requiredAuthorities.add("ROLE_USER");
requiredAuthorities.add("ROLE_ANONYMOUS");
rule.setRequiredAuthorities(requiredAuthorities);
Assert.assertFalse(rule.isAuthorized(getPrincipalAuthorities()));
}
public void testAuthorizedAny() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
Collection requiredAuthorities = new HashSet();
requiredAuthorities.add("ROLE_USER");
requiredAuthorities.add("ROLE_ANONYMOUS");
rule.setRequiredAuthorities(requiredAuthorities);
Assert.assertTrue(rule.isAuthorized(getPrincipalAuthorities()));
}
public void testAuthorizedAnyFail() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ANY);
Collection requiredAuthorities = new HashSet();
requiredAuthorities.add("ROLE_NONE");
requiredAuthorities.add("ROLE_ANONYMOUS");
rule.setRequiredAuthorities(requiredAuthorities);
Assert.assertFalse(rule.isAuthorized(getPrincipalAuthorities()));
}
public void testNonGrantedAuthorities() {
SecurityRule rule = new SecurityRule();
rule.setComparisonType(SecurityRule.COMPARISON_ALL);
Collection requiredAuthorities = new HashSet();
requiredAuthorities.add("ROLE_USER");
requiredAuthorities.add("ROLE_ANONYMOUS");
rule.setRequiredAuthorities(requiredAuthorities);
Collection nonGrantedAuthorities = rule.getNonGrantedAuthorities(getPrincipalAuthorities());
Assert.assertEquals(1, nonGrantedAuthorities.size());
Assert.assertTrue(nonGrantedAuthorities.contains("ROLE_ANONYMOUS"));
}
public void testConvertAuthoritiesToCommaSeparatedString() {
Collection authorities = new ArrayList();
authorities.add("ROLE_USER");
authorities.add("ROLE_ANONYMOUS");
Assert.assertEquals("ROLE_USER, ROLE_ANONYMOUS", SecurityRule
.convertAuthoritiesToCommaSeparatedString(authorities));
}
public void testConvertAuthoritiesFromCommaSeparatedString() {
Collection authorities = SecurityRule
.convertAuthoritiesFromCommaSeparatedString(" ,,ROLE_USER, ROLE_ANONYMOUS");
Assert.assertEquals(2, authorities.size());
Assert.assertTrue(authorities.contains("ROLE_USER"));
Assert.assertTrue(authorities.contains("ROLE_ANONYMOUS"));
}
private Collection getPrincipalAuthorities() {
Collection principalAuthorities = new HashSet();
principalAuthorities.add("ROLE_USER");
principalAuthorities.add("ROLE_SUPERVISOR");
return principalAuthorities;
}
}