/grails-file-uploader

Simple Grails plugin to handle file uploads

Primary LanguageGroovy

Based on the File uploader grails plugin at: http://grails.org/plugin/file-uploader


This plugin adds local storage as an alternative to the Grails native way of storing files into the database as byte arrays.  It allows several ways of doing this.  

FIRST METHOD: Use the LocalUploadService.lookForNewAttachments() closure to associate incoming file attachments with your domain model as UFile objects while you are editing that domain object.  In your controller, you would do:

private List<UFile> lookForNewAttachments(Example example){
  List<UFile> invalidFiles = []

  localUploadService.lookForNewAttachments(request, params, flash){ UFile ufile ->
    if(!example.files){
      example.files = []
    }

    if(ufile.hasErrors()){
      invalidFiles << ufile
    }else{
      example.files << ufile
    }
  }

  return invalidFiles
}

and in your view:
<span>
<g:each in="${exampleInstance?.files}" var="f">
<localUpload:download fileId="${f.id}">${f.name}</localUpload:download>
</g:each>

<localUpload:minupload bucket="docs" name="attachments" multiple="true" />
</span>

Examples usages of this are in localUploadExample/grails-app/views/example/_form.gsp and localUploadExample/grails-app/controllers/com/bowerstudios/fileManager/ExampleController.groovy.  (Don't forget to switch form tag from g:form to g:uploadForm


SECOND METHOD:  If you already have a domain object persisted, you can use the LocalUploadController and LocalUpload tag library to attach files to a domain object.  In your view:
<localUpload:form bucket="docs" saveAssoc="example" multiple="true" id="${exampleInstance.id}"/>
You'll then need to implement ILocalUploadSupportService as a grails service.  Specifically, you need to tell the plugin how to associate a UFile with your domain object.
@Override
void associateUFile(UFile ufile, Map params) {
    if(ufile){
        switch(params.saveAssoc){
            case 'example':
                Example example = Example.load(params.id)

                if(example.files){
                    example.files.add(ufile)
                }else{
                    example.files = [ufile]
                }

                example.save(failOnError:true)

                break
            default:
                log.error("Save Association ${params.saveAssoc} not handled in associateUFile")
                break
            }
    }

    return
}

Note the use of the saveAssoc parameter as a "key".  There's a better way to persist this link, but for now this works.

Leaving out or setting multiple="false" will only clue the browser to submit a single file.  This means the browser will only submit a single file at a time, instead of multiple.  It does not add business logic to limit how many files will be associated with the model.


THIRD METHOD:  This plugin integrates with the bootstrap-file-upload plugin to provide the pleasant jquery-ui.  It is currently being developed.


To Build plugin:
Go to grails-local-upload directory in this directory and run: grails maven-install

To Run exampleProject
Follow steps above to build plugin first.
Go to localUploadExample directory in this directory and run: grails run-app


Installing:
Add compile ":local-upload:3.0.14" to your BuildConfig.groovy in the plugins block


Example config block:
localUpload {
  docs {
    maxSize = 1000 * 1024 * 10       //10 MB
    allowedExtensions = ['*']        //all extensions allowed
    path = "/tmp/docs/"
    // path = "C:\\attachments"      // for windows path
    storageTypes = ['monthSubdirs']  // options (comma separated list of strings): monthSubdirs, plain
  }
}

In the storageTypes section, you can specify parameters for storage, the default stores files with a uuid name and no extension.  This should lend slightly more security.  If two users upload a file with the same name, the files will not overlap or be unintentionally revealed to an incorrect person.  You can store the file by the original filename by using 'plain'.  You can have the plugin create subdirectories by year and month with 'monthSubdirs'.  By default subdirectories are created by the timestamp of the file.


Security:
If using spring-security, you can secure access to the Local Upload Controller by adding it to your static rules.  
Example with annotations enabled:
  grails.plugins.springsecurity.controllerAnnotations.staticRules = [
    '/localUpload/**': ['ROLE_USER']
    '/image/**': ['ROLE_ADMIN']  // if you are also using bootstrap-file-upload
  ]

You must create a service that implements org.grails.plugins.localUpload.ILocalUploadSecurityService

At an insecure minimum, it would look like:
class LocalUploadSecurityService implements ILocalUploadSecurityService {
  @Override
  boolean allowed(UFile ufile) {
    return true
  }
  @Override
  boolean allowedToDelete(UFile ufile) {
    return true
  }
}

Ideally, you would do something like pull the current user from the session, and check to see if they have access to this UFile.  Another option would be creating a UFileRole table that would associate specific files with roles, and you could verify that the user had that role here.

The ILocalUploadSecurityService is only invoked on download or delete.  Uploads are only restricted by the staticRules above.




Todo:
 - Params should pass correctly in download links
 - Test error flows