Grails 1.3.5: Updating user table when session is destroyed - grails

I have HttpSessionListener to listen for when sessions are created and destroyed. I have a user domain which has loggedIn boolean column which I update whenever user logs in or logs out which I use for admin management. I also store the session Id in the database.
I also want to update the loggedIn column whenever the session is destroyed. Below is the code written in the sessionDestroyed method.
def user = User.findByUserSessionId(session.getId())
if(user) {
user.setLoggedIn(false)
user.setUserSessionId("SESSION DESTROYED")
user.save(flush: true)
}
The problem is the user table never gets updated.
Below is the error reported in log file:
[2010-10-16 11:45:07.781] ERROR core.ContainerBase.[Tomcat].[localhost].[/SAMPLE] Session event listener threw exception
org.hibernate.HibernateException: No Hibernate Session bound to thread, and configuration does not allow creation of non-transactional one here
at org.springframework.orm.hibernate3.SpringSessionContext.currentSession(SpringSessionContext.java:63)
at org.hibernate.impl.SessionFactoryImpl.getCurrentSession(SessionFactoryImpl.java:574)
at org.codehaus.groovy.grails.orm.hibernate.validation.HibernateDomainClassValidator.validate(HibernateDomainClassValidator.java:66)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractSavePersistentMethod.doInvokeInternal(AbstractSavePersistentMethod.java:129)
at org.codehaus.groovy.grails.orm.hibernate.metaclass.AbstractDynamicPersistentMethod.invoke(AbstractDynamicPersistentMethod.java:59)
at sun.reflect.GeneratedMethodAccessor500.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite$PojoCachedMethodSite.invoke(PojoMetaMethodSite.java:188)
at org.codehaus.groovy.runtime.callsite.PojoMetaMethodSite.call(PojoMetaMethodSite.java:52)
at org.codehaus.groovy.runtime.callsite.AbstractCallSite.call(AbstractCallSite.java:132)
at org.codehaus.groovy.grails.plugins.orm.hibernate.HibernatePluginSupport$_addBasicPersistenceMethods_closure71.doCall(HibernatePluginSupport.groovy:812)
Can I know how the proper way to update the user table when session is destroyed.
Thank You.
Jay Chandran.

try
User.withTransaction{ txStatus -> .... }
or
User.withSession{ session - > .... }
or perhaps inject a service that does what you need it to do, as service methods should have transactions by default.
edit -- usually I don't go this far, but Im in a good mood today...something like the following should work. You should really read the grails documentation or buy a book...
User.withTransaction( txStatus ->
def user = User.findByUserSessionId(session.getId())
if(user) {
user.setLoggedIn(false)
user.setUserSessionId("SESSION DESTROYED")
user.save(flush: true)
}
}

Related

Grails spring security save login time in user domain

Using this link if we register a call back in grails, how to access springSecurityService in plain groovy/java class, so that we can get the current user domain class and save the login time?
Update:
I have done this using the below:
appCtx.springSecurityService.currentUser.id
If you are using the callback closures you can get the information from the AuthenticationSuccessEvent.
grails.plugin.springsecurity.onAuthenticationSuccessEvent = { e, appCtx ->
// handle AuthenticationSuccessEvent
println "User id ${e.authentication.principal.id} was authenticated"
}

Persisting Groovy models within Spring Security Event Listener

I've added an onAuthenticationSuccessEvent to my config.groovy in order to try add a Login model to a list within the users User model. The problem i'm having, is that the event listener is a closure, and as such has no HibernateSession or access to state outside of the closure.
I know its possible to bind a HibernateSession by doing something like this:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction{
login = new Login()
}
}
The problem is that the Login belongs to a User, and needs to have the User defined in its constructor. How do I pass the User into the closure?
I want to do something like:
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
Login.withTransaction{ user ->
login = new Login(user: user)
}
but dont know how to pass the specific user in.
You can't do Login.withTransaction{ user -> since the parameter of the closure you pass to the withTransaction method takes an argument which is the TransactionStatus - you don't get to specify the type, just the name.
But I'm not sure why you're seeing what you're seeing with regard to closure scope. The cool thing about closures is that they enclose their scope, hence the name. So variables outside of the closure are always available inside the closure.
You would want the User to be loaded inside the withTransaction block though so it's in the same Hibernate session as the transaction's, otherwise the Login save will fail since the User will be disconnected. And use load(), not read() since you're just setting the foreign key in the Login, so load() works best since it just creates a proxy and doesn't hit the database, but makes the id available for the Login save. For the same reason you'd want to avoid using the currentUser method since it's just a shortcut for User.get().
Having said all that, this worked for me:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction { status ->
new Login(user: appCtx.springSecurityService.currentUser).save()
}
}
but since the e variable is an InteractiveAuthenticationSuccessEvent you're better off using this:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
Login.withTransaction { status ->
def user = User.load(e.authentication.principal.id)
new Login(user: user).save()
}
}
Why Can't you do some thing like below, Just fetch the user inside the closure.
Login.withNewSession {
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
login = new Login(user: user)
}
OR
Login.withTransaction{
def user = grails.admin.User.read(appCtx.springSecurityService.currentUser.id)
login = new Login(user: user)
}

