Wicket setResponsePage after a session timeout - redirect

This is the onSubmit() method of my Wicket 1.5 application's login page:
#Override
public void onSubmit() {
super.onSubmit();
User theUser = loginForm.getModelObject();
/* call a DAO function to check the user's credentials */
if(/* DAO call succeeds*/) {
MyCustomeSession authSession = (MyCustomSession)Session.get();
authSession.success("Welcome, " + theUser.getFullName());
setResponsePage(new HomePage());
}
else {
loginForm.error("Username or password was incorrect");
}
}
This works fine for initial login, and for logout then log back in, in that the next thing seen is the Home page with the Welcome message.
Upon session timeout, this application redirects the user back to this same login page, with a feedback message "Your session has expired blah, blah" and allows user to enter username and password again. If the user does this, the login is successful: menus that were hidden become visible, the Welcome message shows, etc.
However, the page shown remains the Login page, with additional feedback messages for the required username and password (even though both had been entered and the login succeeded). Here is a clipped screen shot:
Is this some weird thing with Wicket? Is there a fix or a work-around?

Well, it seems that a page which is a application.setPageExpiredErrorPage(page) cannot subsequently do a setResponsePage(new HomePage()); but must instead do setRepsonsePage(HomePage.class);.
Meanwhile, when this LoginPage is just a normal page, i.e. on initial login or after a logout, it must use setResponsePage(new HomePage()); This doesn't make much sense, but that seems to be the Wicket way.
So I added a boolean value to the LoginPage constructor called isTimeout and call one or the other version of setResponsePage accordingly.

Related

Keycloak : implement "reset password" (as admin) flow same as "forgot password" (as user)

I would like to implement this authentication flow in Keycloak:
A user creates an account by typing only his email
The user is logged in and can access my service
2'. At the same time, an email is sent to him, allowing him to "finalize" his account
The user leaves his session -> to reuse my service, he must click in the received email
By clicking in the received email, the user defines his first password
The user is then logged in automatically (without going through a login page).
The objective of this flow is to be the simplest, to hook users who are not used to webapps.
The implementation I would do:
Create an account without password request: I customize the Keycloak Registration flow by disabling the Password Validation and Profile Validation rules
Programmatically, in my webapp, at the first connection of a user, via the REST Admin API, I trigger the email action UPDATE_PASSWORD
I get something that works, but:
A. The link received by email redirects to an intermediary page confirming the execution of actions ("Perform the following action (s)") - (similar to Keycloak Implement Reset password flow same as forgot password flow)
B. The user is then redirected to a login page, and not directly connected to the application.
When, as a normal user, I trigger a reset password request (through 'forget password' feature), the process is the one I want: by clicking on the email link, I go directly to the page allowing me to enter and confirm a new password, then I'm authenticated.
My question: Do you see a way to implement this 'simplified' flow?
My keycloak version : 11.0.2
Thank you !
I could remove the "info.ftl" page display, customizing the "ExecuteActionsActionTokenHandler", as explained here :
action-token-spi
You have to create a file :
src/main/resources/META-INF/services/org.keycloak.authentication.actiontoken.ActionTokenHandlerFactory
containing the name of the class you want to use instead :
com.example.ExecuteActionTokenHandlerFactory
Then you create that class com.example.ExecuteActionTokenHandlerFactory with the following code :
public class ExecuteActionTokenHandlerFactory extends ExecuteActionsActionTokenHandler {
#Override
public Response handleToken(ExecuteActionsActionToken token, ActionTokenContext<ExecuteActionsActionToken> tokenContext) {
AuthenticationSessionModel authSession = tokenContext.getAuthenticationSession();
String redirectUri = RedirectUtils.verifyRedirectUri(tokenContext.getUriInfo(), token.getRedirectUri(),
tokenContext.getRealm(), authSession.getClient());
if (redirectUri != null) {
authSession.setAuthNote(AuthenticationManager.SET_REDIRECT_URI_AFTER_REQUIRED_ACTIONS, "true");
authSession.setRedirectUri(redirectUri);
authSession.setClientNote(OIDCLoginProtocol.REDIRECT_URI_PARAM, redirectUri);
}
token.getRequiredActions().stream().forEach(authSession::addRequiredAction);
UserModel user = tokenContext.getAuthenticationSession().getAuthenticatedUser();
// verify user email as we know it is valid as this entry point would never have gotten here.
user.setEmailVerified(true);
String nextAction = AuthenticationManager.nextRequiredAction(tokenContext.getSession(), authSession, tokenContext.getClientConnection(), tokenContext.getRequest(), tokenContext.getUriInfo(), tokenContext.getEvent());
return AuthenticationManager.redirectToRequiredActions(tokenContext.getSession(), tokenContext.getRealm(), authSession, tokenContext.getUriInfo(), nextAction);
}
}
Actually it is the same implementation as the upper class, except we removed the following part :
if (tokenContext.isAuthenticationSessionFresh()) {
...
}
which means that if the user did not have a session, which happens when the user is reseting his password, he is redirected to that "info.ftl" page.
As a workaround for problem A, I customize info.ftl template page. I add an ugly inline script to click on the link, redirecting automatically to the update password page.
<#import "template.ftl" as layout>
(...)
<#elseif actionUri?has_content>
<p><a id="yolo" href="${actionUri}">${kcSanitize(msg("proceedWithAction"))?no_esc}</a></p>
<script>document.getElementById('yolo').click()</script>
(...)
It'll do the job until I found a cleaner solution.
At the moment, B problem remains.

