Best practice with custom restful routes - ruby-on-rails

In my web-app I have a typical User-Activation process by e-Mail.
At the moment I have a custom controller action for each step in the activation process, i.e. I've got an action "activate" that more or less simply renders the "register"-page, then I have another action "activation" which is the target action of the actual form on the register page.
I wonder if this is best practice? - right now, I'm on the verge of implementing admin-initiated password resets (admin clicks a link for a password reset on a specific user, the user gets an email with a link to a page where he can set a new password). I would go further, and add 3 more controller actions (one for sending the reset-email, one for the user accessing the reset-page, and one for the action of the actual reset-form).
This seems to clutter up my controller quite a bit and I wonder if this was the "right" way to do it?
thx for any advice

What you are doing is fine - it is not necessary to conform your controller actions to the RESTful actions. REST should only be used if it fits your model, but you shouldn't try to make your model conform to a RESTful architecture unless it makes the interaction easier and intuitive.
It might be best to place these custom actions into their own controllers though, rather than the UsersController. Once you feel that your controller is getting too crowded, it's probably a good idea to move some actions into separate modules or controllers of their own.
class RegistrationController
def activate # perform the activation
..
end
def activation # show the activation page
...
end
end
class PasswordController
def send # send the email
end
def resetter # show the page to reset the password
end
def reset # actually reset the password
end
end
These controller actions could be accessed via custom routes instead of resources.
match 'register/activation' => 'registration#activation'
post 'register/activate' => 'registration#activate'
post 'password/send' => 'password#send'
match 'password/resetter' => 'password#resetter'
post 'password/reset' => 'password#reset'

Related

In rails 4.2, how to display a form for preview but ensure it cannot be submitted

I'd like to have a a form view that can, depending on circumstances, have submit functionality disabled in a bullet-proof way so that even a clever user could not edit the HTML source (via a browser extension) to re-add the submit button.
It seems one way to do that might be to somehow inject an invalid authenticity token that replaces the (valid) rails-generated one, so that even if a user somehow re-adds the submit button (by editing the HTML via a browser extension) it would still be an invalid submission.
My thought is to have some logic in the view:
- if #form_disabled # set by controller
- somehow_invalidate_the_authenticity_token?
How might one 'break' Rails form submission?
The purpose of doing this, instead of rendering the preview in a :show action, is to have the exact same view displaying both the live-form and the dead-form.
If I were you, I would use pundit.
It's pretty simple, and has few lines of code if you need to know how it works.
I'd start to write the code here, but I realize that the example at the readme fit your needs.
At the application controller add this
At the folder app/policies put the class PostPolicy, of course, you must replace "Post" with the name of your controller in singular (even if you have not a model with that name). The update? (and create?) actions should return true/false to indicate if user is allowed or not.
A few lines down on the readme, you will find the PostsController#update action, which call to authorize with the record before the update. I think you want do the same with create (then you need a create? method at the policy class).
Pundit needs current_user controller method, if you don't have it. Just follow the user customization instructions.
Of course, new and edit actions don't call authorize because they are allowed to everybody. Only the POST & the PUT/PATCH actions are forbidden.
Yes, it's more than a surgery of one line of code. But it's simple and the right way of give access to users.
After reading my other answer, I start thinking that you can do the same that Pundit does at the controller:
def update
if <unauthorized user>
flash[:alert] = "You are not authorized to perform this action."
redirect_to(request.referrer || root_path)
else
# all the update stuff
# ...
end
end

rails 3 / heroku / devise: how specify different 'landing page' when users sign in?

