Modifying HTTP requests to act like Ajax

Hi all,

I have an application with a pretty standard login system: most actions have a before_filter which redirects the user to a login page if they don't have permission to perform the given action. What I want to do is modify this such that if a user clicks a link, and doesn't have permission to perform the action, a login box appears using javascript on the current page.

Is it possible to do the above without a very significant rewrite of the code? It could be done by replacing lots of link_to functions with lots of link_to_remote functions, then using javascript to redirect the page if the user has permission to perform the action, but this seems clumsy. What I'd like to do is use a before_filter to modify HTTP requests (GET, POST etc.) such that they behave like Ajax requests and return Javascript to the page, if the user doesn't have permission to do something. Is this possible?

Many thanks, Adam

I assume this is just for UI improvement, and that you wouldn't depend on this for protection -- users who visited restricted URLs manually would still be hit by the before_filter.

If so, then that's a very, _very_ interesting idea. As a user, I would _much_ prefer a popup window (or floating div) that I can cancel if I don't want to log in.

I think you'd need to use UJS or a hand-rolled page decorator to attach onclick event handlers to your anchors (A tags) on page load.

Perhaps your decorator would look for links inside containers (divs or spans) with CSS class "restricted". The CSS selector might be:

  .restricted A

Then, the work of migrating your site to the new system would just be a case of making sure the decorator code is included in your page layout, and making sure that restricted links were properly identified by the decorator. If you're already making consistent use of link helpers, that's probably not a great deal of work.

Ciao, Sheldon.

Hey Sheldon, thanks for that. I'd thought of doing something like that, but my permissions system is pretty complex, so it wouldn't be as simple as putting a "restricted" class on my links. There's several different before_filters that I use to check for different permissions, but they all call the same helper method, login_redirect, to redirect to a login page. I was hoping to find a solution that just requires changing this helper method rather than editing hundreds of links by adding class names.

One idea - perhaps an onclick function could be added to ALL links that would query the page via Ajax before the link is called. My login_redirect helper method could be made to return a certain HTTP response code if the page wasn't accessible. If this code was returned the login box would be shown, else the link would be processed in the normal manner. The problem with this is that all links have to perform two queries instead of one, so it's not really ideal. I'd still prefer to somehow use the login_redirect method to coerce HTTP requests into Ajax requests and return javascript if authentication failed. Any more thoughts?...

Adam

I don't think you can do this without changing the original links: when you click on a 'normal' page link, the browser will replace the current page with whatever it gets back from the server (and it would probably just display the javascript). An ajax link is different (since the http request is made via XmlHttpRequest or similar). You can certainly render some javascript from your before_filter, I just don't think it will do you any good.

Fred

Thanks Fred, I was afraid that might be the case. I think that my best bet would be to rewrite my before_filters to use some kind of lookup function such that I can determine if a given link should be restricted for a given user. Using this I can stick onclick attributes on all my links as required.

This leads me to another question. I'd like to do the above without actually changing the links. I want to use javascript to extract the href attribute of the link, check if the logged in user is allowed to go there, then add an onclick function if appropriate. Given that the href attribute will be a string like "http://localhost:3000/users/1/ edit", is there a way to use Rails' routing functions to extract the parameters from this string? Ideally I need a function such that function("http://localhost:3000/users/1/edit"\) returns {:controller => "users", :action => "edit", :id => 1}.

Many thanks, Adam

Just found the answer to my own question here: http://www.scoutlabs.com/2008/01/23/rails-tip-inverse-of-url_for/

I might be sticking my foot in my mouth, but I think jQuery can handle that by overriding the default handler for clicks on links. So, you could try something like:

$('a.restricted_action').click(function() {     [check user using jQuery XHR support]     if(!valid_user)        show_login_form(); }

The XHR (XmlHttpRequest) jQuery would execute could provide the 'href' attribute of the 'a' element to the remote action, which would then return something indicating whether or not the user has the required privileges. In case the user does not, you could show up your login form, submit it asynchronously and set the user session accordingly.

That *might* work without changing the links, but I have not tried to implement that, it's based completely on my experience with jQuery and Rails. Of course, it also relies on an external Javascript framework, which might be an issue for some.

Just my $0.02... Ulisses

Thanks for the idea, but that still runs into the problem of having to stick a "restricted_action" class on all my links. That's not really an option as I have quite a complex permissions system, so different links may be restricted for different users. Also I've spent a very long time moving to the Mootools javascript framework instead of Prototype, and I'll be damned if I'm going to start again with jQuery! (although I think mootools could do that just as well)

The solution I've gone with is to rewrite my permissions code such that I have a single lookup function which determines whether a user can access an action defined by a certain set of parameters (controller name, action name, id etc.). I then rewrote the link_to function such that it parses the link given from a string back into request parameters (using the inverse url_for mentioned in my last post), checks if there is a logged in user and if they can access this link, and if not replaces the href attribute of the link with "#" and the onclick attribute with a "show_login()" function. I'm not sure if this is the most efficient way to do things, but it does work!