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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 <attribute/> element)
|
||||
|
||||
<li>Secured flow
|
||||
(See the <secured/> element)
|
||||
|
||||
<li>Be a persistence context for managing persistent objects during the course of flow execution.
|
||||
(See the <persistence-context/> 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>
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user