How to use a shiro native session in a grails web application?

Currently, I am using the default HttpSession object in both controllers and gsp pages:
In controllers:
...
session.mykey = anObject; // adding an object to session
...
if (session.otherkey) { // performing some checking
In GSPs:
...
<g:if test="${session.mykey}">
...
I'd like to have a "remember me" functionality. Shiro has already it built in. However, as far as I understood, in order to do it I have to use the shiro native session mode (in Config.groovy: security.shiro.session.mode="native"). By default, it persists the session state, so objects will remain in the session as far as the cookie expires or the user logs off.
Is my understanding right?
Then i will have to change my controllers to this:
def shiroSession = SecurityUtils.subject.session
shiroSession.setAttribute("mykey",anObject)
....
if (shiroSession.getAttribute("otherkey") ){
And my views to this:
<g:if test="${SecurityUtils.subject.session.getAttribute('mykey')}">
So, my questions are:
Is that right?
Can't I just use the previous way to access the session?
Do I have to turn off the default http session in some configuration?
I gave up keeping objects in the session persistently (until cookie expires). Here is what i did:
In the login method in the controller:
if (! session.currentProfile){
Subject currentUser = SecurityUtils.getSubject()
if (currentUser.isRemembered()){
boolean success = configureSession(session, currentUser.getPrincipal())
if (success){
...
}
}
....
}
The first "if" checks whether the session has the object i need.
The configureSession method puts in the session all information I need.

Last login date in grails with spring security

I am running into this error when I try to set a lastLogin date with an event listener in Config.groovy:
2011-05-12 00:30:16,501 ["ajp-bio-8009"-exec-6] ERROR
events.PatchedDefaultFlushEventListener - Could not synchronize
database state with session org.hibernate.StaleObjectStateException:
Row was updated or deleted by another transaction (or unsaved-value
mapping was incorrect): [spl.User#110]
My code:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
User.withTransaction {
def user = appCtx.springSecurityService.currentUser
user.lastLogin = new Date()
user.save(flush: true)
}
}
I can't seem to replicate this in my test environment but the one time I tried it live in production I saw this error and decided to revert back. Will changing save to merge solve the problem?
EDIT: I was able to replicate the error in my test environment by signing on as the same user simultaneously. To debug further, I added some print statements inside of the event handler and went live with it. It turns out that when I sign or anyone signs on to my site, the event handler gets called MULTIPLE times, hence stale object exception. Why is the onInteractiveAuthenticationSuccessEvent getting called more than once on a login?
Thanks,
Ricardo
You'll want to get a fresh copy of the user in the session first:
grails.plugins.springsecurity.onInteractiveAuthenticationSuccessEvent = { e, appCtx ->
User.withTransaction {
def user = User.get(appCtx.springSecurityService.currentUser.id)
user.lastLogin = new Date()
user.save(flush: true)
}
}

symfony sfGuardUser hasCrendential live after update

I'm using symfony 1.4 and the sfGuardDoctrinePlugin, I've got it installed and setup fine but I have the following problem:
If I login as an admin and update the permissions for a user, that user must logout then login again before having the newly added credential/permission.
Is there a way around this?
I'm not sure how easy this would be to fix. When a user logs in I think their credentials are added to their session attributes there and then. So when the admin updates their credentials their session still holds the old credentials. This means any call to hasCredential isn't "live".
Thanks
This would add extra queries to each and every request to your application. You could force update of the credentials by $user->getSfGuardUser()->refresh(true), which would reload the entity and all its relations (and thus its permissions).
Thanks for your answer, I've modified the processForm function of the sfGuardUser module's actions class.
If I login and change my own permissions, the session is updated there and then.
My problem is that if I edit the user permissions of another user, I would need to edit their session data. To solve this I enabled database sessions, so I now have sessions saving there instead of to file. So my next problem is how to isolate the session for the other user.
The sessions database has the following columns: sess_id, sess_data, sess_time.
sess_data is serialized and that is what I would need to update.
But I think symfony updates the session ids quite often and it would be hard to always isolate the correct session for the other user.
I think that it would also be slow to try and unserialize, check user_id then reserialize the data. I would need a user_id column I think.
I know this is an old question, but I recently had this same problem and it took me way longer than it should have to find the answer (which was posted in Symfony's code snippet section). Paste this function in your myUser class and all problems go away:
/**
* Overridden method that actually reads the permission from DB
* instead of relying on data present when the user logs in.
*
* #param string permission name
*
* #return boolean true if the user has credential
*/
public function hasCredential($permission_name)
{
if (!$this->isAuthenticated()) {
return false;
}
$gu = $this->getGuardUser();
$groups = $gu->getGroups();
$permissions = $gu->getPermissions();
$permission_names = array();
foreach($permissions as $permission) {
$permission_names[] = $permission->getName();
}
foreach($groups as $group) {
$group_permissions = $group->getPermissions();
foreach($group_permissions as $group_permission) {
$permission_names = array_merge($permission_names, array($group_permission->getName()));
}
}
$permission_names = array_unique($permission_names);
return (in_array($permission_name, $permission_names)) ? true : false;
}

Resources