diff --git a/docs/src/reference/asciidoc/index.adoc b/docs/src/reference/asciidoc/index.adoc index 73e7ee9d..155cd356 100644 --- a/docs/src/reference/asciidoc/index.adoc +++ b/docs/src/reference/asciidoc/index.adoc @@ -11,6 +11,7 @@ :core-jdbc-JdbcTemplate: http://docs.spring.io/spring/docs/{spring-version}/spring-framework-reference/html/jdbc.html#jdbc-JdbcTemplate :sm-statecontext: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.html :sm-statecontext-stage: http://docs.spring.io/spring-statemachine/docs/{spring-statemachine-version}/api/org/springframework/statemachine/StateContext.Stage.html +:spring-security-site: http://projects.spring.io/spring-security = Spring Statemachine - Reference Documentation diff --git a/docs/src/reference/asciidoc/sm.adoc b/docs/src/reference/asciidoc/sm.adoc index d590292e..888f556d 100644 --- a/docs/src/reference/asciidoc/sm.adoc +++ b/docs/src/reference/asciidoc/sm.adoc @@ -1076,10 +1076,10 @@ More about error handling shown in above example, see section [[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 <>. +Security features are build atop of functionality from a +{spring-security-site}[_Spring Security_]. Security features are +handy when it is required to protect part of a state machine +execution and interaction with it. [IMPORTANT] ==== @@ -1088,13 +1088,26 @@ we don't go into details of how overall security framework works. For this read _Spring Security_ reference documentation. ==== +First level of defence with a security is naturally protecting events +which really are a driver from user point of view what is going to +happen in a state machine. More fine grained security settings can +then be defined for transitions and actions. This can be think of like +allowing an employee to access a building, walk around it and then +giving more detailed access rights to enter different rooms and allow +to switch lights on and off while being on those rooms. If you trust +your users then event security may be all you need, if you don't, then +more detailed security needs to be applied. + +More detailed info can be +found from section <>. + [TIP] ==== For complete example, see sample <>. ==== === Configuring Security -All generic configurations for security are done from +All generic configurations for security are done in `SecurityConfigurer` which is obtained from `StateMachineConfigurationConfigurer`. Security is disabled on default even if _Spring Security_ classes are @@ -1172,38 +1185,125 @@ 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. +transitions both use a `RoleVoter`, meaning you can use role attributes +familiar from _Spring Security_. For attributes we have 3 different comparison types, `ANY`, `ALL` and -`MAJORITY` which maps into voters `AffirmativeBased`, `UnanimousBased` -and `ConsensusBased` respectively. +`MAJORITY` which maps into default access decision managers +`AffirmativeBased`, `UnanimousBased` and `ConsensusBased` respectively. +If custom `AccessDecisionManager` has been defined, comparison type is +effectively discarded as it's only used to create a default manager. ==== Generic Expression Usage Security expressions needs to return either _TRUE_ or _FALSE_. +The base class for expression root objects is +`SecurityExpressionRoot`. This provides some common expressions which +are available in both transition and event security. + +[[common-expressions]] +.Common built-in expressions +|=== +| Expression | Description + +| `hasRole([role])` +| Returns `true` if the current principal has the specified role. By +default if the supplied role does not start with 'ROLE_' it will be +added. This can be customized by modifying the `defaultRolePrefix` on +`DefaultWebSecurityExpressionHandler`. + +| `hasAnyRole([role1,role2])` +| Returns `true` if the current principal has any of the supplied +roles (given as a comma-separated list of strings). By default if the +supplied role does not start with 'ROLE_' it will be added. This can +be customized by modifying the `defaultRolePrefix` on +`DefaultWebSecurityExpressionHandler`. + +| `hasAuthority([authority])` +| Returns `true` if the current principal has the specified authority. + +| `hasAnyAuthority([authority1,authority2])` +| Returns `true` if the current principal has any of the supplied +roles (given as a comma-separated list of strings) + +| `principal` +| Allows direct access to the principal object representing the +current user + +| `authentication` +| Allows direct access to the current `Authentication` object obtained +from the `SecurityContext` + +| `permitAll` +| Always evaluates to `true` + +| `denyAll` +| Always evaluates to `false` + +| `isAnonymous()` +| Returns `true` if the current principal is an anonymous user + +| `isRememberMe()` +| Returns `true` if the current principal is a remember-me user + +| `isAuthenticated()` +| Returns `true` if the user is not anonymous + +| `isFullyAuthenticated()` +| Returns `true` if the user is not an anonymous or a remember-me user + +| `hasPermission(Object target, Object permission)` +| Returns `true` if the user has access to the provided target for the +given permission. For example, `hasPermission(domainObject, 'read')` + +| `hasPermission(Object targetId, String targetType, Object +permission)` +| Returns `true` if the user has access to the provided target for the +given permission. For example, `hasPermission(1, +'com.example.domain.Message', 'read')` +|=== + ==== Event Attributes -Event id can be matched by using prefix _EVENT__. For example matching -event `A` would match with attribte _EVENT_A_. +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. +The base class for expression root object for event is +`EventSecurityExpressionRoot`. This provides access to a `Message` +object which is passed around with eventing. + +.Event expressions +|=== +| Expression | Description + +| `hasEvent(Object event)` +| Returns `true` if the event matches given event. + +|=== ==== Transition Attributes Matching transition sources and targets, use prefixes -_TRANSITION_SOURCE__ and _TRANSITION_TARGET__ respectively. +`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. +The base class for expression root object for transition is +`TransitionSecurityExpressionRoot`. This provides access to a +`Transition` +object which is passed around for transition changes. + +.Transition expressions +|=== +| Expression | Description + +| `hasSource(Object source)` +| Returns `true` if the transition source matches given source. + +| `hasTarget(Object target)` +| Returns `true` if the transition target matches given target. + +|=== [[sm-security-details]] === Understanding Security @@ -1212,14 +1312,17 @@ 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. +[NOTE] +==== 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 +<> 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 diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/EventSecurityExpressionRoot.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/EventSecurityExpressionRoot.java index e9c9eccb..ccda83b8 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/EventSecurityExpressionRoot.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/EventSecurityExpressionRoot.java @@ -1,5 +1,5 @@ /* - * Copyright 2015 the original author or authors. + * Copyright 2016 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. @@ -18,6 +18,7 @@ package org.springframework.statemachine.security; import org.springframework.messaging.Message; import org.springframework.security.access.expression.SecurityExpressionRoot; import org.springframework.security.core.Authentication; +import org.springframework.util.ObjectUtils; /** * The {@link SecurityExpressionRoot} used for {@link Message} expressions. @@ -29,8 +30,19 @@ public class EventSecurityExpressionRoot extends SecurityExpressionRoot { public final Message message; + /** + * Instantiates a new event security expression root. + * + * @param authentication the authentication + * @param message the message + */ public EventSecurityExpressionRoot(Authentication authentication, Message message) { super(authentication); this.message = message; } + + public final boolean hasEvent(Object source) { + return ObjectUtils.nullSafeEquals(source, message.getPayload()); + } + } diff --git a/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/TransitionSecurityExpressionRoot.java b/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/TransitionSecurityExpressionRoot.java index b439db33..741fa925 100644 --- a/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/TransitionSecurityExpressionRoot.java +++ b/spring-statemachine-core/src/main/java/org/springframework/statemachine/security/TransitionSecurityExpressionRoot.java @@ -45,8 +45,8 @@ public class TransitionSecurityExpressionRoot extends SecurityExpressionRoot { return ObjectUtils.nullSafeEquals(source, transition.getSource().getId()); } - public final boolean hasTarget(Object source) { - return ObjectUtils.nullSafeEquals(source, transition.getTarget().getId()); + public final boolean hasTarget(Object target) { + return ObjectUtils.nullSafeEquals(target, transition.getTarget().getId()); } }