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?
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.
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?...
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.
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}.
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.
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!