= FilePermissions Use this plugin if you want to fine-tune permissions on some or all the files in your +public+ directory. Normally these files are served by the webserver before Rails ever gets a chance to see the request. This plugin tells the webserver to pass certain file requests on to Rails, where a +before_filter+ intercepts them and passes them to a callback you provide to determine whether you would like to serve them or not. It provides a handy controller method that lets you serve the file efficiently, complete with flexible cache-control support. == Example You can register the files you are interested in anywhere, really, but it probably makes the most sense to put it in app/controllers/application.rb, together with the before_filter, so that it's all in one place: class ApplicationController < ActionController::Base # Make sure we've had a chance to login the user, first, if we support # "remember-me" style login via cookies. before_filter :autologin # Now intercept file requests and evaluate permissions. before_filter :file_permissions # Simplest example: just check if the current user has permission to see # a given file. It serves the file automatically if you return true, # otherwise it renders /public/403.html (this is not supplied with Rails). FilePermissions.register("/public/images/user") do |controller, file| controller.session[:user].has_permission_to_view_image(file) end # You may customize things very easily: This time we look up the Image # object associated with our images, query a database to see if the current # user has access. If they do, render it, but instruct the browser to keep # the image in a private cache, and tell it to revalidate once a day. If # the user does not have access, render the "access_denied" view. FilePermissions.register(/public.images.user.(\d+).jpg$/) do |controller, file, match| if (img = Image.find(match[1])) && img.can_user_see_me?(controller.session[:user]) controller.render_file(file, :private => true, :max_age => 1.day, :must_revalidate => true ) else controller.render(:action => "access_denied") end end end == Limitations Currently it only works with Mongrel. But it should be relatively easy to hack Webrick or any other Ruby webservers the same way we've done Mongrel. You will need to make sure there is a route defined for the files you are protecting. I recommend something like what's shown below to capture everything under a given directory. Remember, it doesn't actually ever reach the bogus action. In fact, even the controller is unimportant if you place before_filter :file_permissions in ApplicationController. ActionController::Routing::Routes.draw do |map| # Route everything under /images to "anycontroller". map.connect "/images/*file", :controller => "anycontroller", :action => "bogus" end If you are running Mongrel under Apache, then you will further need to instruct Apache to pass these files. Typically this is done in the configuration file using ProxyPass directives. For example, a typical configuration might look like this: ServerName yourdomain.com DocumentRoot "/usr/local/rails/yourdomain" SetEnv RAILS_ENV production Options FollowSymLinks AllowOverride AuthConfig Limit Order allow,deny Allow from all # Tell Apache to pass requests for user images on to Rails. ProxyPass /images/user balancer://mongrel_cluster/ # Everything else in public is okay to serve up as-is. ProxyPass /images ! ProxyPass /stylesheets ! ProxyPass /javascripts ! ProxyPass /robots.txt ! ProxyPass /favicon.ico ! # Everything else goes to Rails. ProxyPass / balancer://mongrel_cluster/ ProxyPreserveHost on BalancerMember http://127.0.0.1:3000 BalancerMember http://127.0.0.1:3001 BalancerMember http://127.0.0.1:3002 RewriteEngine on etc... Author:: Jason Hollinger License:: Free - do whatever you want to with it. Last Update:: July 30, 2008