Announcement Announcement Module
Collapse
No announcement yet.
GORM - Prevent Insert/Update/Delete Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • GORM - Prevent Insert/Update/Delete

    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
    Code:
    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!

  • #2
    You can fetch a domain class in read-only mode with the read() method, but you need to know the object ID for that. There is a pull request 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?

    Comment


    • #3
      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?

      Comment


      • #4
        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.../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:

        Code:
        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:

        Code:
        import com.mycompany.myapp.ReadOnlyEventListener
        
        import org.codehaus.groovy.grails.orm.hibernate.HibernateEventListeners
        
        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:

        Code:
        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.

        Comment


        • #5
          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!

          Comment

          Working...
          X