PDA

View Full Version : GORM - Prevent Insert/Update/Delete



Brian Riley
May 3rd, 2011, 10:25 AM
Hi,

Does GORM provide a way to prevent a domain object from being inserted/updated/deleted? My security objects are being loaded from an ERP system and I want to prevent updates/deletes/inserts from persisting back to the database. Those objects are managed from within the ERP and only need to be referenced from within my Grails app.

I tried setting my
static mappings = { cache 'read-only' } but when I call .save() on the object in my test it does not fail.

I also saw the beforeDelete() function but didn't see a way to cancel the transaction from there.

Any guidance would be greatly appreciated. Thanks!

pledbrook
May 4th, 2011, 05:18 AM
You can fetch a domain class in read-only mode with the read() method (http://grails.org/doc/latest/ref/Domain%20Classes/read.html), but you need to know the object ID for that. There is a pull request (https://github.com/grails/grails-core/pull/61) to add support for read-only queries, but it's only likely to be available in Grails 1.4.

I can't think of other workarounds for the moment. You can disable automatic flushing of the underlying Hibernate session, but I assume you want to persist other domain instances within the same request?

Brian Riley
May 4th, 2011, 08:20 AM
Thanks Pledbrook,

I was thinking this morning of just creating a disconnected delegate object that can be passed back and forth through a service (probably an extension of the core plugin's securityService() rather than giving the user direct access to the domain object.

I'm trying to create a plugin though that includes the spring-security-core and spring-security-cas plugins so that my colleagues can just pull in just the one wrapper plugin but I'm not sure how that would work with the underlying security architecture. The core security plugins will obviously interact with the domain objects and then perhaps I can just give explicit instructions to the developers to use my service methods to get the User and Role delegate objects.

That shouldn't create any issues should it? The @Secured('ROLE_BLAHBLAH') would still function within their controllers I would think. They would just use my service to access the User and Role rather than the core plugin's securityService.

I think if I extend the securityService any changes back to using the core plugin's securityService would be minimal if Grails later allows me to lock the domain objects down.

Do you think this approach will work?

burtbeckwith
May 4th, 2011, 01:01 PM
Spring Security doesn't update domain class instances, so there's not direct need to do much there - your app code is where users and roles are created/updated/deleted.

Grails doesn't have direct support for read-only domain classes and it's mostly a Hibernate limitation - see http://docs.jboss.org/hibernate/core/3.5/reference/en/html/readonly.html

You can return false from a beforeUpdate method in your domain class and it'll cause any changes to be discarded, but the return value is ignored in beforeInsert and beforeDelete, so this isn't sufficient.

You can however register event listeners that veto updates, creates, and deletes:



package com.mycompany.myapp

import org.hibernate.event.PreDeleteEvent
import org.hibernate.event.PreDeleteEventListener
import org.hibernate.event.PreInsertEvent
import org.hibernate.event.PreInsertEventListener
import org.hibernate.event.PreUpdateEvent
import org.hibernate.event.PreUpdateEventListener

class ReadOnlyEventListener implements PreDeleteEventListener,
PreInsertEventListener,
PreUpdateEventListener {

private static final List<String> READ_ONLY = [
'com.mycompany.myapp.User',
'com.mycompany.myapp.Role',
'com.mycompany.myapp.UserRole']

boolean onPreDelete(PreDeleteEvent event) {
return isReadOnly(event.persister.entityName)
}

boolean onPreInsert(PreInsertEvent event) {
return isReadOnly(event.persister.entityName)
}

boolean onPreUpdate(PreUpdateEvent event) {
return isReadOnly(event.persister.entityName)
}

private boolean isReadOnly(String entityName) {
return READ_ONLY.contains(entityName)
}
}


and register it in resources.groovy:



import com.mycompany.myapp.ReadOnlyEventListener

import org.codehaus.groovy.grails.orm.hibernate.Hibernate EventListeners

beans = {

readOnlyEventListener(ReadOnlyEventListener)

hibernateEventListeners(HibernateEventListeners) {
listenerMap = ['pre-delete': readOnlyEventListener,
'pre-insert': readOnlyEventListener,
'pre-update': readOnlyEventListener]
}
}


This will disable updates and deletes - save() and delete() calls will be silently ignored. Unfortunately you'll get an exception calling save() on a new instance due to another listener that Grails registers.

You can use JDBC to create new or modify existing records, but if you want to do this with GORM you can have admin-only domain classes (usage needs to be enforced in the code though) that map to the same tables but aren't known to the listener:



package com.mycompany.myapp

class WritableUser {

String username
String password
boolean enabled
boolean accountExpired
boolean accountLocked
boolean passwordExpired

static constraints = {
username blank: false, unique: true
password blank: false
}

static mapping = {
table 'user'
}

Set<WritableRole> getAuthorities() {
WritableUserRole.findAllByUser(this).collect { it.role } as Set
}
}


and likewise for the other two classes.

Brian Riley
May 5th, 2011, 10:05 AM
Thanks guys. I appreciate it.

This seems like more work than its worth though since these are coworkers and I'm not distributing the plugin externally. I can always just walk over and slap their hands if they try to make changes to the users and roles!