Restarting Spring Web Flow with new model values - spring

I am trying to find a way that if the user restarts the flow the values are not in it. If you look at my flow below you can see that the user enters data, previews it and then saves it.. after the save the user can go back to enter new data into the input screen but with my current setup the screen shows the pre-data. how can I clear it on restart?
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="customer" class="org.uftwf.domain.Customer"/>
<view-state id="helloworld" view="input.jsp" model="customer" popup="true">
<transition on="submit" to="preview" />
<transition on="cancel" to="thanks" validate="false"/>
</view-state>
<view-state id="preview" model="customer">
<transition on="cancel" to="helloworld"/>
<transition on="accept" to="save">
<evaluate expression="hellowWorldFlowActions.addCustomer(customer)"/>
</transition>
</view-state>
<view-state id="save" model="customer">
<transition on="accept" to="thanks"/>
</view-state>
<view-state id="thanks">
<transition on="restart" to="helloworld"/>
</view-state>
</flow>

One simple way is to define a reset() method on your Customer class and call that in whichever <view-state> <on-entry> (like "thanks") or <transition> ("restart") makes sense.

Have you tried:
<view-state id="helloworld" view="input.jsp" model="customer" popup="true">
<on-entry>
<set name="flowScope.costumer" value="new org.uftwf.domain.Customer()" />
</on-entry>
<transition on="submit" to="preview" />
<transition on="cancel" to="thanks" validate="false"/>
</view-state>

You can do an external redirect to do same flow. Here is an example.
<var name="customer" class="org.uftwf.domain.Customer"/>
<view-state id="thanks">
<transition on="restart" to="doRestart"/>
</view-state>
<end-state id="doRestart" view="externalRedirect:nameOfThisFlow"></end-state>

Related

Spring upgrade on-start equivalent

I am trying to use Spring Webflow 2.3.2.
I have a legacy web flow similar to:
<start-state id-ref="A" />
<action-state id="A">
<action bean="B" />
<transition on="success" to="T" />
</action-state>
<action-state id="T">
...
</action-state>
The equivalent code that I am writing for Spring Webflow 2.3.2 is:
<on-start>
<evaluate expression="B" />
</on-start>
<action-state>
<transition on="success" to="T" />
</action-state>
<action-state id="T">
...
</action-state>
Clearly I am missing the string to connect the initial evaluation to the transition. How can I connect the two?
Let's assume your flow starts with a form
Form
<input type="hidden" name="_flowExecutionKey" value="${flowExecutionKey}" />
<input type="hidden" name="_eventId" value="send" /> //This starts the flow
Flow.xml
<view-state id="showForm" model="formModel">
<transition on="send" to="send2"></transition>
</view-state>
<action-state id="send2">
<evaluate expression="..."></evaluate>
<transition to="send3"></transition>
</action-state>
<view-state id="send3">
</view-state>
Based on comments from #M. Deinum, I realize that a Spring upgrade does not affect Spring webflow usage in the application. The new webflow which finally worked is:
<action-state id="A">
<evaluate expression="B" />
<transition on="success" to="T" />
</action-state>
<action-state id="T">
...
</action-state>
All that was required is to replace <action bean="B" /> with <evaluate expression="B" /> and add an id to the <action-state>.

Spring webflow start flow and event from a external link (p.e. confirmation email)