Handle session time out with Wicket

I'm working on a wicket legacy-project and i'm trying to fix a bug with the session time-out.
Basically I'd like to have a redirect to a customed error page after session times out.
This is what I did:
web.xml :
<session-config>
<session-timeout>1</session-timeout>
</session-config>
in the application class:
#Override
public void init() {
super.init();
getApplicationSettings().setPageExpiredErrorPage(ErrorMessagePage.class);
This is not working. I mean after session time out, nothing happens.
What am I doing wrong?
EDIT 04.05.20
Based on the feedback from Martin I tried to implement a session validaty checker:
public class SessionValidityChecker implements IRequestCycleListener {
#Override
public void onBeginRequest(RequestCycle cycle) {
HttpServletRequest request = (HttpServletRequest) cycle.getRequest().getContainerRequest();
boolean sessionValid = request.isRequestedSessionIdValid();
if (!sessionValid) {
cycle.setResponsePage(SessionExpiredPage.class);
}
}
}
and in Application.class
public void init() {
super.init();
getRequestCycleListeners().add(new SessionValidityChecker());
}
Also what I may should have specified in my first post is that I use the wicket SignInPanel for authentification. After timeout, I'd like the user to be logged out and redirected to a specific page.
This is what I've tried with the above code, but after session time out, no redirect happens. Even worst, the user is still signed in. What am I missing?
You are mistaking page expiration with session expiration.
Stateful pages are stored in a PageStore (disk) and the store may grow up to some predefined size. Once this size is reached the oldest page is removed to make room for the newest one.
If your user uses the browser Back button many times at some point Wicket will throw PageExpiredException for the deleted page.
In your case when the session expires usually the web server (e.g. Tomcat) will just create a new one. If your application has authentication enabled then it will detect that there is no authenticated user in the new http session and most probably will redirect the user to the login page.
If there is no authentication in place then Wicket will create a new instance of the requested page and render it. You can change this by changing PageSettings#recreateBookmarkablePagesAfterExpiry to false
(see https://github.com/apache/wicket/blob/79f63f66eb588a5d69e9feff7066f1244f61f387/wicket-core/src/main/java/org/apache/wicket/settings/PageSettings.java#L46)
You may use javax/servlet/http/HttpServletRequest.html#isRequestedSessionIdValid() method to find whether the the request came with an expired JSESSIONID cookie/url. If it is false then the web server just created a new HttpSession. You can do the check in Wicket's IRequestCycleListener#onBeginRequest()

Serenity-JS, step function timed out

I'm in my first steps with Serenity and I've been stuck for 2 days with this problem.
I've got:
navigate to the login page
write the username
write the password
click the login button
and then, in the Step where the web change from login page to welcome page I want to validate if one of the buttons of the welcome page is present.
In the transition from login page to welcome appears a Loading Splash and then after a few seconds appears the Welcome page.
This is my scenario
Given that Sarah navigates to the access page
When she enters email as xxxx#yyyyy.com
And she enters password as zzzzzz
And she clicks the button Login
Then she should navigates to the Empresa JMM Enterprise welcome page
I got the error in the last step (Then).
This is my code for the step where clicks the button Login:
this.When(/^prueba pulsar boton (.*?)$/, function (buttonText: string) {
return this.stage.theActorInTheSpotlight().attemptsTo(
ClickIniciarSesion.click(buttonText)
)
});
And this is the code where I validate if the button is present
this.Then(/^s?he should navigates to (.*?) Enterprise welcome page$/, function (enterpriseName: string) {
return this.stage.theActorInTheSpotlight().attemptsTo(
See.if(WebElement.of(Header.WelcomeButton), el => expect(el).to.eventually.be.displayed)
)
I see the execution and seconds before of the timeout I see the welcome page loaded and the button. I don't know why the error is timeout and not that the driver can't find the element.
I think that I have the solution. In the transition from the login page to the welcome page appears one page of loading. I think that serenity is "looking" in this loading page.
The test passed OK disabling the Angular synchronisation, doing the wait manually and enabling the synchronisation.
this.Then(/^s?he waits the enterpise data load$/, function () {
return this.stage.theActorInTheSpotlight().attemptsTo(
UseAngular.disableSynchronisation()
);
});
this.Then(/^s?he should navigates to the (.*?) Enterprise welcome page$/, function (enterpriseName: string) {
return this.stage.theActorInTheSpotlight().attemptsTo(
Wait.until(Header.EnterpriseName, Is.visible()),
See.if(Header.EnterpriseName_Text, el => expect(el).to.eventually.be.equal(enterpriseName)),
UseAngular.enableSynchronisation()
)
});
I don't know if is the better solution.

GWT: A way to cancel PlaceChangeEvent?

I'm using Activity/Place in my GWT project, if current user is not logged in, when he navigates to some Place, the user will be redirect to login page, if the user has logged in, then he will be taken to that Place. How to implement this logic efficiently?
I tried to hook PlaceChangeRequestEvent:
eventBus.addHandler(PlaceChangeRequestEvent.TYPE,new PlaceChangeRequestEvent.Handler() {
#Override
public void onPlaceChangeRequest(PlaceChangeRequestEvent event) {
Place newPlace = event.getNewPlace();
if (newPlace instanceof MyProtectedPlace && userNotLoggedIn()) {
event.goTo(new LoginPlace());
}
}
});
Unfortunately it does not work since the ongoing request for MyProtectedPlace is not cancelled.
Yes I could check this when user are about to navigation away from current place, but this will not be efficient as the check logic will scattered throughout the program.
Thanks.
You can do it a little bit differently I think. Let's say that you want a place called SecuredPlace to be accessible only after login. You have a corresponding SecuredActivity.
What you can do is, when you start your SecuredActivity, you check if your user is logged in. If not you do placeController.goTo(new LoginPlace ()).
If the user is logged in then you continue. As the start is called by the framework there is no way to skip this step which in my opinion makes it secured enough.
But you should implement your security on network calls to your backend not on places. Every time you call the backend, you check that user is authenticated and has the right credentials. If not you can intercept the callback, check that it is a 403 error and then redirect automatically to your login page. Because if your backend calls are not secured, securing your places is useless.

How do I pop up a dialog from Facebook to request the user to "allow" for open graph?

In my Facebook application, I am requesting 3 scopes: email,publish_stream,publish_action
I am using the FB.login function. These 2 steps pop up.
When the user clicks "Cancel" in the first step, FB.login will show "status: unknown" as the response object.
However, when user clicks cancel in the second step, FB.login shows it as "status:connected" and treats it as if the user accepted everything.
I recently learned that you can check if the user allowed the 2nd step using
FB.api('/me/permissions', function (response) {
console.log(response);
} );
My question is...knowing that the user denied the open graph step, how can I pop that dialog up again?
You are correct, the 2nd stage of the Auth Dialog is optional, the user does not have to accept all of the extended permissions you ask for, or any of them, as it states in the Permissions sections of the auth dialog documentation:
The user will be able to remove any of these permissions, or skip this
stage entirely, which results in rejecting every extended permission
you've requested. Your app should be able to handle revocation of any
subset of extended permissions for installed users.
The best approach I think is to have your app manage with what the user accepts, but if you HAVE to have the permission(s) in the optional stage (extended permissions) then this is what you can do:
FB.init({
....
});
var requiredPermissions = ["email", "publish_stream", "publish_action"];
function checkPermissions(response) {
var ok = true;
if (!response.data || response.data.length != 1)
ok = false;
else for (var perm in requiredPermissions) {
if (!(perm in response.data[0])) {
ok = false;
break;
}
}
if (!ok)
login();
else
console.log("Hey there user who granted all the required permissions");
}
function loginCallback(response) {
if (response.authResponse) {
FB.api("/me/permissions", checkPermissions);
}
else {
console.log("User cancelled login or did not fully authorize.");
}
}
functoin login() {
FB.login(loginCallback, { scope: requiredPermissions.join(",") });
}
I haven't tested this code, it's a nudge in the right direction though.
Also, this code will go on forever until the user accepts all permissions or just gives up, you should somehow let him know that you need those permissions and about to send him for the auth dialog again.
Edit
I keep forgetting to include this with my answers:
Calling FB.login opens a new pop-up window, and browsers usually blocks that unless it's a result of a user action, as it says in the docs:
Calling FB.login results in the JS SDK attempting to open a popup
window. As such, this method should only be called after a user click
event, otherwise the popup window will be blocked by most browsers.
It also says there:
If you need to collect more permissions from users who have already
authenticated with your application, call FB.login again with the
permissions you want the user to grant. In the authentication dialog,
the user will only ever be asked for permissions they have not already
granted.
Which means that it's a way to do what you want, the popup probably did not open because it was blocked by your browser.
You need to modify the code I gave you so that every time you call the login function it's after the user interacted with your page, i.e.: display a message saying "the application needs this and that permissions, please click this button to continue" which then will call the login function.