Add base security support
- Add base of support using spring security to protect events, transitions and actions. - Fixes #114
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -8,6 +8,7 @@ build
|
|||||||
*.log
|
*.log
|
||||||
*.ipr
|
*.ipr
|
||||||
*.iws
|
*.iws
|
||||||
|
*.swp
|
||||||
metastore_db
|
metastore_db
|
||||||
/src/test/resources/s3.properties
|
/src/test/resources/s3.properties
|
||||||
/.idea/
|
/.idea/
|
||||||
|
|||||||
18
build.gradle
18
build.gradle
@@ -27,7 +27,7 @@ configure(allprojects) {
|
|||||||
apply plugin: 'java'
|
apply plugin: 'java'
|
||||||
apply plugin: 'eclipse'
|
apply plugin: 'eclipse'
|
||||||
apply plugin: 'idea'
|
apply plugin: 'idea'
|
||||||
|
apply plugin: 'propdeps'
|
||||||
|
|
||||||
compileJava {
|
compileJava {
|
||||||
sourceCompatibility = 1.7
|
sourceCompatibility = 1.7
|
||||||
@@ -59,9 +59,9 @@ configure(allprojects) {
|
|||||||
// servlet-api (2.5) and tomcat-servlet-api (3.0) classpath entries should not be
|
// servlet-api (2.5) and tomcat-servlet-api (3.0) classpath entries should not be
|
||||||
// exported to dependent projects in Eclipse to avoid false compilation errors due
|
// exported to dependent projects in Eclipse to avoid false compilation errors due
|
||||||
// to changing APIs across these versions
|
// to changing APIs across these versions
|
||||||
eclipse.classpath.file.whenMerged { classpath ->
|
// eclipse.classpath.file.whenMerged { classpath ->
|
||||||
classpath.entries.findAll { entry -> entry.path.contains('servlet-api') }*.exported = false
|
// classpath.entries.findAll { entry -> entry.path.contains('servlet-api') }*.exported = false
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(subprojects) { subproject ->
|
configure(subprojects) { subproject ->
|
||||||
@@ -116,6 +116,7 @@ project('spring-statemachine-core') {
|
|||||||
dependencies {
|
dependencies {
|
||||||
compile "org.springframework:spring-tx:$springVersion"
|
compile "org.springframework:spring-tx:$springVersion"
|
||||||
compile "org.springframework:spring-messaging:$springVersion"
|
compile "org.springframework:spring-messaging:$springVersion"
|
||||||
|
optional "org.springframework.security:spring-security-core:$springSecurityVersion"
|
||||||
|
|
||||||
testCompile "org.springframework:spring-test:$springVersion"
|
testCompile "org.springframework:spring-test:$springVersion"
|
||||||
testCompile "org.springframework:spring-web:$springVersion"
|
testCompile "org.springframework:spring-web:$springVersion"
|
||||||
@@ -124,8 +125,14 @@ project('spring-statemachine-core') {
|
|||||||
testCompile "org.apache.tomcat.embed:tomcat-embed-logging-juli:$tomcatEmbedVersion"
|
testCompile "org.apache.tomcat.embed:tomcat-embed-logging-juli:$tomcatEmbedVersion"
|
||||||
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
testCompile "org.hamcrest:hamcrest-core:$hamcrestVersion"
|
||||||
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
testCompile "org.hamcrest:hamcrest-library:$hamcrestVersion"
|
||||||
|
testCompile("org.mockito:mockito-core:$mockitoVersion") { dep ->
|
||||||
|
exclude group: "org.hamcrest"
|
||||||
|
}
|
||||||
testCompile "junit:junit:$junitVersion"
|
testCompile "junit:junit:$junitVersion"
|
||||||
testRuntime("log4j:log4j:$log4jVersion")
|
testCompile "org.springframework.security:spring-security-config:$springSecurityVersion"
|
||||||
|
testCompile "org.springframework.security:spring-security-test:$springSecurityVersion"
|
||||||
|
testCompile "javax.servlet:javax.servlet-api:$servletApiVersion"
|
||||||
|
testRuntime "log4j:log4j:$log4jVersion"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,6 +271,7 @@ configure(rootProject) {
|
|||||||
from 'spring-statemachine-samples/cdplayer/src/main/java/'
|
from 'spring-statemachine-samples/cdplayer/src/main/java/'
|
||||||
from 'spring-statemachine-samples/persist/src/main/java/'
|
from 'spring-statemachine-samples/persist/src/main/java/'
|
||||||
from 'spring-statemachine-samples/zookeeper/src/main/java/'
|
from 'spring-statemachine-samples/zookeeper/src/main/java/'
|
||||||
|
from 'spring-statemachine-samples/security/src/main/java/'
|
||||||
include '**/*.java'
|
include '**/*.java'
|
||||||
into 'docs/src/reference/asciidoc/samples'
|
into 'docs/src/reference/asciidoc/samples'
|
||||||
}
|
}
|
||||||
|
|||||||
BIN
docs/src/reference/asciidoc/images/statechart13.png
Normal file
BIN
docs/src/reference/asciidoc/images/statechart13.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.3 KiB |
@@ -28,6 +28,8 @@ normal build cycle. Samples in this chapter are:
|
|||||||
|
|
||||||
<<statemachine-examples-scope>> Scope.
|
<<statemachine-examples-scope>> Scope.
|
||||||
|
|
||||||
|
<<statemachine-examples-security>> Security.
|
||||||
|
|
||||||
|
|
||||||
[source,text]
|
[source,text]
|
||||||
----
|
----
|
||||||
@@ -1070,3 +1072,62 @@ _Chrome_ and one in _Firefox_, you should get a new state machine
|
|||||||
instance per user session.
|
instance per user session.
|
||||||
|
|
||||||
image::images/sm-scope-1.png[width=500]
|
image::images/sm-scope-1.png[width=500]
|
||||||
|
|
||||||
|
[[statemachine-examples-security]]
|
||||||
|
== Security
|
||||||
|
Security is a state machine example using most of a compinations of
|
||||||
|
securing a state machine. It is securing sending events, transitions
|
||||||
|
and actions.
|
||||||
|
|
||||||
|
image::images/statechart13.png[width=500]
|
||||||
|
|
||||||
|
[source,text,subs="attributes"]
|
||||||
|
----
|
||||||
|
@n1:~# java -jar spring-statemachine-samples-secure-{revnumber}.jar
|
||||||
|
----
|
||||||
|
|
||||||
|
We secure event sending with a users having a role `USER`. None of
|
||||||
|
a other users imposed by a _Spring Security_ can't send events into a
|
||||||
|
state machine.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/demo/security/StateMachineConfig.java[tags=snippetA]
|
||||||
|
----
|
||||||
|
|
||||||
|
In this sample we define two users, _user_ having a role `USER` and
|
||||||
|
_admin_ having both roles `USER` and `ADMIN`. Authentication for both
|
||||||
|
user for password is `password`.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/demo/security/StateMachineConfig.java[tags=snippetE]
|
||||||
|
----
|
||||||
|
|
||||||
|
We define various transitions between states according to a statechart
|
||||||
|
seen above. Only a user with active `ADMIN` role can execute
|
||||||
|
external transitions between `S2` and `S3`. Similarly `ADMIN` can only
|
||||||
|
execute internal transition in a state `S1`.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/demo/security/StateMachineConfig.java[tags=snippetB]
|
||||||
|
----
|
||||||
|
|
||||||
|
`Action` `adminAction` is secured with a role `ADMIN`.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/demo/security/StateMachineConfig.java[tags=snippetC]
|
||||||
|
----
|
||||||
|
|
||||||
|
Below `Action` would only be executed with internal transition in a
|
||||||
|
state `S1` when event `F` is send. Transition itself is secured with a
|
||||||
|
role `ADMIN` so this transition will not be executed if current user
|
||||||
|
does not hate that role.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/demo/security/StateMachineConfig.java[tags=snippetD]
|
||||||
|
----
|
||||||
|
|
||||||
|
|||||||
@@ -894,6 +894,170 @@ More about error handling shown in above example, see section
|
|||||||
<<sm-error-handling>>.
|
<<sm-error-handling>>.
|
||||||
====
|
====
|
||||||
|
|
||||||
|
[[sm-security]]
|
||||||
|
== State Machine Security
|
||||||
|
Security features are build atop of functionality from a _Spring
|
||||||
|
Security_. Security features are handy when it is required to protect
|
||||||
|
part of a state machine execution and interaction with it. More
|
||||||
|
detailed info can be found from section <<sm-security-details>>.
|
||||||
|
|
||||||
|
[IMPORTANT]
|
||||||
|
====
|
||||||
|
We expect user to be fairly familiar with a _Spring Security_ meaning
|
||||||
|
we don't go into details of how overall security framework works. For
|
||||||
|
this read _Spring Security_ reference documentation.
|
||||||
|
====
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
====
|
||||||
|
For complete example, see sample <<statemachine-examples-security>>.
|
||||||
|
====
|
||||||
|
|
||||||
|
=== Configuring Security
|
||||||
|
All generic configurations for security are done from
|
||||||
|
`SecurityConfigurer` which is obtained from
|
||||||
|
`StateMachineConfigurationConfigurer`. Security is disabled on
|
||||||
|
default even if _Spring Security_ classes are
|
||||||
|
present.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetD]
|
||||||
|
----
|
||||||
|
|
||||||
|
If absolutely needed `AccessDecisionManager` for both events and
|
||||||
|
transitions can be customised. If decision managers are not defined or
|
||||||
|
are set to `null`, default managers are created internally.
|
||||||
|
|
||||||
|
=== Securing Events
|
||||||
|
Event security is defined on a global level within a
|
||||||
|
`SecurityConfigurer`.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetA]
|
||||||
|
----
|
||||||
|
|
||||||
|
In above configuration we use expression `true` which always evaluates
|
||||||
|
to _TRUE_. Using an expression which always evaluates to _TRUE_
|
||||||
|
would not make sense in a real application but gives a point that
|
||||||
|
expression needs to return either _TRUE_ or _FALSE_. We also defined
|
||||||
|
attribute `ROLE_ANONYMOUS` and `ComparisonType` `ANY`. Using attributes
|
||||||
|
and expressions, see section <<sm-security-attributes-expressions>>.
|
||||||
|
|
||||||
|
=== Securing Transitions
|
||||||
|
Transition security can be defined globally.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetF]
|
||||||
|
----
|
||||||
|
|
||||||
|
If security is defined in a transition itself it will override any
|
||||||
|
globally set security.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetB]
|
||||||
|
----
|
||||||
|
|
||||||
|
Using attributes and expressions, see section <<sm-security-attributes-expressions>>.
|
||||||
|
|
||||||
|
=== Securing Actions
|
||||||
|
There are no dedicated security definitions for actions in a state
|
||||||
|
machine, but it can be accomplished using a global method security
|
||||||
|
from a _Spring Security_. This simply needs that an `Action` is
|
||||||
|
defined as a proxied `@Bean` and its `execute` method annotated with a
|
||||||
|
`@Secured`.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetC]
|
||||||
|
----
|
||||||
|
|
||||||
|
Global method security needs to be enabled with a _Spring Security_
|
||||||
|
which is done with along a lines shown below. See _Spring Security_
|
||||||
|
reference docs for more details.
|
||||||
|
|
||||||
|
[source,java,indent=0]
|
||||||
|
----
|
||||||
|
include::samples/DocsConfigurationSampleTests3.java[tags=snippetE]
|
||||||
|
----
|
||||||
|
|
||||||
|
[[sm-security-attributes-expressions]]
|
||||||
|
=== Using Security Attributes and Expressions
|
||||||
|
Generally there are two ways to define security properties, firstly
|
||||||
|
using security attributes and secondly using security expressions.
|
||||||
|
Attributes are easier to use but are relatively limited in terms of
|
||||||
|
functionality. Expressions provide more features but are a little bit
|
||||||
|
of harder to use.
|
||||||
|
|
||||||
|
This section is splitted into different subsections because we're
|
||||||
|
re-using something from a _Spring Security_ and then do custom
|
||||||
|
attributes and expression handling for events and transitions.
|
||||||
|
|
||||||
|
==== Generic Attribute Usage
|
||||||
|
On default `AccessDecisionManager` instances for events and
|
||||||
|
transitions use a `RoleVoter`, meaning you can use role attributes.
|
||||||
|
|
||||||
|
For attributes we have 3 different comparison types, `ANY`, `ALL` and
|
||||||
|
`MAJORITY` which maps into voters `AffirmativeBased`, `UnanimousBased`
|
||||||
|
and `ConsensusBased` respectively.
|
||||||
|
|
||||||
|
==== Generic Expression Usage
|
||||||
|
Security expressions needs to return either _TRUE_ or _FALSE_.
|
||||||
|
|
||||||
|
==== Event Attributes
|
||||||
|
Event id can be matched by using prefix _EVENT__. For example matching
|
||||||
|
event `A` would match with attribte _EVENT_A_.
|
||||||
|
|
||||||
|
==== Event Expressions
|
||||||
|
For generic options for expressions, see _Spring Security_ docs. We
|
||||||
|
inherit from `SecurityExpressionRoot` and options from there are
|
||||||
|
available.
|
||||||
|
|
||||||
|
==== Transition Attributes
|
||||||
|
Matching transition sources and targets, use prefixes
|
||||||
|
_TRANSITION_SOURCE__ and _TRANSITION_TARGET__ respectively.
|
||||||
|
|
||||||
|
==== Transition Expressions
|
||||||
|
For generic options for expressions, see _Spring Security_ docs. We
|
||||||
|
inherit from `SecurityExpressionRoot` and options from there are
|
||||||
|
available.
|
||||||
|
|
||||||
|
[[sm-security-details]]
|
||||||
|
=== Understanding Security
|
||||||
|
This section provides more detailed info how security works within a
|
||||||
|
state machine. Not really something you'd need to know but it is
|
||||||
|
always better to be transparent instead of hiding all the magic what
|
||||||
|
happens behind a scenes.
|
||||||
|
|
||||||
|
Security only makes sense if _State Machine_ is executed in a wallet
|
||||||
|
garden where user don't have direct access to the application thus
|
||||||
|
could modify Spring Security's `SecurityContext` hold in a thread
|
||||||
|
local. If user controls the jvm, then effectively there is no security
|
||||||
|
at all.
|
||||||
|
|
||||||
|
Integration point for security is done with a
|
||||||
|
`StateMachineInterceptor` which is then added automatically into a
|
||||||
|
state machine if security is enabled. Specific class is a
|
||||||
|
`StateMachineSecurityInterceptor` which intercepts events and
|
||||||
|
transitions. This interceptor then consults Spring Security's
|
||||||
|
`AccessDecisionManager` if event can be send or if transition can be
|
||||||
|
executed. Effectively if decision or vote with a `AccessDecisionManager`
|
||||||
|
will result an exception, event or transition is denied.
|
||||||
|
|
||||||
|
Due to way how `AccessDecisionManager` from Spring Security works, we
|
||||||
|
need one instance of it per secured object. This is a reason why there
|
||||||
|
is a different manager for events and transitions. In this case events
|
||||||
|
and transitions are different class objects we're securing.
|
||||||
|
|
||||||
|
On default for events, voters `EventExpressionVoter`, `EventVoter` and
|
||||||
|
`RoleVoter` are added into a `AccessDecisionManager`.
|
||||||
|
|
||||||
|
On default for transitions, voters `TransitionExpressionVoter`,
|
||||||
|
`TransitionVoter` and `RoleVoter` are added into a `AccessDecisionManager`.
|
||||||
|
|
||||||
[[sm-error-handling]]
|
[[sm-error-handling]]
|
||||||
== State Machine Error Handling
|
== State Machine Error Handling
|
||||||
If state machine detects an internal error during a state transition
|
If state machine detects an internal error during a state transition
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
springShellVersion=1.1.0.RELEASE
|
springShellVersion=1.1.0.RELEASE
|
||||||
springSecurityVersion=4.0.2.RELEASE
|
springSecurityVersion=4.0.3.RELEASE
|
||||||
junitVersion=4.12
|
junitVersion=4.12
|
||||||
springVersion=4.2.2.RELEASE
|
springVersion=4.2.2.RELEASE
|
||||||
kryoVersion=2.24.0
|
kryoVersion=2.24.0
|
||||||
log4jVersion=1.2.17
|
log4jVersion=1.2.17
|
||||||
springBootVersion=1.2.5.RELEASE
|
springBootVersion=1.2.5.RELEASE
|
||||||
version=1.1.0.BUILD-SNAPSHOT
|
version=1.1.0.BUILD-SNAPSHOT
|
||||||
|
mockitoVersion = 1.9.5
|
||||||
hamcrestVersion=1.3
|
hamcrestVersion=1.3
|
||||||
springSessionVersion=1.0.2.RELEASE
|
springSessionVersion=1.0.2.RELEASE
|
||||||
curatorVersion=2.8.0
|
curatorVersion=2.8.0
|
||||||
tomcatEmbedVersion=7.0.56
|
tomcatEmbedVersion=7.0.56
|
||||||
|
servletApiVersion=3.0.1
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ include 'spring-statemachine-samples:zookeeper'
|
|||||||
include 'spring-statemachine-samples:persist'
|
include 'spring-statemachine-samples:persist'
|
||||||
include 'spring-statemachine-samples:web'
|
include 'spring-statemachine-samples:web'
|
||||||
include 'spring-statemachine-samples:scope'
|
include 'spring-statemachine-samples:scope'
|
||||||
|
include 'spring-statemachine-samples:security'
|
||||||
|
|
||||||
rootProject.children.find {
|
rootProject.children.find {
|
||||||
if (it.name == 'spring-statemachine-recipes') {
|
if (it.name == 'spring-statemachine-recipes') {
|
||||||
|
|||||||
@@ -24,6 +24,8 @@ import java.util.Map;
|
|||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Stack;
|
import java.util.Stack;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.beans.factory.BeanNameAware;
|
import org.springframework.beans.factory.BeanNameAware;
|
||||||
import org.springframework.context.SmartLifecycle;
|
import org.springframework.context.SmartLifecycle;
|
||||||
@@ -43,6 +45,7 @@ import org.springframework.statemachine.config.builders.StateMachineTransitions.
|
|||||||
import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
import org.springframework.statemachine.ensemble.DistributedStateMachine;
|
||||||
import org.springframework.statemachine.listener.StateMachineListener;
|
import org.springframework.statemachine.listener.StateMachineListener;
|
||||||
import org.springframework.statemachine.region.Region;
|
import org.springframework.statemachine.region.Region;
|
||||||
|
import org.springframework.statemachine.security.StateMachineSecurityInterceptor;
|
||||||
import org.springframework.statemachine.state.ChoicePseudoState;
|
import org.springframework.statemachine.state.ChoicePseudoState;
|
||||||
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
import org.springframework.statemachine.state.ChoicePseudoState.ChoiceStateData;
|
||||||
import org.springframework.statemachine.state.DefaultPseudoState;
|
import org.springframework.statemachine.state.DefaultPseudoState;
|
||||||
@@ -80,6 +83,8 @@ import org.springframework.util.ObjectUtils;
|
|||||||
public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectSupport implements
|
public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectSupport implements
|
||||||
StateMachineFactory<S, E>, BeanNameAware {
|
StateMachineFactory<S, E>, BeanNameAware {
|
||||||
|
|
||||||
|
private final Log log = LogFactory.getLog(AbstractStateMachineFactory.class);
|
||||||
|
|
||||||
private final StateMachineTransitions<S, E> stateMachineTransitions;
|
private final StateMachineTransitions<S, E> stateMachineTransitions;
|
||||||
|
|
||||||
private final StateMachineStates<S, E> stateMachineStates;
|
private final StateMachineStates<S, E> stateMachineStates;
|
||||||
@@ -213,9 +218,24 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
|||||||
public void apply(StateMachineAccess<S, E> function) {
|
public void apply(StateMachineAccess<S, E> function) {
|
||||||
function.setRelay(fmachine);
|
function.setRelay(fmachine);
|
||||||
}
|
}
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: should error out if sec is enabled but spring-security is not in cp
|
||||||
|
if (stateMachineConfigurationConfig.isSecurityEnabled()) {
|
||||||
|
final StateMachineSecurityInterceptor<S, E> securityInterceptor = new StateMachineSecurityInterceptor<S, E>(
|
||||||
|
stateMachineConfigurationConfig.getTransitionSecurityAccessDecisionManager(),
|
||||||
|
stateMachineConfigurationConfig.getEventSecurityAccessDecisionManager(),
|
||||||
|
stateMachineConfigurationConfig.getEventSecurityRule());
|
||||||
|
log.info("Adding security interceptor " + securityInterceptor);
|
||||||
|
fmachine.getStateMachineAccessor().doWithAllRegions(new StateMachineFunction<StateMachineAccess<S, E>>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void apply(StateMachineAccess<S, E> function) {
|
||||||
|
function.addStateMachineInterceptor(securityInterceptor);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// setup distributed state machine if needed.
|
// setup distributed state machine if needed.
|
||||||
// we wrap previously build machine with a distributed
|
// we wrap previously build machine with a distributed
|
||||||
// state machine and set it to use given ensemble.
|
// state machine and set it to use given ensemble.
|
||||||
@@ -533,12 +553,14 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
|
DefaultExternalTransition<S, E> transition = new DefaultExternalTransition<S, E>(stateMap.get(source),
|
||||||
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger);
|
stateMap.get(target), transitionData.getActions(), event, transitionData.getGuard(), trigger,
|
||||||
|
transitionData.getSecurityRule());
|
||||||
transitions.add(transition);
|
transitions.add(transition);
|
||||||
|
|
||||||
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
|
} else if (transitionData.getKind() == TransitionKind.INTERNAL) {
|
||||||
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
|
DefaultInternalTransition<S, E> transition = new DefaultInternalTransition<S, E>(stateMap.get(source),
|
||||||
transitionData.getActions(), event, transitionData.getGuard(), trigger);
|
transitionData.getActions(), event, transitionData.getGuard(), trigger,
|
||||||
|
transitionData.getSecurityRule());
|
||||||
transitions.add(transition);
|
transitions.add(transition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -569,10 +591,10 @@ public abstract class AbstractStateMachineFactory<S, E> extends LifecycleObjectS
|
|||||||
}
|
}
|
||||||
|
|
||||||
TreeTraverser<Node<StateData<S, E>>> traverser = new TreeTraverser<Node<StateData<S, E>>>() {
|
TreeTraverser<Node<StateData<S, E>>> traverser = new TreeTraverser<Node<StateData<S, E>>>() {
|
||||||
@Override
|
@Override
|
||||||
public Iterable<Node<StateData<S, E>>> children(Node<StateData<S, E>> root) {
|
public Iterable<Node<StateData<S, E>>> children(Node<StateData<S, E>> root) {
|
||||||
return root.getChildren();
|
return root.getChildren();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Iterable<Node<StateData<S, E>>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot());
|
Iterable<Node<StateData<S, E>>> postOrderTraversal = traverser.postOrderTraversal(tree.getRoot());
|
||||||
|
|||||||
@@ -25,12 +25,15 @@ public class StateMachineConfigBuilder<S, E>
|
|||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
@Override
|
@Override
|
||||||
protected StateMachineConfig<S, E> performBuild() throws Exception {
|
protected StateMachineConfig<S, E> performBuild() throws Exception {
|
||||||
StateMachineTransitionBuilder<?, ?> sharedObject = getSharedObject(StateMachineTransitionBuilder.class);
|
StateMachineConfigurationBuilder<?, ?> configurationBuilder = getSharedObject(StateMachineConfigurationBuilder.class);
|
||||||
StateMachineStateBuilder<?, ?> sharedObject2 = getSharedObject(StateMachineStateBuilder.class);
|
StateMachineTransitionBuilder<?, ?> transitionBuilder = getSharedObject(StateMachineTransitionBuilder.class);
|
||||||
StateMachineConfigurationBuilder<?, ?> sharedObject3 = getSharedObject(StateMachineConfigurationBuilder.class);
|
StateMachineStateBuilder<?, ?> stateBuilder = getSharedObject(StateMachineStateBuilder.class);
|
||||||
StateMachineTransitions<S, E> transitions = (StateMachineTransitions<S, E>) sharedObject.build();
|
|
||||||
StateMachineStates<S, E> states = (StateMachineStates<S, E>) sharedObject2.build();
|
// build config first as it's shared with transition builder
|
||||||
StateMachineConfigurationConfig<S, E> config = (StateMachineConfigurationConfig<S, E>) sharedObject3.build();
|
StateMachineConfigurationConfig<S, E> config = (StateMachineConfigurationConfig<S, E>) configurationBuilder.build();
|
||||||
|
transitionBuilder.setSharedObject(StateMachineConfigurationConfig.class, config);
|
||||||
|
StateMachineTransitions<S, E> transitions = (StateMachineTransitions<S, E>) transitionBuilder.build();
|
||||||
|
StateMachineStates<S, E> states = (StateMachineStates<S, E>) stateBuilder.build();
|
||||||
StateMachineConfig<S, E> bean = new StateMachineConfig<S, E>(config, transitions, states);
|
StateMachineConfig<S, E> bean = new StateMachineConfig<S, E>(config, transitions, states);
|
||||||
return bean;
|
return bean;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,15 +21,19 @@ import java.util.List;
|
|||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.core.task.TaskExecutor;
|
import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
|
import org.springframework.statemachine.config.common.annotation.AbstractConfiguredAnnotationBuilder;
|
||||||
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
|
import org.springframework.statemachine.config.common.annotation.AnnotationBuilder;
|
||||||
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
|
import org.springframework.statemachine.config.common.annotation.ObjectPostProcessor;
|
||||||
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
||||||
import org.springframework.statemachine.config.configurers.DefaultConfigurationConfigurer;
|
import org.springframework.statemachine.config.configurers.DefaultConfigurationConfigurer;
|
||||||
import org.springframework.statemachine.config.configurers.DefaultDistributedStateMachineConfigurer;
|
import org.springframework.statemachine.config.configurers.DefaultDistributedStateMachineConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.DefaultSecurityConfigurer;
|
||||||
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||||
import org.springframework.statemachine.listener.StateMachineListener;
|
import org.springframework.statemachine.listener.StateMachineListener;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link AnnotationBuilder} for {@link StateMachineStates}.
|
* {@link AnnotationBuilder} for {@link StateMachineStates}.
|
||||||
@@ -49,6 +53,11 @@ public class StateMachineConfigurationBuilder<S, E>
|
|||||||
private boolean autoStart = false;
|
private boolean autoStart = false;
|
||||||
private StateMachineEnsemble<S, E> ensemble;
|
private StateMachineEnsemble<S, E> ensemble;
|
||||||
private final List<StateMachineListener<S, E>> listeners = new ArrayList<StateMachineListener<S, E>>();
|
private final List<StateMachineListener<S, E>> listeners = new ArrayList<StateMachineListener<S, E>>();
|
||||||
|
private boolean securityEnabled = false;
|
||||||
|
private AccessDecisionManager transitionSecurityAccessDecisionManager;
|
||||||
|
private AccessDecisionManager eventSecurityAccessDecisionManager;
|
||||||
|
private SecurityRule eventSecurityRule;
|
||||||
|
private SecurityRule transitionSecurityRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new state machine configuration builder.
|
* Instantiates a new state machine configuration builder.
|
||||||
@@ -87,9 +96,16 @@ public class StateMachineConfigurationBuilder<S, E>
|
|||||||
return apply(new DefaultDistributedStateMachineConfigurer<S, E>());
|
return apply(new DefaultDistributedStateMachineConfigurer<S, E>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> withSecurity() throws Exception {
|
||||||
|
return apply(new DefaultSecurityConfigurer<S, E>());
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected StateMachineConfigurationConfig<S, E> performBuild() throws Exception {
|
protected StateMachineConfigurationConfig<S, E> performBuild() throws Exception {
|
||||||
return new StateMachineConfigurationConfig<>(beanFactory, taskExecutor, taskScheculer, autoStart, ensemble, listeners);
|
return new StateMachineConfigurationConfig<S, E>(beanFactory, taskExecutor, taskScheculer, autoStart, ensemble, listeners,
|
||||||
|
securityEnabled, transitionSecurityAccessDecisionManager, eventSecurityAccessDecisionManager, eventSecurityRule,
|
||||||
|
transitionSecurityRule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -147,4 +163,49 @@ public class StateMachineConfigurationBuilder<S, E>
|
|||||||
this.listeners.addAll(listeners);
|
this.listeners.addAll(listeners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security enabled.
|
||||||
|
*
|
||||||
|
* @param securityEnabled the new security enabled
|
||||||
|
*/
|
||||||
|
public void setSecurityEnabled(boolean securityEnabled) {
|
||||||
|
this.securityEnabled = securityEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security transition access decision manager.
|
||||||
|
*
|
||||||
|
* @param transitionSecurityAccessDecisionManager the new security transition access decision manager
|
||||||
|
*/
|
||||||
|
public void setTransitionSecurityAccessDecisionManager(AccessDecisionManager transitionSecurityAccessDecisionManager) {
|
||||||
|
this.transitionSecurityAccessDecisionManager = transitionSecurityAccessDecisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security event access decision manager.
|
||||||
|
*
|
||||||
|
* @param eventSecurityAccessDecisionManager the new security event access decision manager
|
||||||
|
*/
|
||||||
|
public void setEventSecurityAccessDecisionManager(AccessDecisionManager eventSecurityAccessDecisionManager) {
|
||||||
|
this.eventSecurityAccessDecisionManager = eventSecurityAccessDecisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the event security rule.
|
||||||
|
*
|
||||||
|
* @param eventSecurityRule the new event security rule
|
||||||
|
*/
|
||||||
|
public void setEventSecurityRule(SecurityRule eventSecurityRule) {
|
||||||
|
this.eventSecurityRule = eventSecurityRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the transition security rule.
|
||||||
|
*
|
||||||
|
* @param transitionSecurityRule the new event security rule
|
||||||
|
*/
|
||||||
|
public void setTransitionSecurityRule(SecurityRule transitionSecurityRule) {
|
||||||
|
this.transitionSecurityRule = transitionSecurityRule;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,8 +20,10 @@ import java.util.List;
|
|||||||
import org.springframework.beans.factory.BeanFactory;
|
import org.springframework.beans.factory.BeanFactory;
|
||||||
import org.springframework.core.task.TaskExecutor;
|
import org.springframework.core.task.TaskExecutor;
|
||||||
import org.springframework.scheduling.TaskScheduler;
|
import org.springframework.scheduling.TaskScheduler;
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
import org.springframework.statemachine.ensemble.StateMachineEnsemble;
|
||||||
import org.springframework.statemachine.listener.StateMachineListener;
|
import org.springframework.statemachine.listener.StateMachineListener;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration object used to keep things together in {@link StateMachineConfigurationBuilder}.
|
* Configuration object used to keep things together in {@link StateMachineConfigurationBuilder}.
|
||||||
@@ -39,6 +41,11 @@ public class StateMachineConfigurationConfig<S, E> {
|
|||||||
private final boolean autoStart;
|
private final boolean autoStart;
|
||||||
private final StateMachineEnsemble<S, E> ensemble;
|
private final StateMachineEnsemble<S, E> ensemble;
|
||||||
private final List<StateMachineListener<S, E>> listeners;
|
private final List<StateMachineListener<S, E>> listeners;
|
||||||
|
private final boolean securityEnabled;
|
||||||
|
private final AccessDecisionManager transitionSecurityAccessDecisionManager;
|
||||||
|
private final AccessDecisionManager eventSecurityAccessDecisionManager;
|
||||||
|
private final SecurityRule eventSecurityRule;
|
||||||
|
private final SecurityRule transitionSecurityRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new state machine configuration config.
|
* Instantiates a new state machine configuration config.
|
||||||
@@ -49,16 +56,28 @@ public class StateMachineConfigurationConfig<S, E> {
|
|||||||
* @param autoStart the autostart flag
|
* @param autoStart the autostart flag
|
||||||
* @param ensemble the state machine ensemble
|
* @param ensemble the state machine ensemble
|
||||||
* @param listeners the state machine listeners
|
* @param listeners the state machine listeners
|
||||||
|
* @param securityEnabled the security enabled flag
|
||||||
|
* @param transitionSecurityAccessDecisionManager the transition security access decision manager
|
||||||
|
* @param eventSecurityAccessDecisionManager the event security access decision manager
|
||||||
|
* @param eventSecurityRule the event security rule
|
||||||
|
* @param transitionSecurityRule the transition security rule
|
||||||
*/
|
*/
|
||||||
public StateMachineConfigurationConfig(BeanFactory beanFactory, TaskExecutor taskExecutor,
|
public StateMachineConfigurationConfig(BeanFactory beanFactory, TaskExecutor taskExecutor,
|
||||||
TaskScheduler taskScheduler, boolean autoStart, StateMachineEnsemble<S, E> ensemble,
|
TaskScheduler taskScheduler, boolean autoStart, StateMachineEnsemble<S, E> ensemble,
|
||||||
List<StateMachineListener<S, E>> listeners) {
|
List<StateMachineListener<S, E>> listeners, boolean securityEnabled,
|
||||||
|
AccessDecisionManager transitionSecurityAccessDecisionManager, AccessDecisionManager eventSecurityAccessDecisionManager,
|
||||||
|
SecurityRule eventSecurityRule, SecurityRule transitionSecurityRule) {
|
||||||
this.beanFactory = beanFactory;
|
this.beanFactory = beanFactory;
|
||||||
this.taskExecutor = taskExecutor;
|
this.taskExecutor = taskExecutor;
|
||||||
this.taskScheduler = taskScheduler;
|
this.taskScheduler = taskScheduler;
|
||||||
this.autoStart = autoStart;
|
this.autoStart = autoStart;
|
||||||
this.ensemble = ensemble;
|
this.ensemble = ensemble;
|
||||||
this.listeners = listeners;
|
this.listeners = listeners;
|
||||||
|
this.securityEnabled = securityEnabled;
|
||||||
|
this.transitionSecurityAccessDecisionManager = transitionSecurityAccessDecisionManager;
|
||||||
|
this.eventSecurityAccessDecisionManager = eventSecurityAccessDecisionManager;
|
||||||
|
this.eventSecurityRule = eventSecurityRule;
|
||||||
|
this.transitionSecurityRule = transitionSecurityRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,4 +134,49 @@ public class StateMachineConfigurationConfig<S, E> {
|
|||||||
return listeners;
|
return listeners;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if is security is enabled.
|
||||||
|
*
|
||||||
|
* @return true, if is security is enabled
|
||||||
|
*/
|
||||||
|
public boolean isSecurityEnabled() {
|
||||||
|
return securityEnabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transition security access decision manager.
|
||||||
|
*
|
||||||
|
* @return the security access decision manager
|
||||||
|
*/
|
||||||
|
public AccessDecisionManager getTransitionSecurityAccessDecisionManager() {
|
||||||
|
return transitionSecurityAccessDecisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the event security access decision manager.
|
||||||
|
*
|
||||||
|
* @return the event security access decision manager
|
||||||
|
*/
|
||||||
|
public AccessDecisionManager getEventSecurityAccessDecisionManager() {
|
||||||
|
return eventSecurityAccessDecisionManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the event security rule.
|
||||||
|
*
|
||||||
|
* @return the event security rule
|
||||||
|
*/
|
||||||
|
public SecurityRule getEventSecurityRule() {
|
||||||
|
return eventSecurityRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transition security rule.
|
||||||
|
*
|
||||||
|
* @return the transition security rule
|
||||||
|
*/
|
||||||
|
public SecurityRule getTransitionSecurityRule() {
|
||||||
|
return transitionSecurityRule;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ package org.springframework.statemachine.config.builders;
|
|||||||
|
|
||||||
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
import org.springframework.statemachine.config.configurers.ConfigurationConfigurer;
|
||||||
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
import org.springframework.statemachine.config.configurers.DistributedStateMachineConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configurer interface exposing generic config.
|
* Configurer interface exposing generic config.
|
||||||
@@ -44,4 +45,12 @@ public interface StateMachineConfigurationConfigurer<S, E> {
|
|||||||
*/
|
*/
|
||||||
DistributedStateMachineConfigurer<S, E> withDistributed() throws Exception;
|
DistributedStateMachineConfigurer<S, E> withDistributed() throws Exception;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a configurer for securing state machine.
|
||||||
|
*
|
||||||
|
* @return {@link SecurityConfigurer} for chaining
|
||||||
|
* @throws Exception if configuration error happens
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> withSecurity() throws Exception;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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.config.builders;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configurer interface exposing generic config.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <S> the type of state
|
||||||
|
* @param <E> the type of event
|
||||||
|
*/
|
||||||
|
public interface StateMachineSecurityConfigurer<S, E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a configurer for security.
|
||||||
|
*
|
||||||
|
* @return {@link SecurityConfigurer} for chaining
|
||||||
|
* @throws Exception if configuration error happens
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> withSecurity() throws Exception;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,6 +40,7 @@ import org.springframework.statemachine.config.configurers.InternalTransitionCon
|
|||||||
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
|
import org.springframework.statemachine.config.configurers.JoinTransitionConfigurer;
|
||||||
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
|
import org.springframework.statemachine.config.configurers.LocalTransitionConfigurer;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.transition.TransitionKind;
|
import org.springframework.statemachine.transition.TransitionKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -109,8 +110,14 @@ public class StateMachineTransitionBuilder<S, E>
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
public void add(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||||
Guard<S, E> guard, TransitionKind kind) {
|
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
|
||||||
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind));
|
// if rule not given, get it from global
|
||||||
|
if (securityRule == null) {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
StateMachineConfigurationConfig<S, E> config = getSharedObject(StateMachineConfigurationConfig.class);
|
||||||
|
securityRule = config.getTransitionSecurityRule();
|
||||||
|
}
|
||||||
|
transitionData.add(new TransitionData<S, E>(source, target, state, event, period, actions, guard, kind, securityRule));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void add(S source, List<ChoiceData<S, E>> choices) {
|
public void add(S source, List<ChoiceData<S, E>> choices) {
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import java.util.Map;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.transition.TransitionKind;
|
import org.springframework.statemachine.transition.TransitionKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -105,6 +106,7 @@ public class StateMachineTransitions<S, E> {
|
|||||||
private final Collection<Action<S, E>> actions;
|
private final Collection<Action<S, E>> actions;
|
||||||
private final Guard<S, E> guard;
|
private final Guard<S, E> guard;
|
||||||
private final TransitionKind kind;
|
private final TransitionKind kind;
|
||||||
|
private final SecurityRule securityRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new transition data.
|
* Instantiates a new transition data.
|
||||||
@@ -117,9 +119,10 @@ public class StateMachineTransitions<S, E> {
|
|||||||
* @param actions the actions
|
* @param actions the actions
|
||||||
* @param guard the guard
|
* @param guard the guard
|
||||||
* @param kind the kind
|
* @param kind the kind
|
||||||
|
* @param securityRule the security rule
|
||||||
*/
|
*/
|
||||||
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
public TransitionData(S source, S target, S state, E event, Long period, Collection<Action<S, E>> actions,
|
||||||
Guard<S, E> guard, TransitionKind kind) {
|
Guard<S, E> guard, TransitionKind kind, SecurityRule securityRule) {
|
||||||
this.source = source;
|
this.source = source;
|
||||||
this.target = target;
|
this.target = target;
|
||||||
this.state = state;
|
this.state = state;
|
||||||
@@ -128,6 +131,7 @@ public class StateMachineTransitions<S, E> {
|
|||||||
this.actions = actions;
|
this.actions = actions;
|
||||||
this.guard = guard;
|
this.guard = guard;
|
||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
|
this.securityRule = securityRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,6 +205,15 @@ public class StateMachineTransitions<S, E> {
|
|||||||
public TransitionKind getKind() {
|
public TransitionKind getKind() {
|
||||||
return kind;
|
return kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the security rule.
|
||||||
|
*
|
||||||
|
* @return the security rule
|
||||||
|
*/
|
||||||
|
public SecurityRule getSecurityRule() {
|
||||||
|
return securityRule;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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.config.configurers;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
||||||
|
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for transition configurers.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <S> the type of state
|
||||||
|
* @param <E> the type of event
|
||||||
|
*/
|
||||||
|
public abstract class AbstractTransitionConfigurer<S, E> extends
|
||||||
|
AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>> {
|
||||||
|
|
||||||
|
private S source;
|
||||||
|
private S target;
|
||||||
|
private S state;
|
||||||
|
private E event;
|
||||||
|
private Long period;
|
||||||
|
private final Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
||||||
|
private Guard<S, E> guard;
|
||||||
|
private SecurityRule securityRule;
|
||||||
|
|
||||||
|
protected S getSource() {
|
||||||
|
return source;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected S getTarget() {
|
||||||
|
return target;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected S getState() {
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected E getEvent() {
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Long getPeriod() {
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Collection<Action<S, E>> getActions() {
|
||||||
|
return actions;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Guard<S, E> getGuard() {
|
||||||
|
return guard;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected SecurityRule getSecurityRule() {
|
||||||
|
return securityRule;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setSource(S source) {
|
||||||
|
this.source = source;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setTarget(S target) {
|
||||||
|
this.target = target;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setState(S state) {
|
||||||
|
this.state = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setEvent(E event) {
|
||||||
|
this.event = event;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setPeriod(long period) {
|
||||||
|
this.period = period;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addAction(Action<S, E> action) {
|
||||||
|
this.actions.add(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setGuard(Guard<S, E> guard) {
|
||||||
|
this.guard = guard;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setSecurityRule(String attributes, ComparisonType match) {
|
||||||
|
if (securityRule == null) {
|
||||||
|
securityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
securityRule.setAttributes(SecurityRule.commaDelimitedListToSecurityAttributes(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void setSecurityRule(String expression) {
|
||||||
|
if (securityRule == null) {
|
||||||
|
securityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
securityRule.setExpression(expression);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -15,19 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.statemachine.config.configurers;
|
package org.springframework.statemachine.config.configurers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.springframework.expression.spel.SpelCompilerMode;
|
import org.springframework.expression.spel.SpelCompilerMode;
|
||||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
|
||||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
import org.springframework.statemachine.transition.TransitionKind;
|
import org.springframework.statemachine.transition.TransitionKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,68 +33,54 @@ import org.springframework.statemachine.transition.TransitionKind;
|
|||||||
* @param <S> the type of state
|
* @param <S> the type of state
|
||||||
* @param <E> the type of event
|
* @param <E> the type of event
|
||||||
*/
|
*/
|
||||||
public class DefaultExternalTransitionConfigurer<S, E>
|
public class DefaultExternalTransitionConfigurer<S, E> extends AbstractTransitionConfigurer<S, E>
|
||||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
|
||||||
implements ExternalTransitionConfigurer<S, E> {
|
implements ExternalTransitionConfigurer<S, E> {
|
||||||
|
|
||||||
private S source;
|
|
||||||
|
|
||||||
private S target;
|
|
||||||
|
|
||||||
private S state;
|
|
||||||
|
|
||||||
private E event;
|
|
||||||
|
|
||||||
private Long period;
|
|
||||||
|
|
||||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
|
||||||
|
|
||||||
private Guard<S, E> guard;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.EXTERNAL);
|
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.EXTERNAL,
|
||||||
|
getSecurityRule());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> source(S source) {
|
public ExternalTransitionConfigurer<S, E> source(S source) {
|
||||||
this.source = source;
|
setSource(source);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> target(S target) {
|
public ExternalTransitionConfigurer<S, E> target(S target) {
|
||||||
this.target = target;
|
setTarget(target);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> state(S state) {
|
public ExternalTransitionConfigurer<S, E> state(S state) {
|
||||||
this.state = state;
|
setState(state);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> event(E event) {
|
public ExternalTransitionConfigurer<S, E> event(E event) {
|
||||||
this.event = event;
|
setEvent(event);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> timer(long period) {
|
public ExternalTransitionConfigurer<S, E> timer(long period) {
|
||||||
this.period = period;
|
setPeriod(period);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
public ExternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||||
actions.add(action);
|
addAction(action);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ExternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
public ExternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||||
this.guard = guard;
|
setGuard(guard);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +88,19 @@ public class DefaultExternalTransitionConfigurer<S, E>
|
|||||||
public ExternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
public ExternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||||
SpelExpressionParser parser = new SpelExpressionParser(
|
SpelExpressionParser parser = new SpelExpressionParser(
|
||||||
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
||||||
this.guard = new SpelExpressionGuard<S, E>(parser.parseExpression(expression));
|
setGuard(new SpelExpressionGuard<S, E>(parser.parseExpression(expression)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalTransitionConfigurer<S, E> secured(String attributes, ComparisonType match) {
|
||||||
|
setSecurityRule(attributes, match);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ExternalTransitionConfigurer<S, E> secured(String expression) {
|
||||||
|
setSecurityRule(expression);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,19 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.statemachine.config.configurers;
|
package org.springframework.statemachine.config.configurers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.springframework.expression.spel.SpelCompilerMode;
|
import org.springframework.expression.spel.SpelCompilerMode;
|
||||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
|
||||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
import org.springframework.statemachine.transition.TransitionKind;
|
import org.springframework.statemachine.transition.TransitionKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,62 +33,48 @@ import org.springframework.statemachine.transition.TransitionKind;
|
|||||||
* @param <S> the type of state
|
* @param <S> the type of state
|
||||||
* @param <E> the type of event
|
* @param <E> the type of event
|
||||||
*/
|
*/
|
||||||
public class DefaultInternalTransitionConfigurer<S, E>
|
public class DefaultInternalTransitionConfigurer<S, E> extends AbstractTransitionConfigurer<S, E>
|
||||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
|
||||||
implements InternalTransitionConfigurer<S, E> {
|
implements InternalTransitionConfigurer<S, E> {
|
||||||
|
|
||||||
private S source;
|
|
||||||
|
|
||||||
private S target;
|
|
||||||
|
|
||||||
private S state;
|
|
||||||
|
|
||||||
private E event;
|
|
||||||
|
|
||||||
private Long period;
|
|
||||||
|
|
||||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
|
||||||
|
|
||||||
private Guard<S, E> guard;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.INTERNAL);
|
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.INTERNAL,
|
||||||
|
getSecurityRule());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> source(S source) {
|
public InternalTransitionConfigurer<S, E> source(S source) {
|
||||||
this.source = source;
|
setSource(source);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> state(S state) {
|
public InternalTransitionConfigurer<S, E> state(S state) {
|
||||||
this.state = state;
|
setState(state);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> event(E event) {
|
public InternalTransitionConfigurer<S, E> event(E event) {
|
||||||
this.event = event;
|
setEvent(event);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> timer(long period) {
|
public InternalTransitionConfigurer<S, E> timer(long period) {
|
||||||
this.period = period;
|
setPeriod(period);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
public InternalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||||
actions.add(action);
|
addAction(action);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public InternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
public InternalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||||
this.guard = guard;
|
setGuard(guard);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,7 +82,19 @@ public class DefaultInternalTransitionConfigurer<S, E>
|
|||||||
public InternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
public InternalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||||
SpelExpressionParser parser = new SpelExpressionParser(
|
SpelExpressionParser parser = new SpelExpressionParser(
|
||||||
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
||||||
this.guard = new SpelExpressionGuard<S, E>(parser.parseExpression(expression));
|
setGuard(new SpelExpressionGuard<S, E>(parser.parseExpression(expression)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InternalTransitionConfigurer<S, E> secured(String attributes, ComparisonType match) {
|
||||||
|
setSecurityRule(attributes, match);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public InternalTransitionConfigurer<S, E> secured(String expression) {
|
||||||
|
setSecurityRule(expression);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -15,19 +15,14 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.statemachine.config.configurers;
|
package org.springframework.statemachine.config.configurers;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
|
||||||
|
|
||||||
import org.springframework.expression.spel.SpelCompilerMode;
|
import org.springframework.expression.spel.SpelCompilerMode;
|
||||||
import org.springframework.expression.spel.SpelParserConfiguration;
|
import org.springframework.expression.spel.SpelParserConfiguration;
|
||||||
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
import org.springframework.statemachine.config.builders.StateMachineTransitionBuilder;
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
|
||||||
import org.springframework.statemachine.config.builders.StateMachineTransitions;
|
|
||||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
import org.springframework.statemachine.guard.SpelExpressionGuard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
import org.springframework.statemachine.transition.TransitionKind;
|
import org.springframework.statemachine.transition.TransitionKind;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,68 +33,53 @@ import org.springframework.statemachine.transition.TransitionKind;
|
|||||||
* @param <S> the type of state
|
* @param <S> the type of state
|
||||||
* @param <E> the type of event
|
* @param <E> the type of event
|
||||||
*/
|
*/
|
||||||
public class DefaultLocalTransitionConfigurer<S, E>
|
public class DefaultLocalTransitionConfigurer<S, E> extends AbstractTransitionConfigurer<S, E> implements LocalTransitionConfigurer<S, E> {
|
||||||
extends AnnotationConfigurerAdapter<StateMachineTransitions<S, E>, StateMachineTransitionConfigurer<S, E>, StateMachineTransitionBuilder<S, E>>
|
|
||||||
implements LocalTransitionConfigurer<S, E> {
|
|
||||||
|
|
||||||
private S source;
|
|
||||||
|
|
||||||
private S target;
|
|
||||||
|
|
||||||
private S state;
|
|
||||||
|
|
||||||
private E event;
|
|
||||||
|
|
||||||
private Long period;
|
|
||||||
|
|
||||||
private Collection<Action<S, E>> actions = new ArrayList<Action<S, E>>();
|
|
||||||
|
|
||||||
private Guard<S, E> guard;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
public void configure(StateMachineTransitionBuilder<S, E> builder) throws Exception {
|
||||||
builder.add(source, target, state, event, period, actions, guard, TransitionKind.LOCAL);
|
builder.add(getSource(), getTarget(), getState(), getEvent(), getPeriod(), getActions(), getGuard(), TransitionKind.LOCAL,
|
||||||
|
getSecurityRule());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> source(S source) {
|
public LocalTransitionConfigurer<S, E> source(S source) {
|
||||||
this.source = source;
|
setSource(source);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> target(S target) {
|
public LocalTransitionConfigurer<S, E> target(S target) {
|
||||||
this.target = target;
|
setTarget(target);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> state(S state) {
|
public LocalTransitionConfigurer<S, E> state(S state) {
|
||||||
this.state = state;
|
setState(state);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> event(E event) {
|
public LocalTransitionConfigurer<S, E> event(E event) {
|
||||||
this.event = event;
|
setEvent(event);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> timer(long period) {
|
public LocalTransitionConfigurer<S, E> timer(long period) {
|
||||||
this.period = period;
|
setPeriod(period);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
public LocalTransitionConfigurer<S, E> action(Action<S, E> action) {
|
||||||
actions.add(action);
|
addAction(action);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LocalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
public LocalTransitionConfigurer<S, E> guard(Guard<S, E> guard) {
|
||||||
this.guard = guard;
|
setGuard(guard);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +87,19 @@ public class DefaultLocalTransitionConfigurer<S, E>
|
|||||||
public LocalTransitionConfigurer<S, E> guardExpression(String expression) {
|
public LocalTransitionConfigurer<S, E> guardExpression(String expression) {
|
||||||
SpelExpressionParser parser = new SpelExpressionParser(
|
SpelExpressionParser parser = new SpelExpressionParser(
|
||||||
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
new SpelParserConfiguration(SpelCompilerMode.MIXED, null));
|
||||||
this.guard = new SpelExpressionGuard<S, E>(parser.parseExpression(expression));
|
setGuard(new SpelExpressionGuard<S, E>(parser.parseExpression(expression)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalTransitionConfigurer<S, E> secured(String attributes, ComparisonType match) {
|
||||||
|
setSecurityRule(attributes, match);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LocalTransitionConfigurer<S, E> secured(String expression) {
|
||||||
|
setSecurityRule(expression);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* 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.config.configurers;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationBuilder;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfig;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Default implementation of a {@link SecurityConfigurer}.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <S> the type of state
|
||||||
|
* @param <E> the type of event
|
||||||
|
*/
|
||||||
|
public class DefaultSecurityConfigurer<S, E>
|
||||||
|
extends AnnotationConfigurerAdapter<StateMachineConfigurationConfig<S, E>, StateMachineConfigurationConfigurer<S, E>, StateMachineConfigurationBuilder<S, E>>
|
||||||
|
implements SecurityConfigurer<S, E> {
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
private AccessDecisionManager transitionAccessDecisionManager;
|
||||||
|
private AccessDecisionManager eventAccessDecisionManager;
|
||||||
|
private SecurityRule eventSecurityRule;
|
||||||
|
private SecurityRule transitionSecurityRule;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationBuilder<S, E> builder) throws Exception {
|
||||||
|
if (enabled) {
|
||||||
|
builder.setSecurityEnabled(true);
|
||||||
|
builder.setTransitionSecurityAccessDecisionManager(transitionAccessDecisionManager);
|
||||||
|
builder.setEventSecurityAccessDecisionManager(eventAccessDecisionManager);
|
||||||
|
builder.setEventSecurityRule(eventSecurityRule);
|
||||||
|
builder.setTransitionSecurityRule(transitionSecurityRule);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> enabled(boolean enabled) {
|
||||||
|
this.enabled = enabled;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> transitionAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
|
||||||
|
this.transitionAccessDecisionManager = accessDecisionManager;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> eventAccessDecisionManager(AccessDecisionManager accessDecisionManager) {
|
||||||
|
this.eventAccessDecisionManager = accessDecisionManager;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> event(String attributes, ComparisonType match) {
|
||||||
|
if (eventSecurityRule == null) {
|
||||||
|
eventSecurityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
eventSecurityRule.setAttributes(SecurityRule.commaDelimitedListToSecurityAttributes(attributes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> event(String expression) {
|
||||||
|
if (eventSecurityRule == null) {
|
||||||
|
eventSecurityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
eventSecurityRule.setExpression(expression);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> transition(String attributes, ComparisonType match) {
|
||||||
|
if (transitionSecurityRule == null) {
|
||||||
|
transitionSecurityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
transitionSecurityRule.setAttributes(SecurityRule.commaDelimitedListToSecurityAttributes(attributes));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityConfigurer<S, E> transition(String expression) {
|
||||||
|
if (transitionSecurityRule == null) {
|
||||||
|
transitionSecurityRule = new SecurityRule();
|
||||||
|
}
|
||||||
|
transitionSecurityRule.setExpression(expression);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* 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.config.configurers;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base {@code ConfigConfigurer} interface for configuring generic config.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <S> the type of state
|
||||||
|
* @param <E> the type of event
|
||||||
|
*/
|
||||||
|
public interface SecurityConfigurer<S, E> extends
|
||||||
|
AnnotationConfigurerBuilder<StateMachineConfigurationConfigurer<S, E>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify if security is enabled. On default security is enabled
|
||||||
|
* if configurer is used.
|
||||||
|
*
|
||||||
|
* @param enabled the enable flag
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> enabled(boolean enabled);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a custom {@link AccessDecisionManager} for transitions.
|
||||||
|
*
|
||||||
|
* @param accessDecisionManager the access decision manager
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> transitionAccessDecisionManager(AccessDecisionManager accessDecisionManager);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a custom {@link AccessDecisionManager} for events.
|
||||||
|
*
|
||||||
|
* @param accessDecisionManager the access decision manager
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> eventAccessDecisionManager(AccessDecisionManager accessDecisionManager);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security attributes for events.
|
||||||
|
*
|
||||||
|
* @param attributes the security attributes
|
||||||
|
* @param match the match type
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> event(String attributes, ComparisonType match);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security attributes for events.
|
||||||
|
*
|
||||||
|
* @param expression the the security expression
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> event(String expression);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security attributes for transitions.
|
||||||
|
*
|
||||||
|
* @param attributes the security attributes
|
||||||
|
* @param match the match type
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> transition(String attributes, ComparisonType match);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security attributes for transitions.
|
||||||
|
*
|
||||||
|
* @param expression the the security expression
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
SecurityConfigurer<S, E> transition(String expression);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import org.springframework.statemachine.action.Action;
|
|||||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurerBuilder;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
import org.springframework.statemachine.transition.Transition;
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -90,4 +91,22 @@ public interface TransitionConfigurer<T, S, E> extends
|
|||||||
*/
|
*/
|
||||||
T guardExpression(String expression);
|
T guardExpression(String expression);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security attributes for this {@link Transition}.
|
||||||
|
*
|
||||||
|
* @param attributes the security attributes
|
||||||
|
* @param match the match type
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
T secured(String attributes, ComparisonType match);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specify a security expression for this {@link Transition}.
|
||||||
|
*
|
||||||
|
* @param expression the security expression
|
||||||
|
* @return configurer for chaining
|
||||||
|
*/
|
||||||
|
T secured(String expression);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -185,6 +185,11 @@ public class DistributedStateMachine<S, E> extends LifecycleObjectSupport implem
|
|||||||
*/
|
*/
|
||||||
private class LocalStateMachineInterceptor implements StateMachineInterceptor<S, E> {
|
private class LocalStateMachineInterceptor implements StateMachineInterceptor<S, E> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||||
StateMachine<S, E> stateMachine) {
|
StateMachine<S, E> stateMachine) {
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionOperations;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default implementation of {@link SecurityExpressionHandler} which uses a
|
||||||
|
* {@link EventSecurityExpressionRoot}.
|
||||||
|
*
|
||||||
|
* @param <T> the type for the body of the Message
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
public class DefaultEventSecurityExpressionHandler<T> extends
|
||||||
|
AbstractSecurityExpressionHandler<Message<T>> {
|
||||||
|
|
||||||
|
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SecurityExpressionOperations createSecurityExpressionRoot(
|
||||||
|
Authentication authentication, Message<T> invocation) {
|
||||||
|
EventSecurityExpressionRoot root = new EventSecurityExpressionRoot(
|
||||||
|
authentication, invocation);
|
||||||
|
root.setPermissionEvaluator(getPermissionEvaluator());
|
||||||
|
root.setTrustResolver(trustResolver);
|
||||||
|
root.setRoleHierarchy(getRoleHierarchy());
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
|
||||||
|
Assert.notNull(trustResolver, "trustResolver cannot be null");
|
||||||
|
this.trustResolver = trustResolver;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.security.access.expression.AbstractSecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionOperations;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
public class DefaultTransitionSecurityExpressionHandler extends AbstractSecurityExpressionHandler<Transition<?, ?>>
|
||||||
|
implements SecurityExpressionHandler<Transition<?, ?>> {
|
||||||
|
|
||||||
|
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||||
|
private String defaultRolePrefix = "ROLE_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected SecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication, Transition<?, ?> transition) {
|
||||||
|
TransitionSecurityExpressionRoot root = new TransitionSecurityExpressionRoot(authentication, transition);
|
||||||
|
root.setPermissionEvaluator(getPermissionEvaluator());
|
||||||
|
root.setTrustResolver(trustResolver);
|
||||||
|
root.setRoleHierarchy(getRoleHierarchy());
|
||||||
|
root.setDefaultRolePrefix(defaultRolePrefix);
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link AuthenticationTrustResolver} to be used. The default is
|
||||||
|
* {@link AuthenticationTrustResolverImpl}.
|
||||||
|
*
|
||||||
|
* @param trustResolver
|
||||||
|
* the {@link AuthenticationTrustResolver} to use. Cannot be
|
||||||
|
* null.
|
||||||
|
*/
|
||||||
|
public void setTrustResolver(AuthenticationTrustResolver trustResolver) {
|
||||||
|
Assert.notNull(trustResolver, "trustResolver cannot be null");
|
||||||
|
this.trustResolver = trustResolver;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>
|
||||||
|
* Sets the default prefix to be added to
|
||||||
|
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasAnyRole(String...)}
|
||||||
|
* or
|
||||||
|
* {@link org.springframework.security.access.expression.SecurityExpressionRoot#hasRole(String)}
|
||||||
|
* . For example, if hasRole("ADMIN") or hasRole("ROLE_ADMIN") is passed in,
|
||||||
|
* then the role ROLE_ADMIN will be used when the defaultRolePrefix is
|
||||||
|
* "ROLE_" (default).
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* <p>
|
||||||
|
* If null or empty, then no default role prefix is used.
|
||||||
|
* </p>
|
||||||
|
*
|
||||||
|
* @param defaultRolePrefix
|
||||||
|
* the default prefix to add to roles. Default "ROLE_".
|
||||||
|
*/
|
||||||
|
public void setDefaultRolePrefix(String defaultRolePrefix) {
|
||||||
|
this.defaultRolePrefix = defaultRolePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.expression.Expression;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple expression configuration attribute for use in {@link Message} authorizations.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
class EventExpressionConfigAttribute implements ConfigAttribute {
|
||||||
|
private final Expression authorizeExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance
|
||||||
|
*
|
||||||
|
* @param authorizeExpression the {@link Expression} to use. Cannot be null
|
||||||
|
*/
|
||||||
|
public EventExpressionConfigAttribute(Expression authorizeExpression) {
|
||||||
|
Assert.notNull(authorizeExpression, "authorizeExpression cannot be null");
|
||||||
|
|
||||||
|
this.authorizeExpression = authorizeExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
Expression getAuthorizeExpression() {
|
||||||
|
return authorizeExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getAttribute() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return authorizeExpression.getExpressionString();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.access.expression.ExpressionUtils;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Voter which handles {@link Message} authorisation decisions. If a
|
||||||
|
* {@link EventExpressionConfigAttribute} is found, then its expression is evaluated. If
|
||||||
|
* true, {@code ACCESS_GRANTED} is returned. If false, {@code ACCESS_DENIED} is returned.
|
||||||
|
* If no {@code MessageExpressionConfigAttribute} is found, then {@code ACCESS_ABSTAIN} is
|
||||||
|
* returned.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
public class EventExpressionVoter<T> implements AccessDecisionVoter<Message<T>> {
|
||||||
|
private SecurityExpressionHandler<Message<T>> expressionHandler = new DefaultEventSecurityExpressionHandler<T>();
|
||||||
|
|
||||||
|
public int vote(Authentication authentication, Message<T> message,
|
||||||
|
Collection<ConfigAttribute> attributes) {
|
||||||
|
assert authentication != null;
|
||||||
|
assert message != null;
|
||||||
|
assert attributes != null;
|
||||||
|
|
||||||
|
EventExpressionConfigAttribute attr = findConfigAttribute(attributes);
|
||||||
|
|
||||||
|
if (attr == null) {
|
||||||
|
return ACCESS_ABSTAIN;
|
||||||
|
}
|
||||||
|
|
||||||
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
|
||||||
|
message);
|
||||||
|
|
||||||
|
return ExpressionUtils.evaluateAsBoolean(attr.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED
|
||||||
|
: ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventExpressionConfigAttribute findConfigAttribute(
|
||||||
|
Collection<ConfigAttribute> attributes) {
|
||||||
|
for (ConfigAttribute attribute : attributes) {
|
||||||
|
if (attribute instanceof EventExpressionConfigAttribute) {
|
||||||
|
return (EventExpressionConfigAttribute) attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
return attribute instanceof EventExpressionConfigAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
boolean foo = Message.class.isAssignableFrom(clazz);
|
||||||
|
return foo;
|
||||||
|
// return Message.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setExpressionHandler(
|
||||||
|
SecurityExpressionHandler<Message<T>> expressionHandler) {
|
||||||
|
Assert.notNull(expressionHandler, "expressionHandler cannot be null");
|
||||||
|
this.expressionHandler = expressionHandler;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionRoot;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link SecurityExpressionRoot} used for {@link Message} expressions.
|
||||||
|
*
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
public class EventSecurityExpressionRoot extends SecurityExpressionRoot {
|
||||||
|
|
||||||
|
public final Message<?> message;
|
||||||
|
|
||||||
|
public EventSecurityExpressionRoot(Authentication authentication, Message<?> message) {
|
||||||
|
super(authentication);
|
||||||
|
this.message = message;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/*
|
||||||
|
* 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.Collection;
|
||||||
|
|
||||||
|
import org.springframework.messaging.Message;
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating
|
||||||
|
* that it is an event. The default prefix is <Code>EVENT</code>, but
|
||||||
|
* it may be overridden to any value. It may also be set to empty, which means that
|
||||||
|
* essentially any attribute will be voted on. As described further below, the effect
|
||||||
|
* of an empty prefix may not be quite desirable.
|
||||||
|
* <p>
|
||||||
|
* All comparisons and prefixes are case sensitive.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <T> the message type
|
||||||
|
*/
|
||||||
|
public class EventVoter<T> implements AccessDecisionVoter<Message<T>>{
|
||||||
|
|
||||||
|
private String eventPrefix = "EVENT_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
if ((attribute.getAttribute() != null) && attribute.getAttribute().startsWith(getEventPrefix())) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return Message.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int vote(Authentication authentication, Message<T> event, Collection<ConfigAttribute> attributes) {
|
||||||
|
int result = ACCESS_ABSTAIN;
|
||||||
|
if (authentication == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
T e = event.getPayload();
|
||||||
|
|
||||||
|
for (ConfigAttribute attribute : attributes) {
|
||||||
|
if (this.supports(attribute)) {
|
||||||
|
result = ACCESS_DENIED;
|
||||||
|
String attr = attribute.getAttribute();
|
||||||
|
if (attr.startsWith(getEventPrefix()) && attr.equals(getEventPrefix() + e.toString())) {
|
||||||
|
return ACCESS_GRANTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the event prefix.
|
||||||
|
*
|
||||||
|
* @return the event prefix
|
||||||
|
*/
|
||||||
|
public String getEventPrefix() {
|
||||||
|
return eventPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* 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.Collection;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulates the rules for comparing security attributes and expression.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
public class SecurityRule {
|
||||||
|
|
||||||
|
private Collection<String> attributes;
|
||||||
|
private ComparisonType comparisonType = ComparisonType.ANY;
|
||||||
|
private String expression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert attributes to comma separated String
|
||||||
|
*
|
||||||
|
* @param attributes the attributes to convert
|
||||||
|
* @return comma separated String
|
||||||
|
*/
|
||||||
|
public static String securityAttributesToCommaDelimitedList(Collection<?> attributes) {
|
||||||
|
return StringUtils.collectionToDelimitedString(attributes, ", ");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert attributes from comma separated String to Collection
|
||||||
|
*
|
||||||
|
* @param attributes the attributes to convert
|
||||||
|
* @return comma parsed Collection
|
||||||
|
*/
|
||||||
|
public static Collection<String> commaDelimitedListToSecurityAttributes(String attributes) {
|
||||||
|
Collection<String> attrs = new HashSet<String>();
|
||||||
|
for (String attribute : attributes.split(",")) {
|
||||||
|
attribute = attribute.trim();
|
||||||
|
if (!"".equals(attribute)) {
|
||||||
|
attrs.add(attribute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the security attributes.
|
||||||
|
*
|
||||||
|
* @return the security attributes
|
||||||
|
*/
|
||||||
|
public Collection<String> getAttributes() {
|
||||||
|
return attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security attributes.
|
||||||
|
*
|
||||||
|
* @param attributes the new security attributes
|
||||||
|
*/
|
||||||
|
public void setAttributes(Collection<String> attributes) {
|
||||||
|
this.attributes = attributes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the comparison type.
|
||||||
|
*
|
||||||
|
* @return the comparison type
|
||||||
|
*/
|
||||||
|
public ComparisonType getComparisonType() {
|
||||||
|
return comparisonType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the comparison type.
|
||||||
|
*
|
||||||
|
* @param comparisonType the new comparison type
|
||||||
|
*/
|
||||||
|
public void setComparisonType(ComparisonType comparisonType) {
|
||||||
|
this.comparisonType = comparisonType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the security expression.
|
||||||
|
*
|
||||||
|
* @return the security expression
|
||||||
|
*/
|
||||||
|
public String getExpression() {
|
||||||
|
return expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the security expression.
|
||||||
|
*
|
||||||
|
* @param expression the new security expression
|
||||||
|
*/
|
||||||
|
public void setExpression(String expression) {
|
||||||
|
this.expression = expression;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Security comparison types.
|
||||||
|
*/
|
||||||
|
public static enum ComparisonType {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare method where any attribute authorization allows access
|
||||||
|
*/
|
||||||
|
ANY,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare method where all attribute authorization allows access
|
||||||
|
*/
|
||||||
|
ALL,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare method where majority attribute authorization allows access
|
||||||
|
*/
|
||||||
|
MAJORITY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "SecurityRule [attributes=" + attributes + ", comparisonType=" + comparisonType + ", expression=" + expression + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* 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 + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.expression.Expression;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specific {@link ConfigAttribute} for spel expression.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("serial")
|
||||||
|
public class TransitionExpressionConfigAttribute implements ConfigAttribute {
|
||||||
|
private final Expression authorizeExpression;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new transition expression config attribute.
|
||||||
|
*
|
||||||
|
* @param authorizeExpression the authorize expression
|
||||||
|
*/
|
||||||
|
public TransitionExpressionConfigAttribute(Expression authorizeExpression) {
|
||||||
|
Assert.notNull(authorizeExpression, "Expression must be set");
|
||||||
|
this.authorizeExpression = authorizeExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAttribute() {
|
||||||
|
// always null
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the authorize expression.
|
||||||
|
*
|
||||||
|
* @return the authorize expression
|
||||||
|
*/
|
||||||
|
Expression getAuthorizeExpression() {
|
||||||
|
return authorizeExpression;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return authorizeExpression.getExpressionString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
/*
|
||||||
|
* 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.Collection;
|
||||||
|
|
||||||
|
import org.springframework.expression.EvaluationContext;
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.access.expression.ExpressionUtils;
|
||||||
|
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link AccessDecisionVoter} evaluating access via security spel expression.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TransitionExpressionVoter implements AccessDecisionVoter<Transition<?, ?>> {
|
||||||
|
|
||||||
|
private final SecurityExpressionHandler<Transition<?, ?>> expressionHandler = new DefaultTransitionSecurityExpressionHandler();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
return attribute instanceof TransitionExpressionConfigAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return Transition.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int vote(Authentication authentication, Transition<?, ?> object, Collection<ConfigAttribute> attributes) {
|
||||||
|
TransitionExpressionConfigAttribute teca = findConfigAttribute(attributes);
|
||||||
|
if (teca == null) {
|
||||||
|
return ACCESS_ABSTAIN;
|
||||||
|
}
|
||||||
|
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication, object);
|
||||||
|
return ExpressionUtils.evaluateAsBoolean(teca.getAuthorizeExpression(), ctx) ? ACCESS_GRANTED : ACCESS_DENIED;
|
||||||
|
}
|
||||||
|
|
||||||
|
private TransitionExpressionConfigAttribute findConfigAttribute(Collection<ConfigAttribute> attributes) {
|
||||||
|
for (ConfigAttribute attribute : attributes) {
|
||||||
|
if (attribute instanceof TransitionExpressionConfigAttribute) {
|
||||||
|
return (TransitionExpressionConfigAttribute) attribute;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.springframework.security.access.expression.SecurityExpressionRoot;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
import org.springframework.util.ObjectUtils;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root object for security spel evaluation.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class TransitionSecurityExpressionRoot extends SecurityExpressionRoot {
|
||||||
|
|
||||||
|
public final Transition<?, ?> transition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new transition security expression root.
|
||||||
|
*
|
||||||
|
* @param authentication the authentication
|
||||||
|
* @param transition the transition
|
||||||
|
*/
|
||||||
|
public TransitionSecurityExpressionRoot(Authentication authentication, Transition<?, ?> transition) {
|
||||||
|
super(authentication);
|
||||||
|
this.transition = transition;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean hasSource(Object source) {
|
||||||
|
return ObjectUtils.nullSafeEquals(source, transition.getSource().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public final boolean hasTarget(Object source) {
|
||||||
|
return ObjectUtils.nullSafeEquals(source, transition.getTarget().getId());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
/*
|
||||||
|
* 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.Collection;
|
||||||
|
|
||||||
|
import org.springframework.security.access.AccessDecisionVoter;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Votes if any {@link ConfigAttribute#getAttribute()} starts with a prefix indicating
|
||||||
|
* that it is a transition source or target. The default prefixes strings are
|
||||||
|
* <Code>TRANSITION_SOURCE</code> and <Code>TRANSITION_TARGET</code>, but
|
||||||
|
* those may be overridden to any value. It may also be set to empty, which means that
|
||||||
|
* essentially any attribute will be voted on. As described further below, the effect
|
||||||
|
* of an empty prefix may not be quite desirable.
|
||||||
|
* <p>
|
||||||
|
* All comparisons and prefixes are case sensitive.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
* @param <S> the type of state
|
||||||
|
* @param <E> the type of event
|
||||||
|
*/
|
||||||
|
public class TransitionVoter<S, E> implements AccessDecisionVoter<Transition<S, E>> {
|
||||||
|
|
||||||
|
private String transitionSourcePrefix = "TRANSITION_SOURCE_";
|
||||||
|
private String transitionTargetPrefix = "TRANSITION_TARGET_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
if ((attribute.getAttribute() != null) && (attribute.getAttribute().startsWith(getTransitionSourcePrefix())
|
||||||
|
|| attribute.getAttribute().startsWith(getTransitionTargetPrefix()))) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return Transition.class.isAssignableFrom(clazz);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int vote(Authentication authentication, Transition<S, E> transition, Collection<ConfigAttribute> attributes) {
|
||||||
|
int result = ACCESS_ABSTAIN;
|
||||||
|
if (authentication == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
S source = transition.getSource().getId();
|
||||||
|
S target = transition.getTarget().getId();
|
||||||
|
|
||||||
|
for (ConfigAttribute attribute : attributes) {
|
||||||
|
if (this.supports(attribute)) {
|
||||||
|
result = ACCESS_DENIED;
|
||||||
|
String attr = attribute.getAttribute();
|
||||||
|
if (attr.startsWith(getTransitionSourcePrefix())
|
||||||
|
&& attr.equals(getTransitionSourcePrefix() + source.toString())) {
|
||||||
|
return ACCESS_GRANTED;
|
||||||
|
} else if (attr.startsWith(getTransitionTargetPrefix())
|
||||||
|
&& attr.equals(getTransitionTargetPrefix() + target.toString())) {
|
||||||
|
return ACCESS_GRANTED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transition source prefix.
|
||||||
|
*
|
||||||
|
* @return the transition source prefix
|
||||||
|
*/
|
||||||
|
public String getTransitionSourcePrefix() {
|
||||||
|
return transitionSourcePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the default transition source prefix of <code>TRANSITION_SOURCE_</code> to
|
||||||
|
* be overridden. May be set to an empty value, although this is usually not desirable.
|
||||||
|
*
|
||||||
|
* @param transitionSourcePrefix the new transition source prefix
|
||||||
|
*/
|
||||||
|
public void setTransitionSourcePrefix(String transitionSourcePrefix) {
|
||||||
|
this.transitionSourcePrefix = transitionSourcePrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the transition target prefix.
|
||||||
|
*
|
||||||
|
* @return the transition target prefix
|
||||||
|
*/
|
||||||
|
public String getTransitionTargetPrefix() {
|
||||||
|
return transitionTargetPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows the default transition target prefix of <code>TRANSITION_TARGET_</code> to
|
||||||
|
* be overridden. May be set to an empty value, although this is usually not desirable.
|
||||||
|
*
|
||||||
|
* @param transitionTargetPrefix the new transition source prefix
|
||||||
|
*/
|
||||||
|
public void setTransitionTargetPrefix(String transitionTargetPrefix) {
|
||||||
|
this.transitionTargetPrefix = transitionTargetPrefix;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -17,6 +17,8 @@ package org.springframework.statemachine.state;
|
|||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
import org.springframework.statemachine.StateMachine;
|
import org.springframework.statemachine.StateMachine;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
@@ -32,6 +34,8 @@ import org.springframework.statemachine.region.Region;
|
|||||||
*/
|
*/
|
||||||
public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(ObjectState.class);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Instantiates a new object state.
|
* Instantiates a new object state.
|
||||||
*
|
*
|
||||||
@@ -122,7 +126,11 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
|||||||
Collection<? extends Action<S, E>> actions = getExitActions();
|
Collection<? extends Action<S, E>> actions = getExitActions();
|
||||||
if (actions != null) {
|
if (actions != null) {
|
||||||
for (Action<S, E> action : actions) {
|
for (Action<S, E> action : actions) {
|
||||||
action.execute(context);
|
try {
|
||||||
|
action.execute(context);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Action execution resulted error", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,7 +140,11 @@ public class ObjectState<S, E> extends AbstractSimpleState<S, E> {
|
|||||||
Collection<? extends Action<S, E>> actions = getEntryActions();
|
Collection<? extends Action<S, E>> actions = getEntryActions();
|
||||||
if (actions != null) {
|
if (actions != null) {
|
||||||
for (Action<S, E> action : actions) {
|
for (Action<S, E> action : actions) {
|
||||||
action.execute(context);
|
try {
|
||||||
|
action.execute(context);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Action execution resulted error", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -195,6 +195,15 @@ public abstract class AbstractStateMachine<S, E> extends StateMachineObjectSuppo
|
|||||||
notifyEventNotAccepted(event);
|
notifyEventNotAccepted(event);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
event = getStateMachineInterceptors().preEvent(event, this);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.info("Event " + event + " threw exception in interceptors, not accepting event");
|
||||||
|
notifyEventNotAccepted(event);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
if (isComplete() || !isRunning()) {
|
if (isComplete() || !isRunning()) {
|
||||||
notifyEventNotAccepted(event);
|
notifyEventNotAccepted(event);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -190,7 +190,15 @@ public class DefaultStateMachineExecutor<S, E> extends LifecycleObjectSupport im
|
|||||||
}
|
}
|
||||||
|
|
||||||
StateContext<S, E> stateContext = buildStateContext(queuedMessage, t, relayStateMachine);
|
StateContext<S, E> stateContext = buildStateContext(queuedMessage, t, relayStateMachine);
|
||||||
stateContext = interceptors.preTransition(stateContext);
|
try {
|
||||||
|
stateContext = interceptors.preTransition(stateContext);
|
||||||
|
} catch (Exception e) {
|
||||||
|
// currently expect that if exception is
|
||||||
|
// thrown, this transition will not match.
|
||||||
|
// i.e. security may throw AccessDeniedException
|
||||||
|
log.info("Interceptors threw exception", e);
|
||||||
|
stateContext = null;
|
||||||
|
}
|
||||||
if (stateContext == null) {
|
if (stateContext == null) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,6 +32,16 @@ import org.springframework.statemachine.transition.Transition;
|
|||||||
*/
|
*/
|
||||||
public interface StateMachineInterceptor<S, E> {
|
public interface StateMachineInterceptor<S, E> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called before message is sent to processing. Throwing exception or
|
||||||
|
* returning null will skip the message.
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
* @param stateMachine the state machine
|
||||||
|
* @return the intercepted message
|
||||||
|
*/
|
||||||
|
Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called prior of a state change. Throwing an exception
|
* Called prior of a state change. Throwing an exception
|
||||||
* from this method will stop a state change logic.
|
* from this method will stop a state change logic.
|
||||||
|
|||||||
@@ -31,6 +31,11 @@ import org.springframework.statemachine.transition.Transition;
|
|||||||
*/
|
*/
|
||||||
public class StateMachineInterceptorAdapter<S, E> implements StateMachineInterceptor<S, E> {
|
public class StateMachineInterceptorAdapter<S, E> implements StateMachineInterceptor<S, E> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
public void preStateChange(State<S, E> state, Message<E> message, Transition<S, E> transition,
|
||||||
StateMachine<S, E> stateMachine) {
|
StateMachine<S, E> stateMachine) {
|
||||||
|
|||||||
@@ -71,6 +71,22 @@ public class StateMachineInterceptorList<S, E> {
|
|||||||
return interceptors.remove(interceptor);
|
return interceptors.remove(interceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pre event.
|
||||||
|
*
|
||||||
|
* @param message the message
|
||||||
|
* @param stateMachine the state machine
|
||||||
|
* @return the message
|
||||||
|
*/
|
||||||
|
public Message<E> preEvent(Message<E> message, StateMachine<S, E> stateMachine) {
|
||||||
|
for (StateMachineInterceptor<S, E> interceptor : interceptors) {
|
||||||
|
if ((message = interceptor.preEvent(message, stateMachine)) == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Pre state change.
|
* Pre state change.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -19,14 +19,20 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
public abstract class AbstractExternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
public abstract class AbstractExternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
||||||
|
|
||||||
public AbstractExternalTransition(State<S,E> source, State<S,E> target, Collection<Action<S, E>> actions, E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
public AbstractExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions,
|
||||||
|
E event, Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
|
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger, securityRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions,
|
||||||
|
E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
||||||
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger);
|
super(source, target, actions, event, TransitionKind.EXTERNAL, guard, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,20 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
public class AbstractInternalTransition <S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
public class AbstractInternalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
||||||
|
|
||||||
public AbstractInternalTransition(State<S, E> source,Collection<Action<S, E>> actions, E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
public AbstractInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
|
||||||
|
Trigger<S, E> trigger) {
|
||||||
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger);
|
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AbstractInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
|
||||||
|
Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
|
super(source, source, actions, event, TransitionKind.INTERNAL, guard, trigger, securityRule);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,20 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
public class AbstractLocalTransition <S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
public class AbstractLocalTransition<S, E> extends AbstractTransition<S, E> implements Transition<S, E> {
|
||||||
|
|
||||||
public AbstractLocalTransition(State<S, E> source, State<S,E> target,Collection<Action<S, E>> actions, E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
public AbstractLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
|
Guard<S, E> guard, Trigger<S, E> trigger) {
|
||||||
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger);
|
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AbstractLocalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
|
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
|
super(source, target, actions, event, TransitionKind.LOCAL, guard, trigger, securityRule);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.Collection;
|
|||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
@@ -44,10 +45,17 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
|||||||
|
|
||||||
private final Guard<S, E> guard;
|
private final Guard<S, E> guard;
|
||||||
|
|
||||||
private Trigger<S, E> trigger;
|
private final Trigger<S, E> trigger;
|
||||||
|
|
||||||
|
private final SecurityRule securityRule;
|
||||||
|
|
||||||
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
TransitionKind kind, Guard<S, E> guard, Trigger<S, E> trigger) {
|
TransitionKind kind, Guard<S, E> guard, Trigger<S, E> trigger) {
|
||||||
|
this(source, target, actions, event, kind, guard, trigger, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public AbstractTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
|
TransitionKind kind, Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
Assert.notNull(source, "Source must be set");
|
Assert.notNull(source, "Source must be set");
|
||||||
Assert.notNull(kind, "Transition type must be set");
|
Assert.notNull(kind, "Transition type must be set");
|
||||||
this.source = source;
|
this.source = source;
|
||||||
@@ -56,6 +64,7 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
|||||||
this.kind = kind;
|
this.kind = kind;
|
||||||
this.guard = guard;
|
this.guard = guard;
|
||||||
this.trigger = trigger;
|
this.trigger = trigger;
|
||||||
|
this.securityRule = securityRule;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -98,4 +107,9 @@ public abstract class AbstractTransition<S, E> implements Transition<S, E> {
|
|||||||
return kind;
|
return kind;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityRule getSecurityRule() {
|
||||||
|
return securityRule;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,20 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
public class DefaultExternalTransition<S, E> extends AbstractExternalTransition<S, E> {
|
public class DefaultExternalTransition<S, E> extends AbstractExternalTransition<S, E> {
|
||||||
|
|
||||||
public DefaultExternalTransition(State<S,E> source, State<S,E> target, Collection<Action<S, E>> actions, E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
public DefaultExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
|
Guard<S, E> guard, Trigger<S, E> trigger) {
|
||||||
super(source, target, actions, event, guard, trigger);
|
super(source, target, actions, event, guard, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DefaultExternalTransition(State<S, E> source, State<S, E> target, Collection<Action<S, E>> actions, E event,
|
||||||
|
Guard<S, E> guard, Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
|
super(source, target, actions, event, guard, trigger, securityRule);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,13 +19,20 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.guard.Guard;
|
import org.springframework.statemachine.guard.Guard;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
public class DefaultInternalTransition<S, E> extends AbstractInternalTransition<S, E> {
|
public class DefaultInternalTransition<S, E> extends AbstractInternalTransition<S, E> {
|
||||||
|
|
||||||
public DefaultInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard, Trigger<S, E> trigger) {
|
public DefaultInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
|
||||||
|
Trigger<S, E> trigger) {
|
||||||
super(source, actions, event, guard, trigger);
|
super(source, actions, event, guard, trigger);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public DefaultInternalTransition(State<S, E> source, Collection<Action<S, E>> actions, E event, Guard<S, E> guard,
|
||||||
|
Trigger<S, E> trigger, SecurityRule securityRule) {
|
||||||
|
super(source, actions, event, guard, trigger, securityRule);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
@@ -108,4 +109,10 @@ public class InitialTransition<S, E> implements Transition<S, E> {
|
|||||||
return TransitionKind.INITIAL;
|
return TransitionKind.INITIAL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityRule getSecurityRule() {
|
||||||
|
// initial cannot have security
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import java.util.Collection;
|
|||||||
|
|
||||||
import org.springframework.statemachine.StateContext;
|
import org.springframework.statemachine.StateContext;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.trigger.Trigger;
|
import org.springframework.statemachine.trigger.Trigger;
|
||||||
|
|
||||||
@@ -76,4 +77,11 @@ public interface Transition<S, E> {
|
|||||||
*/
|
*/
|
||||||
TransitionKind getKind();
|
TransitionKind getKind();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the security rule.
|
||||||
|
*
|
||||||
|
* @return the security rule
|
||||||
|
*/
|
||||||
|
SecurityRule getSecurityRule();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -956,6 +956,11 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
|||||||
stateMachine.getStateMachineAccessor()
|
stateMachine.getStateMachineAccessor()
|
||||||
.withRegion().addStateMachineInterceptor(new StateMachineInterceptor<String, String>() {
|
.withRegion().addStateMachineInterceptor(new StateMachineInterceptor<String, String>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message<String> preEvent(Message<String> message, StateMachine<String, String> stateMachine) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public StateContext<String, String> preTransition(StateContext<String, String> stateContext) {
|
public StateContext<String, String> preTransition(StateContext<String, String> stateContext) {
|
||||||
return stateContext;
|
return stateContext;
|
||||||
@@ -1021,7 +1026,7 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
|
public void stateMachineError(StateMachine<String, String> stateMachine, Exception exception) {
|
||||||
// do something with error
|
// do something with error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end::snippet2[]
|
// end::snippet2[]
|
||||||
@@ -1030,12 +1035,12 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
|||||||
public class GenericApplicationEventListener
|
public class GenericApplicationEventListener
|
||||||
implements ApplicationListener<StateMachineEvent> {
|
implements ApplicationListener<StateMachineEvent> {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(StateMachineEvent event) {
|
public void onApplicationEvent(StateMachineEvent event) {
|
||||||
if (event instanceof OnStateMachineError) {
|
if (event instanceof OnStateMachineError) {
|
||||||
// do something with error
|
// do something with error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end::snippet3[]
|
// end::snippet3[]
|
||||||
|
|
||||||
@@ -1045,7 +1050,7 @@ public class DocsConfigurationSampleTests extends AbstractStateMachineTests {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onApplicationEvent(OnStateMachineError event) {
|
public void onApplicationEvent(OnStateMachineError event) {
|
||||||
// do something with error
|
// do something with error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// end::snippet4[]
|
// end::snippet4[]
|
||||||
|
|||||||
@@ -0,0 +1,171 @@
|
|||||||
|
/*
|
||||||
|
* 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.docs;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.context.annotation.ScopedProxyMode;
|
||||||
|
import org.springframework.security.access.annotation.Secured;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.StateContext;
|
||||||
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
|
||||||
|
public class DocsConfigurationSampleTests3 {
|
||||||
|
|
||||||
|
// tag::snippetA[]
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.event("true")
|
||||||
|
.event("ROLE_ANONYMOUS", ComparisonType.ANY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end::snippetA[]
|
||||||
|
|
||||||
|
// tag::snippetB[]
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config2 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A")
|
||||||
|
.secured("ROLE_ANONYMOUS", ComparisonType.ANY)
|
||||||
|
.secured("hasTarget('S1')");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end::snippetB[]
|
||||||
|
|
||||||
|
|
||||||
|
// tag::snippetC[]
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config3 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.action(securedAction())
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||||
|
@Bean
|
||||||
|
public Action<String, String> securedAction() {
|
||||||
|
return new Action<String, String>() {
|
||||||
|
|
||||||
|
@Secured("ROLE_ANONYMOUS")
|
||||||
|
@Override
|
||||||
|
public void execute(StateContext<String, String> context) {
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
// end::snippetC[]
|
||||||
|
|
||||||
|
// tag::snippetD[]
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config4 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.transitionAccessDecisionManager(null)
|
||||||
|
.eventAccessDecisionManager(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end::snippetD[]
|
||||||
|
|
||||||
|
// tag::snippetE[]
|
||||||
|
@Configuration
|
||||||
|
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||||
|
public static class Config5 extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth
|
||||||
|
.inMemoryAuthentication()
|
||||||
|
.withUser("user").password("password").roles("USER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end::snippetE[]
|
||||||
|
|
||||||
|
// tag::snippetF[]
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config6 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.transition("true")
|
||||||
|
.transition("ROLE_ANONYMOUS", ComparisonType.ANY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// end::snippetF[]
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,136 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.springframework.core.task.SyncTaskExecutor;
|
||||||
|
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.config.StateMachineBuilder;
|
||||||
|
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.config.configurers.SecurityConfigurer;
|
||||||
|
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.statemachine.state.State;
|
||||||
|
|
||||||
|
public abstract class AbstractSecurityTests extends AbstractStateMachineTests {
|
||||||
|
|
||||||
|
protected static void assertTransitionAllowed(StateMachine<States, Events> machine, TestListener listener) throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void assertTransitionDenied(StateMachine<States, Events> machine, TestListener listener) throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(false));
|
||||||
|
assertThat(listener.stateChangedCount, is(0));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static enum States {
|
||||||
|
S0, S1;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static enum Events {
|
||||||
|
A;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class TestListener extends StateMachineListenerAdapter<States, Events> {
|
||||||
|
|
||||||
|
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||||
|
volatile int stateChangedCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stateChanged(State<States, Events> from, State<States, Events> to) {
|
||||||
|
stateChangedCount++;
|
||||||
|
stateChangedLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(int c1) {
|
||||||
|
stateChangedLatch = new CountDownLatch(c1);
|
||||||
|
stateChangedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static StateMachine<States, Events> buildMachine(TestListener listener, String attributes, ComparisonType match, String expression) throws Exception {
|
||||||
|
return buildMachine(listener, attributes, match, expression, null, null, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static StateMachine<States, Events> buildMachine(TestListener listener, String attributes, ComparisonType match,
|
||||||
|
String expression, String eventAttributes, ComparisonType eventMatch, String eventExpression) throws Exception {
|
||||||
|
Builder<States, Events> builder = StateMachineBuilder.<States, Events>builder();
|
||||||
|
|
||||||
|
StateMachineConfigurationConfigurer<States, Events> configureConfiguration = builder.configureConfiguration();
|
||||||
|
configureConfiguration.withConfiguration()
|
||||||
|
.listener(listener)
|
||||||
|
.autoStartup(true)
|
||||||
|
.taskExecutor(new SyncTaskExecutor());
|
||||||
|
|
||||||
|
SecurityConfigurer<States, Events> withSecurity = configureConfiguration.withSecurity();
|
||||||
|
withSecurity.enabled(true);
|
||||||
|
|
||||||
|
if (eventAttributes != null && eventMatch != null) {
|
||||||
|
withSecurity.event(eventAttributes, eventMatch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (eventExpression != null) {
|
||||||
|
withSecurity.event(eventExpression);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
builder.configureStates()
|
||||||
|
.withStates()
|
||||||
|
.initial(States.S0)
|
||||||
|
.state(States.S0)
|
||||||
|
.state(States.S1);
|
||||||
|
|
||||||
|
ExternalTransitionConfigurer<States, Events> withExternal = builder.configureTransitions()
|
||||||
|
.withExternal();
|
||||||
|
if (attributes != null) {
|
||||||
|
withExternal.secured(attributes, match);
|
||||||
|
}
|
||||||
|
if (expression != null) {
|
||||||
|
withExternal.secured(expression);
|
||||||
|
}
|
||||||
|
withExternal.source(States.S0);
|
||||||
|
withExternal.target(States.S1);
|
||||||
|
withExternal.event(Events.A);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,208 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.context.annotation.ScopedProxyMode;
|
||||||
|
import org.springframework.security.access.annotation.Secured;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||||
|
import org.springframework.statemachine.StateContext;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||||
|
import org.springframework.statemachine.security.ActionSecurityTests.Config1;
|
||||||
|
import org.springframework.statemachine.security.ActionSecurityTests.Config2;
|
||||||
|
import org.springframework.statemachine.state.State;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for securing actions.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration(classes = {Config1.class, Config2.class})
|
||||||
|
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||||
|
public class ActionSecurityTests extends AbstractStateMachineTests {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
StateMachine<States, Events> machine;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TestListener listener;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
TestSecAction action1;
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "FOO" })
|
||||||
|
public void testActionExecutionDenied() throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1));
|
||||||
|
assertThat(action1.getCount(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testActionExecutionAllowed() throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1));
|
||||||
|
assertThat(action1.getCount(), is(1));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||||
|
public static class Config1 extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth
|
||||||
|
.inMemoryAuthentication()
|
||||||
|
.withUser("user").password("password").roles("USER");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
public static class Config2 extends EnumStateMachineConfigurerAdapter<States, Events> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withConfiguration()
|
||||||
|
.listener(testListener())
|
||||||
|
.autoStartup(true)
|
||||||
|
.and()
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<States, Events> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial(States.S0)
|
||||||
|
.state(States.S0)
|
||||||
|
.state(States.S1, action1(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S0)
|
||||||
|
.target(States.S1)
|
||||||
|
.event(Events.A);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public TestListener testListener() {
|
||||||
|
return new TestListener();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||||
|
@Bean
|
||||||
|
public TestSecAction action1() {
|
||||||
|
return new TestSecAction();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum States {
|
||||||
|
S0, S1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum Events {
|
||||||
|
A;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestSecAction implements Action<States, Events> {
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
@Secured("ROLE_USER")
|
||||||
|
@Override
|
||||||
|
public void execute(StateContext<States, Events> context) {
|
||||||
|
count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getCount() {
|
||||||
|
return count;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestListener extends StateMachineListenerAdapter<States, Events> {
|
||||||
|
|
||||||
|
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||||
|
volatile int stateChangedCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stateChanged(State<States, Events> from, State<States, Events> to) {
|
||||||
|
stateChangedCount++;
|
||||||
|
stateChangedLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(int c1) {
|
||||||
|
stateChangedLatch = new CountDownLatch(c1);
|
||||||
|
stateChangedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||||
|
public class EventSecurityTests extends AbstractSecurityTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSecurityContext() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "ANONYMOUS" })
|
||||||
|
public void testEventDeniedViaExpression() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, null, null, null, null, null, "false");
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "ANONYMOUS" })
|
||||||
|
public void testEventDeniedViaAttributes() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, null, null, null, "EVENT_B", ComparisonType.ALL, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "ANONYMOUS" })
|
||||||
|
public void testEventAllowedViaAttributes() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, null, null, null, "EVENT_A", ComparisonType.ALL, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class Config {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,479 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.hamcrest.Matchers.notNullValue;
|
||||||
|
import static org.hamcrest.Matchers.nullValue;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.hamcrest.Matchers.instanceOf;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.access.AccessDecisionManager;
|
||||||
|
import org.springframework.security.access.AccessDeniedException;
|
||||||
|
import org.springframework.security.access.ConfigAttribute;
|
||||||
|
import org.springframework.security.authentication.InsufficientAuthenticationException;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||||
|
import org.springframework.statemachine.ObjectStateMachine;
|
||||||
|
import org.springframework.statemachine.StateMachineSystemConstants;
|
||||||
|
import org.springframework.statemachine.TestUtils;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.StateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.statemachine.support.StateMachineInterceptor;
|
||||||
|
import org.springframework.statemachine.support.StateMachineInterceptorList;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic security config tests.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class SecurityConfigTests extends AbstractStateMachineTests {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AnnotationConfigApplicationContext buildContext() {
|
||||||
|
return new AnnotationConfigApplicationContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityEnabledWithTrue() throws Exception {
|
||||||
|
context.register(Config1.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors, notNullValue());
|
||||||
|
assertThat(interceptors.size(), is(1));
|
||||||
|
assertThat(interceptors.get(0), instanceOf(StateMachineSecurityInterceptor.class));
|
||||||
|
Object adm = TestUtils.readField("transitionAccessDecisionManager", interceptors.get(0));
|
||||||
|
assertThat(adm, nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityDisabledWithFalse() throws Exception {
|
||||||
|
context.register(Config2.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors, notNullValue());
|
||||||
|
assertThat(interceptors.size(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityEnabledWithJustWith() throws Exception {
|
||||||
|
context.register(Config3.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors.size(), is(1));
|
||||||
|
assertThat(interceptors.get(0), instanceOf(StateMachineSecurityInterceptor.class));
|
||||||
|
Object adm = TestUtils.readField("transitionAccessDecisionManager", interceptors.get(0));
|
||||||
|
assertThat(adm, nullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSecurityDisabledNoSecurityConfigurer() throws Exception {
|
||||||
|
context.register(Config4.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors, notNullValue());
|
||||||
|
assertThat(interceptors.size(), is(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCustomAccessDecisionManager() throws Exception {
|
||||||
|
context.register(Config5.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors, notNullValue());
|
||||||
|
assertThat(interceptors.size(), is(1));
|
||||||
|
assertThat(interceptors.get(0), instanceOf(StateMachineSecurityInterceptor.class));
|
||||||
|
Object adm = TestUtils.readField("transitionAccessDecisionManager", interceptors.get(0));
|
||||||
|
assertThat(adm, notNullValue());
|
||||||
|
assertThat(adm, instanceOf(MockAccessDecisionManager.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransitionExplicit() throws Exception {
|
||||||
|
context.register(Config6.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
Transition<String, String> transition = machine.getTransitions().iterator().next();
|
||||||
|
assertThat(transition.getSecurityRule(), notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransitionGlobal() throws Exception {
|
||||||
|
context.register(Config8.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
Transition<String, String> transition = machine.getTransitions().iterator().next();
|
||||||
|
assertThat(transition.getSecurityRule(), notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testEventRule() throws Exception {
|
||||||
|
context.register(Config7.class);
|
||||||
|
context.refresh();
|
||||||
|
assertTrue(context.containsBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE));
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
ObjectStateMachine<String, String> machine =
|
||||||
|
context.getBean(StateMachineSystemConstants.DEFAULT_ID_STATEMACHINE, ObjectStateMachine.class);
|
||||||
|
assertThat(machine, notNullValue());
|
||||||
|
|
||||||
|
StateMachineInterceptorList<?, ?> ilist = TestUtils.readField("interceptors", machine);
|
||||||
|
List<StateMachineInterceptor<?, ?>> interceptors = TestUtils.readField("interceptors", ilist);
|
||||||
|
assertThat(interceptors, notNullValue());
|
||||||
|
assertThat(interceptors.size(), is(1));
|
||||||
|
assertThat(interceptors.get(0), instanceOf(StateMachineSecurityInterceptor.class));
|
||||||
|
Object adm = TestUtils.readField("eventSecurityRule", interceptors.get(0));
|
||||||
|
assertThat(adm, notNullValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config1 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config2 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config3 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config4 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config5 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.eventAccessDecisionManager(new MockAccessDecisionManager())
|
||||||
|
.transitionAccessDecisionManager(new MockAccessDecisionManager());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config6 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A")
|
||||||
|
.secured("expression")
|
||||||
|
.secured("FOO", ComparisonType.ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config7 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.event("expression")
|
||||||
|
.event("FOO", ComparisonType.ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config8 extends StateMachineConfigurerAdapter<String, String> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<String, String> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.transition("expression")
|
||||||
|
.transition("FOO", ComparisonType.ALL);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<String, String> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial("S0")
|
||||||
|
.state("S1");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<String, String> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source("S0")
|
||||||
|
.target("S1")
|
||||||
|
.event("A");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MockAccessDecisionManager implements AccessDecisionManager {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes)
|
||||||
|
throws AccessDeniedException, InsufficientAuthenticationException {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(ConfigAttribute attribute) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean supports(Class<?> clazz) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class SecurityRuleTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertAttributesToCommaSeparatedString() {
|
||||||
|
Collection<String> attributes = new ArrayList<String>();
|
||||||
|
attributes.add("ROLE_1");
|
||||||
|
attributes.add("ROLE_2");
|
||||||
|
assertEquals("ROLE_1, ROLE_2", SecurityRule.securityAttributesToCommaDelimitedList(attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testConvertAttributesFromCommaSeparatedString() {
|
||||||
|
Collection<String> attributes = SecurityRule.commaDelimitedListToSecurityAttributes(" ,,ROLE_1, ROLE_2");
|
||||||
|
assertEquals(2, attributes.size());
|
||||||
|
assertTrue(attributes.contains("ROLE_1"));
|
||||||
|
assertTrue(attributes.contains("ROLE_2"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testDefaultComparisonType() {
|
||||||
|
SecurityRule rule = new SecurityRule();
|
||||||
|
assertTrue(rule.getComparisonType() == SecurityRule.ComparisonType.ANY);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||||
|
public class TransitionSecurityAttributeTests extends AbstractSecurityTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testAnonymousRole() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testTransitionSource() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "TRANSITION_SOURCE_S0", ComparisonType.ANY, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testTransitionTarget() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "TRANSITION_TARGET_S1", ComparisonType.ANY, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testTransitionSourceAndTargetAll() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "TRANSITION_SOURCE_S0,TRANSITION_TARGET_S1", ComparisonType.ALL, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testTransitionSourceAndTargetAny() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "TRANSITION_SOURCE_S0,TRANSITION_TARGET_S1", ComparisonType.ANY, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class Config {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.junit.Assert.assertFalse;
|
||||||
|
import static org.junit.Assert.assertTrue;
|
||||||
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.eq;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.expression.Expression;
|
||||||
|
import org.springframework.expression.spel.standard.SpelExpressionParser;
|
||||||
|
import org.springframework.expression.spel.support.StandardEvaluationContext;
|
||||||
|
import org.springframework.security.access.PermissionEvaluator;
|
||||||
|
import org.springframework.security.access.expression.ExpressionUtils;
|
||||||
|
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||||
|
import org.springframework.security.core.Authentication;
|
||||||
|
import org.springframework.statemachine.state.State;
|
||||||
|
import org.springframework.statemachine.transition.Transition;
|
||||||
|
|
||||||
|
public class TransitionSecurityExpressionRootTests {
|
||||||
|
|
||||||
|
SpelExpressionParser parser = new SpelExpressionParser();
|
||||||
|
TransitionSecurityExpressionRoot root;
|
||||||
|
StandardEvaluationContext ctx;
|
||||||
|
private AuthenticationTrustResolver trustResolver;
|
||||||
|
private Authentication user;
|
||||||
|
private Transition<?, ?> transition;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void createContext() {
|
||||||
|
user = mock(Authentication.class);
|
||||||
|
transition = mock(Transition.class);
|
||||||
|
root = new TransitionSecurityExpressionRoot(user, transition);
|
||||||
|
ctx = new StandardEvaluationContext();
|
||||||
|
ctx.setRootObject(root);
|
||||||
|
trustResolver = mock(AuthenticationTrustResolver.class);
|
||||||
|
root.setTrustResolver(trustResolver);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||||
|
@Test
|
||||||
|
public void testSourceTarget() throws Exception {
|
||||||
|
State source = mock(State.class);
|
||||||
|
when(source.getId()).thenReturn("S1");
|
||||||
|
|
||||||
|
State target = mock(State.class);
|
||||||
|
when(target.getId()).thenReturn("S2");
|
||||||
|
|
||||||
|
when(transition.getSource()).thenReturn(source);
|
||||||
|
when(transition.getTarget()).thenReturn(target);
|
||||||
|
|
||||||
|
Expression e1 = parser.parseExpression("hasSource('S1')");
|
||||||
|
assertTrue(ExpressionUtils.evaluateAsBoolean(e1, ctx));
|
||||||
|
Expression e2 = parser.parseExpression("hasTarget('S2')");
|
||||||
|
assertTrue(ExpressionUtils.evaluateAsBoolean(e2, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void canCallMethodsOnVariables() throws Exception {
|
||||||
|
ctx.setVariable("var", "somestring");
|
||||||
|
Expression e = parser.parseExpression("#var.length() == 10");
|
||||||
|
assertTrue(ExpressionUtils.evaluateAsBoolean(e, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAnonymousReturnsTrueIfTrustResolverReportsAnonymous() {
|
||||||
|
when(trustResolver.isAnonymous(user)).thenReturn(true);
|
||||||
|
assertTrue(root.isAnonymous());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isAnonymousReturnsFalseIfTrustResolverReportsNonAnonymous() {
|
||||||
|
when(trustResolver.isAnonymous(user)).thenReturn(false);
|
||||||
|
assertFalse(root.isAnonymous());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasPermissionOnDomainObjectReturnsFalseIfPermissionEvaluatorDoes() throws Exception {
|
||||||
|
final Object dummyDomainObject = new Object();
|
||||||
|
final PermissionEvaluator pe = mock(PermissionEvaluator.class);
|
||||||
|
ctx.setVariable("domainObject", dummyDomainObject);
|
||||||
|
root.setPermissionEvaluator(pe);
|
||||||
|
when(pe.hasPermission(user, dummyDomainObject, "ignored")).thenReturn(false);
|
||||||
|
assertFalse(root.hasPermission(dummyDomainObject, "ignored"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasPermissionOnDomainObjectReturnsTrueIfPermissionEvaluatorDoes() throws Exception {
|
||||||
|
final Object dummyDomainObject = new Object();
|
||||||
|
final PermissionEvaluator pe = mock(PermissionEvaluator.class);
|
||||||
|
ctx.setVariable("domainObject", dummyDomainObject);
|
||||||
|
root.setPermissionEvaluator(pe);
|
||||||
|
when(pe.hasPermission(user, dummyDomainObject, "ignored")).thenReturn(true);
|
||||||
|
assertTrue(root.hasPermission(dummyDomainObject, "ignored"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void hasPermissionOnDomainObjectWorksWithIntegerExpressions() throws Exception {
|
||||||
|
final Object dummyDomainObject = new Object();
|
||||||
|
ctx.setVariable("domainObject", dummyDomainObject);
|
||||||
|
final PermissionEvaluator pe = mock(PermissionEvaluator.class);
|
||||||
|
root.setPermissionEvaluator(pe);
|
||||||
|
when(pe.hasPermission(eq(user), eq(dummyDomainObject), any(Integer.class))).thenReturn(true).thenReturn(true).thenReturn(false);
|
||||||
|
|
||||||
|
Expression e = parser.parseExpression("hasPermission(#domainObject, 0xA)");
|
||||||
|
// evaluator returns true
|
||||||
|
assertTrue(ExpressionUtils.evaluateAsBoolean(e, ctx));
|
||||||
|
e = parser.parseExpression("hasPermission(#domainObject, 10)");
|
||||||
|
// evaluator returns true
|
||||||
|
assertTrue(ExpressionUtils.evaluateAsBoolean(e, ctx));
|
||||||
|
e = parser.parseExpression("hasPermission(#domainObject, 0xFF)");
|
||||||
|
// evaluator returns false, make sure return value matches
|
||||||
|
assertFalse(ExpressionUtils.evaluateAsBoolean(e, ctx));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,156 @@
|
|||||||
|
/*
|
||||||
|
* 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 static org.hamcrest.Matchers.containsInAnyOrder;
|
||||||
|
import static org.hamcrest.Matchers.is;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.core.task.SyncTaskExecutor;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.config.StateMachineBuilder;
|
||||||
|
import org.springframework.statemachine.config.StateMachineBuilder.Builder;
|
||||||
|
import org.springframework.statemachine.config.configurers.ExternalTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.listener.StateMachineListenerAdapter;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.statemachine.state.State;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for securing transitions.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||||
|
public class TransitionSecurityExpressionTests extends AbstractStateMachineTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(authorities = { "FOO" })
|
||||||
|
public void testAttr() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "FOO" })
|
||||||
|
public void testExpression() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, null, null, "hasRole('FOO')");
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertTransitionAllowed(StateMachine<States, Events> machine, TestListener listener) throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S1));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertTransitionDenied(StateMachine<States, Events> machine, TestListener listener) throws Exception {
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(true));
|
||||||
|
assertThat(listener.stateChangedCount, is(1));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
|
||||||
|
listener.reset(1);
|
||||||
|
machine.sendEvent(Events.A);
|
||||||
|
assertThat(listener.stateChangedLatch.await(2, TimeUnit.SECONDS), is(false));
|
||||||
|
assertThat(listener.stateChangedCount, is(0));
|
||||||
|
assertThat(machine.getState().getIds(), containsInAnyOrder(States.S0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class Config {
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum States {
|
||||||
|
S0, S1;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static enum Events {
|
||||||
|
A;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestListener extends StateMachineListenerAdapter<States, Events> {
|
||||||
|
|
||||||
|
volatile CountDownLatch stateChangedLatch = new CountDownLatch(1);
|
||||||
|
volatile int stateChangedCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void stateChanged(State<States, Events> from, State<States, Events> to) {
|
||||||
|
stateChangedCount++;
|
||||||
|
stateChangedLatch.countDown();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reset(int c1) {
|
||||||
|
stateChangedLatch = new CountDownLatch(c1);
|
||||||
|
stateChangedCount = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static StateMachine<States, Events> buildMachine(TestListener listener, String attributes, ComparisonType match, String expression) throws Exception {
|
||||||
|
Builder<States, Events> builder = StateMachineBuilder.<States, Events>builder();
|
||||||
|
|
||||||
|
builder.configureConfiguration()
|
||||||
|
.withConfiguration()
|
||||||
|
.listener(listener)
|
||||||
|
.autoStartup(true)
|
||||||
|
.taskExecutor(new SyncTaskExecutor())
|
||||||
|
.and()
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true);
|
||||||
|
|
||||||
|
builder.configureStates()
|
||||||
|
.withStates()
|
||||||
|
.initial(States.S0)
|
||||||
|
.state(States.S0)
|
||||||
|
.state(States.S1);
|
||||||
|
|
||||||
|
ExternalTransitionConfigurer<States, Events> withExternal = builder.configureTransitions()
|
||||||
|
.withExternal();
|
||||||
|
if (attributes != null) {
|
||||||
|
withExternal.secured(attributes, match);
|
||||||
|
}
|
||||||
|
if (expression != null) {
|
||||||
|
withExternal.secured(expression);
|
||||||
|
}
|
||||||
|
withExternal.source(States.S0);
|
||||||
|
withExternal.target(States.S1);
|
||||||
|
withExternal.event(Events.A);
|
||||||
|
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
/*
|
||||||
|
* 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 org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext;
|
||||||
|
import org.springframework.test.annotation.DirtiesContext.ClassMode;
|
||||||
|
import org.springframework.test.context.ContextConfiguration;
|
||||||
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for securing transitions.
|
||||||
|
*
|
||||||
|
* @author Janne Valkealahti
|
||||||
|
*/
|
||||||
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
|
@ContextConfiguration
|
||||||
|
@DirtiesContext(classMode = ClassMode.BEFORE_EACH_TEST_METHOD)
|
||||||
|
public class TransitionSecurityTests extends AbstractSecurityTests {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNoSecurityContext() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "ANONYMOUS" })
|
||||||
|
public void testTransitionAllowed() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionAllowed(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(roles = { "FOO" })
|
||||||
|
public void testTransitionDenied() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, "ROLE_ANONYMOUS", ComparisonType.ANY, null);
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser
|
||||||
|
public void testExpression() throws Exception {
|
||||||
|
TestListener listener = new TestListener();
|
||||||
|
StateMachine<States,Events> machine = buildMachine(listener, null, null, "hasTarget('S1')");
|
||||||
|
assertTransitionDenied(machine, listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public static class Config {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -211,11 +211,11 @@ public class StateChangeInterceptorTests extends AbstractStateMachineTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static enum States {
|
public static enum States {
|
||||||
S0, S1, S11, S12, S2, S21, S211, S212
|
S0, S1, S11, S12, S2, S21, S211, S212
|
||||||
}
|
}
|
||||||
|
|
||||||
public static enum Events {
|
public static enum Events {
|
||||||
A, B, C, D, E, F, G, H, I
|
A, B, C, D, E, F, G, H, I
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class FooAction implements Action<States, Events> {
|
private static class FooAction implements Action<States, Events> {
|
||||||
@@ -272,6 +272,11 @@ public class StateChangeInterceptorTests extends AbstractStateMachineTests {
|
|||||||
volatile CountDownLatch preStateChangeLatch = new CountDownLatch(1);
|
volatile CountDownLatch preStateChangeLatch = new CountDownLatch(1);
|
||||||
volatile int preStateChangeCount = 0;
|
volatile int preStateChangeCount = 0;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Message<Events> preEvent(Message<Events> message, StateMachine<States, Events> stateMachine) {
|
||||||
|
return message;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void preStateChange(State<States, Events> state, Message<Events> message,
|
public void preStateChange(State<States, Events> state, Message<Events> message,
|
||||||
Transition<States, Events> transition, StateMachine<States, Events> stateMachine) {
|
Transition<States, Events> transition, StateMachine<States, Events> stateMachine) {
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import org.springframework.statemachine.StateMachine;
|
|||||||
import org.springframework.statemachine.access.StateMachineAccessor;
|
import org.springframework.statemachine.access.StateMachineAccessor;
|
||||||
import org.springframework.statemachine.action.Action;
|
import org.springframework.statemachine.action.Action;
|
||||||
import org.springframework.statemachine.listener.StateMachineListener;
|
import org.springframework.statemachine.listener.StateMachineListener;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule;
|
||||||
import org.springframework.statemachine.state.EnumState;
|
import org.springframework.statemachine.state.EnumState;
|
||||||
import org.springframework.statemachine.state.State;
|
import org.springframework.statemachine.state.State;
|
||||||
import org.springframework.statemachine.transition.Transition;
|
import org.springframework.statemachine.transition.Transition;
|
||||||
@@ -126,6 +127,11 @@ public class StateContextExpressionMethodsTests {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SecurityRule getSecurityRule() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class MockStatemachine implements StateMachine<SpelStates, SpelEvents> {
|
private static class MockStatemachine implements StateMachine<SpelStates, SpelEvents> {
|
||||||
|
|||||||
@@ -54,3 +54,13 @@ project('spring-statemachine-samples-scope') {
|
|||||||
// compile("org.springframework:spring-webmvc:$springVersion")
|
// compile("org.springframework:spring-webmvc:$springVersion")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
project('spring-statemachine-samples-security') {
|
||||||
|
description = 'Spring State Machine Web Security Sample'
|
||||||
|
dependencies {
|
||||||
|
compile("org.springframework.boot:spring-boot-starter-thymeleaf:$springBootVersion")
|
||||||
|
compile("org.springframework.boot:spring-boot-starter-security:$springBootVersion")
|
||||||
|
compile("org.springframework.security:spring-security-config:$springSecurityVersion")
|
||||||
|
compile("org.springframework.security:spring-security-web:$springSecurityVersion")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
/*
|
||||||
|
* 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 demo.security;
|
||||||
|
|
||||||
|
import org.springframework.boot.SpringApplication;
|
||||||
|
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||||
|
|
||||||
|
@SpringBootApplication
|
||||||
|
public class Application {
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
SpringApplication.run(Application.class, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,177 @@
|
|||||||
|
/*
|
||||||
|
* 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 demo.security;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.Scanner;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.Scope;
|
||||||
|
import org.springframework.context.annotation.ScopedProxyMode;
|
||||||
|
import org.springframework.core.io.ClassPathResource;
|
||||||
|
import org.springframework.security.access.annotation.Secured;
|
||||||
|
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||||
|
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.StateContext;
|
||||||
|
import org.springframework.statemachine.action.Action;
|
||||||
|
import org.springframework.statemachine.config.EnableStateMachine;
|
||||||
|
import org.springframework.statemachine.config.EnumStateMachineConfigurerAdapter;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineConfigurationConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||||
|
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||||
|
import org.springframework.statemachine.security.SecurityRule.ComparisonType;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class StateMachineConfig {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(StateMachineConfig.class);
|
||||||
|
|
||||||
|
//tag::snippetE[]
|
||||||
|
@EnableWebSecurity
|
||||||
|
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||||
|
static class SecurityConfig extends WebSecurityConfigurerAdapter {
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
|
||||||
|
auth
|
||||||
|
.inMemoryAuthentication()
|
||||||
|
.withUser("user")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER")
|
||||||
|
.and()
|
||||||
|
.withUser("admin")
|
||||||
|
.password("password")
|
||||||
|
.roles("USER", "ADMIN");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
//end::snippetE[]
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableStateMachine
|
||||||
|
static class Config
|
||||||
|
extends EnumStateMachineConfigurerAdapter<States, Events> {
|
||||||
|
|
||||||
|
//tag::snippetA[]
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineConfigurationConfigurer<States, Events> config)
|
||||||
|
throws Exception {
|
||||||
|
config
|
||||||
|
.withConfiguration()
|
||||||
|
.autoStartup(true)
|
||||||
|
.and()
|
||||||
|
.withSecurity()
|
||||||
|
.enabled(true)
|
||||||
|
.event("hasRole('USER')");
|
||||||
|
}
|
||||||
|
//end::snippetA[]
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineStateConfigurer<States, Events> states)
|
||||||
|
throws Exception {
|
||||||
|
states
|
||||||
|
.withStates()
|
||||||
|
.initial(States.S0)
|
||||||
|
.states(EnumSet.allOf(States.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
//tag::snippetB[]
|
||||||
|
@Override
|
||||||
|
public void configure(StateMachineTransitionConfigurer<States, Events> transitions)
|
||||||
|
throws Exception {
|
||||||
|
transitions
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S0).target(States.S1).event(Events.A)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S1).target(States.S2).event(Events.B)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S2).target(States.S0).event(Events.C)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S2).target(States.S3).event(Events.E)
|
||||||
|
.secured("ROLE_ADMIN", ComparisonType.ANY)
|
||||||
|
.and()
|
||||||
|
.withExternal()
|
||||||
|
.source(States.S3).target(States.S0).event(Events.C)
|
||||||
|
.and()
|
||||||
|
.withInternal()
|
||||||
|
.source(States.S0).event(Events.D)
|
||||||
|
.action(adminAction())
|
||||||
|
.and()
|
||||||
|
.withInternal()
|
||||||
|
.source(States.S1).event(Events.F)
|
||||||
|
.action(transitionAction())
|
||||||
|
.secured("ROLE_ADMIN", ComparisonType.ANY);
|
||||||
|
}
|
||||||
|
//end::snippetB[]
|
||||||
|
|
||||||
|
//tag::snippetC[]
|
||||||
|
@Scope(proxyMode = ScopedProxyMode.TARGET_CLASS)
|
||||||
|
@Bean
|
||||||
|
public Action<States, Events> adminAction() {
|
||||||
|
return new Action<States, Events>() {
|
||||||
|
|
||||||
|
@Secured("ROLE_ADMIN")
|
||||||
|
@Override
|
||||||
|
public void execute(StateContext<States, Events> context) {
|
||||||
|
log.info("Executed only for admin role");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//end::snippetC[]
|
||||||
|
|
||||||
|
//tag::snippetD[]
|
||||||
|
@Bean
|
||||||
|
public Action<States, Events> transitionAction() {
|
||||||
|
return new Action<States, Events>() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(StateContext<States, Events> context) {
|
||||||
|
log.info("Executed only for admin role");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
//end::snippetD[]
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public String stateChartModel() throws IOException {
|
||||||
|
ClassPathResource model = new ClassPathResource("statechartmodel.txt");
|
||||||
|
InputStream inputStream = model.getInputStream();
|
||||||
|
Scanner scanner = new Scanner(inputStream);
|
||||||
|
String content = scanner.useDelimiter("\\Z").next();
|
||||||
|
scanner.close();
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum States {
|
||||||
|
S0, S1, S2, S3;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum Events {
|
||||||
|
A, B, C, D, E, F;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
/*
|
||||||
|
* 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 demo.security;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.statemachine.StateMachine;
|
||||||
|
import org.springframework.stereotype.Controller;
|
||||||
|
import org.springframework.ui.Model;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
|
||||||
|
import demo.security.StateMachineConfig.Events;
|
||||||
|
import demo.security.StateMachineConfig.States;
|
||||||
|
|
||||||
|
@Controller
|
||||||
|
public class StateMachineController {
|
||||||
|
|
||||||
|
private final static Log log = LogFactory.getLog(StateMachineController.class);
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
private StateMachine<States, Events> stateMachine;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
@Qualifier("stateChartModel")
|
||||||
|
private String stateChartModel;
|
||||||
|
|
||||||
|
@RequestMapping("/")
|
||||||
|
public String greeting() {
|
||||||
|
return "redirect:/states";
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping("/states")
|
||||||
|
public String getStates(@RequestParam(value = "event", required = false) Events event, Model model) {
|
||||||
|
if (event != null) {
|
||||||
|
try {
|
||||||
|
stateMachine.sendEvent(event);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Error sendEvent", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
model.addAttribute("states", stateMachine.getState().getIds());
|
||||||
|
model.addAttribute("stateChartModel", stateChartModel);
|
||||||
|
return "states";
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
security:
|
||||||
|
basic:
|
||||||
|
enabled: false
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<configuration>
|
||||||
|
|
||||||
|
<include resource="org/springframework/boot/logging/logback/base.xml"/>
|
||||||
|
|
||||||
|
<logger name="org.springframework.statemachine" level="TRACE"/>
|
||||||
|
|
||||||
|
</configuration>
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
+--------------------------------------------------------------------------+
|
||||||
|
| SM |
|
||||||
|
+--------------------------------------------------------------------------+
|
||||||
|
| |
|
||||||
|
| +-----------+ A +-----------+ B +--------+ E +--------+ |
|
||||||
|
| *-->| S0 |------>| S1 |------>| S2 |------>| S3 | |
|
||||||
|
| | | | | | | | | |
|
||||||
|
| | D | | F | | | | | |
|
||||||
|
| | +-------+ | | +-------+ | | | | | |
|
||||||
|
| | | | | | | | | | | | | |
|
||||||
|
| | | v | | | v | | | | | |
|
||||||
|
| +-----------+ +-----------+ +--------+ +--------+ |
|
||||||
|
| ^ ^ | | |
|
||||||
|
| | | C | | |
|
||||||
|
| | +------------------------------------+ | |
|
||||||
|
| | C | |
|
||||||
|
| +----------------------------------------------------------+ |
|
||||||
|
| |
|
||||||
|
+--------------------------------------------------------------------------+
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<!DOCTYPE HTML>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<title>Spring Statemachine Scope Demo</title>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div th:with="currentUser=${#httpServletRequest.userPrincipal?.principal}" >
|
||||||
|
<div th:if="${currentUser != null}">
|
||||||
|
<form th:action="@{/logout}" method="post">
|
||||||
|
<input type="submit" value="Log out" />
|
||||||
|
</form>
|
||||||
|
<p th:text="'CurrentUser: ' + ${currentUser.username}"/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p th:text="'States: ' + ${states}" />
|
||||||
|
<form action="#" data-th-action="@{/states}" data-th-object="${model}" method="post">
|
||||||
|
<button type="submit" name="event" value="A">Send A</button>
|
||||||
|
<button type="submit" name="event" value="B">Send B</button>
|
||||||
|
<button type="submit" name="event" value="C">Send C</button>
|
||||||
|
<button type="submit" name="event" value="D">Send D</button>
|
||||||
|
<button type="submit" name="event" value="E">Send E</button>
|
||||||
|
<button type="submit" name="event" value="F">Send F</button>
|
||||||
|
</form>
|
||||||
|
<pre th:text="${stateChartModel}"/>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user