App send a confirmation email when an user register a new account. That email contain a link which must start a specific eventId for a flow which got several events. Link redirect properly to flow but I don't get this flow start on confirmation eventId, always it starts on first eventId called login.
url: ....aio/spring/login?_eventId=confirmation&code=cmFmYWVscnVpenRhYmFyZXNAZ21haWwuY29t
I have read possible solutions using externalRedirect and others commands written on flow rules but I don't need that way.
Login flow
<view-state id="login" view="login.xhtml">
<transition on="entry" to="connect"/>
<transition on="recoveryPass" to="recovery" />
</view-state>
<action-state id="connect">
<evaluate expression="login.connect()" />
<transition on="yes" to="finish" />
<transition on="no" to="login" />
</action-state>
<view-state id="recovery" view="recovery.xhtml" model="loginFields">
<transition on="return" to="login" />
<transition on="sendPass" to="recoveryPass" />
</view-state>
<action-state id="recoveryPass">
<evaluate expression="login.recoveryPass()" />
<transition on="yes" to="login" />
<transition on="error" to="error" />
</action-state>
<action-state id="confirmation">
<on-entry>
<set name="confirmationCode" value="requestParameters.code" type="string" />
</on-entry>
<evaluate expression="login.confirmation(confirmationCode)" />
<transition on="yes" to="confirmationOk" />
<transition on="no" to="noUserFound" />
<transition on="error" to="error" />
</action-state>
<view-state id="confirmationOk" view="confirmation.xhtml">
<!--<set name="viewScope.code" value="found" /> -->
</view-state>
<view-state id="noUserFound" view="confirmation.xhtml">
<!--<set name="viewScope.code" value="notFound" />-->
</view-state>
<end-state id="finish" />
<subflow-state id="error" subflow="error">
</subflow-state>
eventId is not same as state id - eventId is required for transitioning in a current state(action/view) of an ongoing flow and not for launching a new flow. The reason why you are landing on login could be that login-flow xml might be mapped to /login mapping and view-state with id login is the start state(by default first state in flow definition file will be start state) of login flow. You need to have a decision state as on start state to redirect to a state based on some conditional parameter whether you need to go to login state or confirmation state.
EDIT:
For example, if code parameter is present only when request is from email, then your flow definition could be like:
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<input name="code" type="java.lang.String">
<decision-state id="checkFlowStart">
<if test="code == null" then="login" else="confirmation"/>
</decision-state>
<view-state id="login" view="login.xhtml">
<transition on="entry" to="connect"/>
<transition on="recoveryPass" to="recovery" />
</view-state>
....
Have you considered make a particular state (implementation in that state) as a different flow. When using in the main flow it can be used as sub-flow and whereas it gives you a freedom to be launched as a separate flow as well.
The parameter from the main flow can be passed as inputs to the sub-flow. Below is an example I am trying to explain myself
Main Flow
<view-state></view-state>
<action-state></action-state>
<!-- This is the re-usable flow which can be used within this flow and can be
launched independently -->
<sub-flow>
<input/>
</sub-flow>
Another spring flow - can be plugged in from another flow or used independently
<input/>
<action-state></action-state>
<end-state></end-state>

Jasig CAS, how to redirect view after successful login