We'd like to have 3 different signin forms for our app:
the default signin form, takes them to their normal dashboard (as we do now)
a "foo" signin form that, if they use THAT form to sign in, takes them to a special purpose screen
a "bar" signin form that takes them to yet anogther special-purpose screen
I assume the right approach is to somehow
a) create a new route for /foo and /bar, probably directing both to the SAME signin method but in the route add a url parameter 'signin_type' telling us which "type" of signin form it is?
b) implement a custom RegistrationsController method(s) (what name?) to handle the signin form (we already have a custom new and create method for when they register, since our registration form needed a 'referral code' field added), and have the method look at the url parameter 'signin_type' to redirect the sign to either the normal, or foo, or bad
c) implement another method that handles the signin submit (is that a different method?) that looks at some special embedded form value to figur eout which signing form was used?
That's my best guess. If correct, it's how to do (b) and (c) that has me stumped. Any thoughts will be appreciated!
I think you may be trying to over engineer this. I would approach this with a single sign_in page, and just use conditional logic in overriding the after_sign_in_path_for(resource) method for the Devise controller. Not only will this be much easier to implement now, it will be a lot easier to maintain in the future. Simply add to your ApplicationController.rb:
protected
def stored_location_for(resource)
nil
end
def after_sign_in_path_for(resource)
if condition_foo
redirect_to foo_url
elsif condition bar
redirect_to bar_url
else
redirect_to dashboard_url
end
end
The first method overrides Devise's default location of root and sets it to nil, then the logic after that is pretty self explanatory. This should work for what you are wanting.

Preventing discoverability in a RESTfully routed model

I have a model in my database whose 'show' action is open to viewing at URLs like:
mysite.com/project/12
mysite.com/project/14
The way my system is set up, there are a couple of defined methods through which these should be accessible:
A custom route I've set up is accessible to any visitor (registered or unregistered) who has this route. As an example, this custom route might be mysite.com/companyname/projectid, which the company might pass out itself to certain people it wants to have access. Note that this custom route runs a separate controller action, which sets some internal analytics then redirects to the show action.
Direct access when linked to by a registered user's home page.
I want to restrict the ability to start with mysite.com/project/14 then simply change the IDs, thereby seeing any project. How can I do this?
Clarification
My goal with this question is not just to obfuscate record IDs to make discovering certain records harder. Instead, I would like there to be only two allowable means of accessing project/12:
A user clicks on a link we provide on their home page (how can I ensure this link alone reaches project 12?)
A user or simple visitor is redirected here by another (specific) controller action.
Typing in project/12 directly should not be possible. At the moment, I imagine the best way to do this would be for the two methods above to pass a code that gets picked up by the project#show action. I just don't know how to implement this and if there are potential drawbacks.
Whatever you come up with - it is going to end up being security through obscurity due to this simple requirement:
A user clicks on a link we provide on
their home page (how can I ensure this
link alone reaches project 12?)
What you can do, however, is make it difficult to just straight-up guess the correct URL for the project.
My thought would be to give every Project a unique 'token' - If you are not logged in as the owner of the project, then you must use the token to access it.
For instance, in your project model you could have this:
class Project
before_create :set_public_token
protected
def set_public_token
# Randomizes a 20-digit long hex code
self.token = ActiveSupport::SecureRandom.hex(20)
end
end
Then, in your project's show action you would need to have this:
class ProjectsController < ApplicationController
def show
#project = Project.find(params[:id])
# Obviously you would changed signed_in? to whatever method
# you have that verifies someone is logged in
if !signed_in? || #project.owner_id != current_user.id
raise "Unauthorized Access" if #project.token != params[:token]
end
end
end
Then the owner of the project can share the 'public' link of their project to people they want to have access to it, which would look something like this:
www.example.com/projects/14?token=3jks83kasdkt84h6cd86
Again, anyone with that url could access the project, and I don't think you will be able to sanely get away from that - but it makes it a lot more difficult to do so.
This is the same concept many password reset functions work. Anyone with access to the password reset token could reset your password after you've requested a password. But knowing what token to use will take you ages (Make the token longer to make it harder to bruteforce).
That personally is how I would handle it, and how I've seen this sort of thing handled in the past (photobucket, private gists on github, etc)
The easiest way is to associate a project with a user or account, then require authentication when browsing your non public routes. If you setup an association, you can then do:
#user = current_user
#project = #user.projects.find(params[:id])
This will ensure that a given user can only find projects they 'own'.
If you don't want authorization, and just want obfuscation, you won't be able to use the 'id' alone in the route (as it is sequential). You could either pair the 'id' with a random key stored in the model (/projects/1?key=1234) or use a GUID instead of an id.
OK so another attempt now that I sort of understand.
First in your public controller action you want to do something like this:
def public_redirect
session[:authorized_for] = params[:id]
redirect_to resource_show_path(params[:id])
end
Now in your private controller:
def show
#resource = current_user.resources.find params[:id]
if #resource # authorized
respond_with #resource # ok
elsif session[:authorized_for] == params[:id] #redirected from public route
#resource = Resource.find params[:id]
respond_with #resource # ok
else
raise NotAuthorizedException # not ok, do something
end
end
This relies on sessions. This is certainly hackable, but it would be much harder then figuring out the public route. See http://guides.rubyonrails.org/security.html#sessions.
You can reuse the session technique for other similar needs (like for links from home pages where you can't verify the user from the controller, etc.
I have a project that has a similar requirement. Now first I feel the need to say that this is security by obscurity - and thus not much security at all. But for some apps that can be OK.
I have a on create callback on my model that generates a random string (or number) that I use as my ID - thus it is impossible hard to guess another resource's path.

RoR : Polymorphic Controllers

I have an existing site that has a bunch of different models and controllers. I am currently integrating Twilio's services into this site. Twilio allows you to supply a url that will be called when a user interacts with your phone number using their phone. Unfortunately, there is only one url that you can provide to Twilio and then all the parsing is done on your end.
So, now I have a twilio controller which parses the user's data and decides what they are trying to do.
Everything the user may be trying to do via their phone can be done on the website already, but now they have the option to use their phone when on the go. If they text my number "create group foo" then the site will try to create the group accordingly. My issue is that I already have a groups controller that knows how to create groups and has the appropriate before_filters to make sure that the user has permission to do so, amongst other things.
Is there a way for the twilio controller to parse the request and then "forward" it over to the proper controller in some way? I'd rather not have the twilio controller duplicate all of the code and filters that are in every other controller and some of that stuff doesn't feel right to be shoved into the models.
I'm somewhat new to rails in general, so I'm open to any suggestion. I'm hoping there's some design pattern out there that fits my use case and I'm willing to refactor my whole project for the correct solution.
I think there are a couple of things you can do. If you don't have to respond in a certain format, then you can simply redirect the request with the appropriately formatted parameters. For example:
class TwilioController
def create
if params[:twilio_action] == 'create group'
redirect_to create_group_path(:id => params[:group_id], :number => params[:number])
end
end
end
There's a good chance that you'll have problems with authentication though, because the twilio api will not be sending and receiving cookies for you, so you will not have an authenticated user. If this is the case it will be best to put all your shared code in the model and handle cookie authentication with your GroupsController and phone number authentication with your TwilioController. For example:
class TwilioController
def create
if params[:twilio_action] == 'create group'
if can_create_group?(params[:phone_number])
Group.create(:id => params[:group_id])
end
end
end
end
It's always best to put your business logic in your model, but if you do actually have a function you want to share within two controllers you can always create a module to do that as well:
module GroupControllerActions
def create_group user
Group.create(params[:group].merge({:user => user}))
end
end
class TwilioController
include GroupControllerActions
def create
if params[:twilio_action] == 'create group'
create_group(User.find_by_number(params[:phone_number]))
end
end
end
class GroupsController
def create
create_group(current_user)
end
end

In RESTful design, what's the best way to support different kinds of GETs?

In a current project I need to support finding a User by login credentials and also by email address. I know that in RESTful design you use a GET to find resources. In Rails...
GET /users # => UsersController.index -- find all the users
GET /users/1 # => UsersController.show -- find a particular user
But I also need something akin to...
GET /users?username=joe&password=mysterio
GET /users?email=foo#bar.com
Is it conventional to add additional routes and actions beyond index and show?
Or is it more common to put conditional logic in the show action to look at the params and detect whether we're finding by one thing or another?
There's a similar issue with PUT requests. In one case I need to set a User to be "active" (user.active = true), and in another case I just need to do a general form-based editing operation.
Thanks guys. Eventually I'm going to figure out this REST stuff.
I'm new to SO, so I can't comment, but the checked green answer is not RESTful.
In a RESTful world, your controller grabs all the parameters and passes it to the model layer for processing. Typically, you shouldn't create another action.
Instead, you should do do something like this:
def show
#user = User.find_by_login_or_email(params[:user])
... #rest of your action
end
Your model can have a method like this:
class User
self.find_by_login_or_email(params)
return find_by_login(params[:login]) unless params[:login].blank?
return find_by_email(params[:email]) unless params[:email].blank?
nil #both were blank
end
end
Your view could look like this:
<%= f.text_field :user, :email %>
or
<%= f.text_field :user, :login %>
Note: untested code, so may be buggy...but the general line of thinking is usually not to create new actions for every one-off rule. Instead, look to see if you can push the logic into the models. If your controllers start to have too many non-standard actions, then it may be time to re-evaluate your domain modeling, and perhaps it's refactor the actions to some new models.
ps: you should never pass in passwords via a GET like that
I don't know how much of this is convention, but this is what I would do. I
would add another action, as long as it's specifically related to that
resource. In your example, show is a find by userid, so it makes sense as
another action on UsersController. You can turn it into a sentence that makes
sense, "get me the user with this email address"
For the other one, GET /users?username=joe&password=mysterio, I would do
that as another resource. I assume you're thinking that action would log in
the user if the password were correct. The verb GET doesn't make sense in that
context.
You probably want a 'session' resource (BTW, this is how restful_auth works).
So you would say "create me a session for this user", or something like POST
/sessions where the body of the post is the username & password for the user.
This also has the good side effect of not saving the password in the history
or letting someone capture it on the HTTP proxy.
So your controller code would look something like this:
class UsersController < ActionController::Base
def show
#user = User.find_by_id(params[:id])
# etc ...
end
def show_by_email
#user = User.find_by_email(params[:email)
end
end
class SessionsController < ActionController::Base
def create
# ... validate user credentials, set a cookie or somehow track that the
# user is logged in to be able to authenticate in other controllers
end
end
You would set up your routes like this:
map.connect "/users/byemail", :controller => "users", :action => "show_by_email", :conditions => { :method => :get }
map.resources :users
map.resources :sessions
That will get you URLs like /users/byemail?email=foo#example.com. There are
issues with encoding the email directly in the URL path, rails sees the '.com'
at the end and by default translates that into the :format. There's probably a
way around it, but this is what I had working.
Also like cletus says, there are ways to make your route match based on the format of the parts of the URL, like all numbers or alphanumeric, but I don't know off hand how to make that work with the dots in the url.
The first thing you can do is make your GETs as smart as possible. In your example, this can be handled programmatically. The argument can be processed this way:
Is a number? It's a userid;
Has a # in it? It's an email;
Otherwise? It's a username.
But I assume that you're not just talking about this example and want something to handle the general case rather than just this specific one.
There are basically two ways of dealing with this:
Add extra path information eg /users/email/me#here.com, /users/name/cletus; or
Be more specific in your "top-level" URL eg /user-by-email/me#here.com, /user-by-name/cletus.
I would handle it programmatically if you can.
Regarding the "ByEmail" request, have you considered creating a new email resource.
GET /email/foo_at_bar_dot_com
The response could contain a link to the related user.
I see so many people trying to apply RESTful design principles to their URL structure and then mapping those urls to procedural handler code. e.g. GET = Show, or is it GET = Index or ShowByEmail. By doing this you are really just pretending to do a RESTful design and then trying to create a mapping between a resource oriented URL space and procedurally oriented implementation. That is really hard to do and the procedural nature keeps leaking out into the URLs.
Resource oriented design often requires a very different way of thinking about problems that we are used to and unfortunately many of the frameworks out there keep sucking us back into the RPC model.
You might be able to set up different routes for different tasks. So for this case you could have one route to a method in UserControll dedecated to getting a user by email, and another for getting the information by credentials.

Resources