in my scenario, user need to change password before first times login.
how to make cas redirect url before login page?
in my app, i already define my own controller to handle certain URL, and also implement my own custom authentication under authenticationHandlers.
now only left this mechanism reset password before login.
can anyone give me a clue how to do it?
my maven project file:
https://qeyg6a.bay.livefilestore.com/y2msQyFts1aCgGkYR4ybMses2mGFVNJBLCX_xQ9pDi8gVdnhx9P5ibVVBlg6p6T9RM3BAN8Qcz2Izo5JGdQzbyU-kSMTIwBVOYhkdG1RvsyE50/cas-server-webapp.rar?download&psid=1
My login-webflow.xml
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to Jasig under one or more contributor license
agreements. See the NOTICE file distributed with this work
for additional information regarding copyright ownership.
Jasig licenses this file to you 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 the following location:
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.
-->
<flow xmlns="http://www.springframework.org/schema/webflow"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/webflow
http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<var name="credentials" class="org.jasig.cas.authentication.principal.UsernamePasswordCredentials" />
<on-start>
<evaluate expression="initialFlowSetupAction" />
</on-start>
<decision-state id="ticketGrantingTicketExistsCheck">
<if test="flowScope.ticketGrantingTicketId != null" then="hasServiceCheck" else="gatewayRequestCheck" />
</decision-state>
<decision-state id="gatewayRequestCheck">
<if test="requestParameters.gateway != '' and requestParameters.gateway != null and flowScope.service != null" then="gatewayServicesManagementCheck" else="serviceAuthorizationCheck" />
</decision-state>
<decision-state id="hasServiceCheck">
<if test="flowScope.service != null" then="renewRequestCheck" else="viewGenericLoginSuccess" />
</decision-state>
<decision-state id="renewRequestCheck">
<if test="requestParameters.renew != '' and requestParameters.renew != null" then="serviceAuthorizationCheck" else="generateServiceTicket" />
</decision-state>
<!-- Do a service authorization check early without the need to login first -->
<action-state id="serviceAuthorizationCheck">
<evaluate expression="serviceAuthorizationCheck"/>
<transition to="generateLoginTicket"/>
</action-state>
<!--
The "warn" action makes the determination of whether to redirect directly to the requested
service or display the "confirmation" page to go back to the server.
-->
<decision-state id="warn">
<if test="flowScope.warnCookieValue" then="showWarningView" else="redirect" />
</decision-state>
<!--
<action-state id="startAuthenticate">
<action bean="x509Check" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="warn" to="warn" />
<transition on="error" to="generateLoginTicket" />
</action-state>
-->
<!--
LPPE transitions begin here: You will also need to
move over the 'lppe-configuration.xml' file from the
'unused-spring-configuration' folder to the 'spring-configuration' folder
so CAS can pick up the definition for the bean 'passwordPolicyAction'.
-->
<action-state id="passwordPolicyCheck">
<evaluate expression="passwordPolicyAction" />
<transition on="showWarning" to="passwordServiceCheck" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="viewLoginForm" />
</action-state>
<action-state id="passwordServiceCheck">
<evaluate expression="sendTicketGrantingTicketAction" />
<transition to="passwordPostCheck" />
</action-state>
<decision-state id="passwordPostCheck">
<if test="flowScope.service != null" then="warnPassRedirect" else="pwdWarningPostView" />
</decision-state>
<action-state id="warnPassRedirect">
<evaluate expression="generateServiceTicketAction" />
<transition on="success" to="pwdWarningPostView" />
<transition on="error" to="generateLoginTicket" />
<transition on="gateway" to="gatewayServicesManagementCheck" />
</action-state>
<end-state id="pwdWarningAbstractView">
<on-entry>
<set name="flowScope.passwordPolicyUrl" value="passwordPolicyAction.getPasswordPolicyUrl()" />
</on-entry>
</end-state>
<end-state id="pwdWarningPostView" view="casWarnPassView" parent="#pwdWarningAbstractView" />
<end-state id="casExpiredPassView" view="casExpiredPassView" parent="#pwdWarningAbstractView" />
<end-state id="casMustChangePassView" view="casMustChangePassView" parent="#pwdWarningAbstractView" />
<end-state id="casAccountDisabledView" view="casAccountDisabledView" />
<end-state id="casAccountLockedView" view="casAccountLockedView" />
<end-state id="casBadHoursView" view="casBadHoursView" />
<end-state id="casBadWorkstationView" view="casBadWorkstationView" />
<!-- LPPE transitions end here... -->
<action-state id="generateLoginTicket">
<evaluate expression="generateLoginTicketAction.generate(flowRequestContext)" />
<transition on="generated" to="viewLoginForm" />
</action-state>
<view-state id="viewLoginForm" view="casLoginView" model="credentials">
<binder>
<binding property="username" />
<binding property="password" />
</binder>
<on-entry>
<set name="viewScope.commandName" value="'credentials'" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="realSubmit">
<evaluate expression="authenticationViaFormAction.doBind(flowRequestContext, flowScope.credentials)" />
</transition>
</view-state>
<action-state id="realSubmit">
<evaluate expression="authenticationViaFormAction.submit(flowRequestContext, flowScope.credentials, messageContext)" />
<!--
To enable LPPE on the 'warn' replace the below transition with:
<transition on="warn" to="passwordPolicyCheck" />
CAS will attempt to transition to the 'warn' when there's a 'renew' parameter
and there exists a ticketGrantingId and a service for the incoming request.
-->
<transition on="warn" to="warn" />
<!--
To enable LPPE on the 'success' replace the below transition with:
<transition on="success" to="passwordPolicyCheck" />
-->
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="generateLoginTicket" />
<transition on="accountDisabled" to="casAccountDisabledView" />
<transition on="mustChangePassword" to="casMustChangePassView" />
<transition on="accountLocked" to="casAccountLockedView" />
<transition on="badHours" to="casBadHoursView" />
<transition on="badWorkstation" to="casBadWorkstationView" />
<transition on="passwordExpired" to="casExpiredPassView" />
</action-state>
<action-state id="sendTicketGrantingTicket">
<evaluate expression="sendTicketGrantingTicketAction" />
<transition to="serviceCheck" />
</action-state>
<decision-state id="serviceCheck">
<if test="flowScope.service != null" then="generateServiceTicket" else="viewGenericLoginSuccess" />
</decision-state>
<action-state id="generateServiceTicket">
<evaluate expression="generateServiceTicketAction" />
<transition on="success" to ="warn" />
<transition on="error" to="generateLoginTicket" />
<transition on="gateway" to="gatewayServicesManagementCheck" />
</action-state>
<action-state id="gatewayServicesManagementCheck">
<evaluate expression="gatewayServicesManagementCheck" />
<transition on="success" to="redirect" />
</action-state>
<action-state id="redirect">
<evaluate expression="flowScope.service.getResponse(requestScope.serviceTicketId)" result-type="org.jasig.cas.authentication.principal.Response" result="requestScope.response" />
<transition to="postRedirectDecision" />
</action-state>
<decision-state id="postRedirectDecision">
<if test="requestScope.response.responseType.name() == 'POST'" then="postView" else="redirectView" />
</decision-state>
<!--
the "viewGenericLogin" is the end state for when a user attempts to login without coming directly from a service.
They have only initialized their single-sign on session.
-->
<end-state id="viewGenericLoginSuccess" view="casLoginGenericSuccessView" />
<!--
The "showWarningView" end state is the end state for when the user has requested privacy settings (to be "warned") to be turned on. It delegates to a
view defines in default_views.properties that display the "Please click here to go to the service." message.
-->
<end-state id="showWarningView" view="casLoginConfirmView" />
<end-state id="postView" view="postResponseView">
<on-entry>
<set name="requestScope.parameters" value="requestScope.response.attributes" />
<set name="requestScope.originalUrl" value="flowScope.service.id" />
</on-entry>
</end-state>
<!--
The "redirect" end state allows CAS to properly end the workflow while still redirecting
the user back to the service required.
-->
<end-state id="redirectView" view="externalRedirect:${requestScope.response.url}" />
<end-state id="viewServiceErrorView" view="viewServiceErrorView" />
<end-state id="viewServiceSsoErrorView" view="viewServiceSsoErrorView" />
<global-transitions>
<!-- CAS-1023 This one is simple - redirects to a login page (same as renew) when 'ssoEnabled' flag is unchecked
instead of showing an intermediate unauthorized view with a link to login page -->
<transition to="viewLoginForm" on-exception="org.jasig.cas.services.UnauthorizedSsoServiceException"/>
<transition to="viewServiceErrorView" on-exception="org.springframework.webflow.execution.repository.NoSuchFlowExecutionException" />
<transition to="viewServiceErrorView" on-exception="org.jasig.cas.services.UnauthorizedServiceException" />
</global-transitions>
</flow>
My Solution
create
resetPasswordView.(class)=org.springframework.web.servlet.view.JstlView
resetPasswordView.url=/WEB-INF/view/jsp/default/ui/ResetPassword.jsp
in default_views.properties
create
public class testflowAction extends AbstractAction{
private String origin;
#Override
protected Event doExecute(RequestContext rc) throws Exception {
origin = rc.getRequestParameters().get("service");
if(true)
return result("setNew");
else
return result("setNew");
}
}
in login-wenflow.xml, replace
<transition on="success" to="sendTicketGrantingTicket" /> with
<transition on="success" to="customFlowCheck" />
also add
<action-state id="customFlowCheck">
<evaluate expression="customCheckAction" />
<transition on="ok" to="sendTicketGrantingTicket" />
<transition on="setNew" to="resetPasswordView" />
</action-state>
and
<end-state id="resetPasswordView" view="resetPasswordView" />
We had similar problem in our company. The user had to be redirected to another page after successful login.
We decided to add another outcome from action state id "realSubmit", that would indicate continuation of flow.
I would suggest that you first let user to preform login, so he confirms that he's the right user. To do so you need to change submit method in your AuthenticationViaFormAction class. Then you need to destroy TGT, and redirect user to your custom Flow Action that reset password.
It would look something like this:
public final String submit(final RequestContext context, final Credentials credentials, final MessageContext messageContext) throws Exception {
(...)
//Login user, so we know that provided password is correct.
WebUtils.putTicketGrantingTicketInRequestScope(context, this.centralAuthenticationService.createTicketGrantingTicket(credentials));
// do custom logic, that check if it is first login attept:
if( firstLoginAttempt(credentials) {
//destroy TGT, so user won't stay logged-in after interrupting next step
centralAuthenticationService.destroyTicketGrantingTicket(ticketGrantingTicketId);
//redirect user to another action that will handle changing password
return "changePassRequired"
}
I don't know how you check if the password IS initial password, so I leave it to you. Next add transition in login-webflow.xml outcome:
<action-state id="realSubmit">
<evaluate expression="authenticationViaFormAction.submit(flowRequestContext,flowScope.credentials, messageContext)" />
<transition on="warn" to="warn" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="error" to="generateLoginTicket" />
<transition on="accountDisabled" to="casAccountDisabledView" />
<transition on="mustChangePassword" to="casMustChangePassView" />
<transition on="accountLocked" to="casAccountLockedView" />
<transition on="badHours" to="casBadHoursView" />
<transition on="badWorkstation" to="casBadWorkstationView" />
<transition on="passwordExpired" to="casExpiredPassView" />
<transition on="changePassRequired" to="changePasswordPrepare" />
</action-state>
And add custom action, you might as well use the same form as in login action so break it into two states, for example:
<view-state id="changePasswordPrepare" view="resetPasswordView">
<on-entry>
<evaluate expression="resetPasswordAction.setupForm(flowRequestContext)" />
</on-entry>
<transition on="submit" bind="true" validate="true" to="resetPasswordRealAction">
</transition>
</view-state>
<action-state id="resetPasswordRealAction">
<evaluate expression="resetPasswordAction.submit(flowRequestContext)" />
<transition on="success" to="sendTicketGrantingTicket" />
<transition on="again" to="changePasswordPrepare" />
<transition on="error" to="generateLoginTicket" />
</action-state>
resetPasswordAction.setupForm will prepare custom view with reset password form if it's needed
resetPasswordAction.submit will get variables from your form, and preform changing password.
snippet of class might look somewhat like this:
public class resetPasswordAction extends FormAction {
public final String submit(final RequestContext context) throws Exception {
final Credentials previousCredentials = context.getFlowScope().get("loginCredentials");
final String oldPass = context.getConversationScope().get("pass");
final String newPassword = context.getConversationScope().get("password");
final String newPasswordagain = context.getConversationScope().get("passwordagain");
if (!newPassword.equals(newPasswordagain) {
return "again";
}
if ( everythingIsOk(previousCredentials.getUsername(),oldPass,newPassword)) {
Credentials credentials = new UsernamePasswordCredentials();
credentials.setUsername(previousCredentials.getUsername());
credentials.setPassword(newPassword);
WebUtils.putTicketGrantingTicketInRequestScope(context, this.casService.createTicketGrantingTicket(credentials));
return "success";
}
return "error";
}
public Event setupForm(RequestContext context) throws Exception {
final Locale locale = LocaleContextHolder.getLocale();
context.getFlashScope().put("language",locale);
context.getFlashScope().put("service",context.getRequestScope().get("service");
}
}
You might as well be interested in CAM - CAS account management module, although it seems to be in early, theoretical phase.
You might as well be interested in cas 4, now in beta release as it seems to contain more elastic authentication logic. One, that you could use to do after login password reset.
I have running a CAS Server, seems that you should customize through Spring WebFlow. Follow my glue:
<action-state id="validateForgotPassword">
<on-entry>
<set name="flashScope.pmTask" value="'forceChangePassword'"/>
</on-entry>
<evaluate expression="forgotPasswordTokenValidateAction" />
<transition on="success" to="passwordManager"/>
<transition on="error" to="badForgotPasswordTokenView" />
</action-state>
You can add a JS script in succes login witch redirect a from your specific URL
document.location.href ="http://www...."
You can personnalized your processing JS.

Invalid _eventId in spring webflow

Is there any way to handle unexisting eventIds or absence of eventId parameter in spring webflow?
e.g. for this webflow
<flow xmlns="http://www.springframework.org/schema/webflow" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/webflow http://www.springframework.org/schema/webflow/spring-webflow-2.0.xsd">
<view-state id="welcome">
<transition on="goForward" to="nextStep"/>
</view-state>
<view-state id="nextStep">
<transition on="endFlow" to="finishStep" />
</view-state>
<end-state id="finishStep"/>
<global-transitions>
<transition on="cancel" to="finishStep"/>
</global-transitions>
</flow>
How to handle requests with param like _eventId=unexistingAction or requests with no _eventId param ?
This will normally produce a page with stack trace...
no transition found on occurence of event in state of flow...
This is how you can handle transitions that don't exist:
<global-transitions>
<transition on-exception="org.springframework.webflow.engine.NoMatchingTransitionException" to="handlingViewState">
<evaluate expression="handlingBean.handle(flowExecutionException)"></evaluate>
</transition>
</global-transitions>

String var assigned to view-state?

I am not sure if this is possible in webflow 2.2.1
flow.xml
<view-state id="flowId1" model="flowModel1" view="/WEB-INF/templates/Flow_Form/form1.jsp">
<set attribute="strVar" value="${'someStringVar'}" />
<transition on="step1" to="step1Action" />
</view-state>
jsp
<h1>${strVar}</h1>
Essentially, I'd like to assign a String var that will change value accross individual view-states, that is set in the flow.xml..
Are there any other simple recommended approaches to this?
Thanks
got it.. :)
<view-state id="flowId1" model="flowModel1" view="/WEB-INF/templates/Flow_Form/form1.jsp">
<on-entry>
<evaluate expression="'someStringVar'" result="flowScope.strVar"/>
</on-entry>
<transition on="step1" to="step1Action" />
</view-state>

Resources