Rails: After devise user_signed_in? have a sitewide validation - ruby-on-rails

As a better alternative to https://stackoverflow.com/questions/25613825/rails-redirect-to-for-default-application-layout I need to validate with user interaction before other web site features become available.
Something like "when user_signed_in? yield to controllers and views if account_verified? otherwise redirect_to verify_account"
I'm guessing this would look like a before_action in ApplicationController... I'm going to try and hash it out. I'll post my answer here when I get it.
EDIT: I'm not verifying the devise login. That's already done. This is something totally different.

You can do this with devises authenticate_user!
So in your application_controller.rb
before_action :authenticate_user!
Which redirects to sign in if they aren't logged in

This solves it for me. Specify which controllers to exclude from before_filter
Also for my before_filter I needed to move the methods into a module in my lib folder since I was getting a Controller not initialized error. The error was from a self.mymethod within the controller being called as MyController.mymethod This doesn't work, hence moving it all into the lib folder inside a module.

Related

Passing rails requests through a series of controllers?

The question is generalized but I want to ask about a specific case which I want to solve.
I'm working with a really really smelly code base of e-commerce app and I want to refactor it. I thought I should start with the User authentication.
Problem
Before every action in any controller, we check if the user is of a particular type: Guest, Signed-In or Admin and also, if the user is allowed to access this action based on the type. If all the conditions are met, then the action is executed. And this happens in majority of the actions in majority of the controllers.
My thinking
I know this code is smelly because checking if the user is of a particular type and (s)he has access to an action is not the action's responsibility.
My solution which may or may not be possible
We can make a SessionsController (or some other name) and let it handle the authentication and authorization part. But I want the SessionsController to do its job automatically before every request. i.e. Every request should go through the SessionsController and then this controller will decide whether or not to forward the request to the appropriate controller.
I search Google for this but didn't find anything. So my logical conclusion is that passing a request through a series of controllers might not be possible. But I'm not sure. So if it is possible, guide me how to do it. And if it is not possible, then suggest any other way to do it.
This sounds like a perfect example in which one or multiple before_action can be used. You can place a before_action in your ApplicationController:
# in app/controllers/application_controller.rb
private
def authorize_admin
render status: 401 unless current_user? && current_user.admin?
end
Then you can declare in any controller in which you want to run this method before running any action.
# in any controller - even the ApplicationController
before_action :authenticate
You can configure before_action to only run on certain conditions or with certain actions. Just a have a look at the how to use Filters in the Rails Guides.

Multi-tenant Rails app and restricting access to controllers

I am using Devise for authentication, and I have "modules" in my rails app. I am trying to figure out the best approach to security. For example, here are the few things that I want to accomplish:
I want the application controller to require login, unless they are accessing the registrations controller, in which case they are just submitting registration details.
On the application level, I would like to define scopes, permitting access to certain controllers that the user's company has access to.
Additionally, on each request, I want to verify that any IDs in the URL (whether it's a GET, POST, whatever), the user's company has access to that controller and ID in the parameter. (So they can't access Report ID 9 if their company doesn't have a report ID 9 associated with it)
I feel like this may be scalable, but I've never done this before so I'm not quite sure.
Bullet 1
In the ApplicationController, I would like to do something like this:
class ApplicationController < ActionController::Base
protect_from_forgery with: :exception
before_action :authenticate_user! unless controller == "Registrations"
end
I tried placing a binding.pry in the application controller, but the controller_path seems to always be application. Not sure if there's an easy way to accomplish this without going to each individual controller, which I'm hoping to avoid because I don't want to accidentally forget something when I add a new controller down the road (basically going against DRY).
Maybe I can implement a security controller and have every controller inherit from it? Never did this before but this might work if I can't accomplish what I'm trying to do in the Application Controller.
Bullet 2
I have tried to access Devise's current_user variable from the Application Controller, but it does not exist, so I'm not sure if I can check the user's permissions from the ApplicationController. Again, I'd love to avoid having to place this check in each controller because as the application expands, I may eventually forget to implement checks.
EDIT
So it looks like I have bullet 1 addressed, but now I'm trying to figure out the other 2. Being able to implement some type of "scope" or permission module at the application level. Any ideas?
The skip_before_action directive can suppress execution of a before_action filter that's already defined. You can use this to turn off an action for a controller:
class ApplicationController < ActionController::Base
before_action :authenticate_user!
end
class RegistrationsController < ApplicationController
skip_before_action :authenticate_user!
end
The thing to note here is Ruby is a highly dynamic programming language and code can be executed while the class is being defined. It's important to pay close attention to when certain statements are run, as things like unless, the statement, tend to run immediately when in that context.
You'll see some others that allow deferred execution like in ActiveRecord with:
validates :name, unless: :anonymous?
Where that validates method has a specific option called unless which is distinct from the keyword unless. That defines a validation trigger with a condition attached to it.
On the other hand this code, while visually similar, is completely different:
validates :name unless anonymous?
This depends on a method called anonymous? being available at the class level and if it returns a non nil or false value will execute the validates function.

How do you make sure that after sign in the user completes the profile form first before they can use the rest of the website functions.

How do you make sure that after sign in the user completes the profile form first before they can use the rest of the website functions. I am trying to make sure that after the member has completed the sign up form and then completes there email confirmation with devise that when they sign in that when they are redirected to the new_member_profile_path(current_member) form that they stay on this page and that if they decide to go to a link and click that that they will automatically be redirected back to the complete your profile page with the notice before please complete your profile first. I have it set already once they have completed the form they will be redirected to their member's page. I have looked in multi-forms with wicked - I really feel that because I am still am a Novice rails developer that this would be unnecessary. I am thinking about putting an if clause in the application.html.erb where the site nav template is based and putting a clause with <% if current_member_profile.blank ? %> then redirect back to new_member_profile_path(current_member) with a flash notice tag written in the html file. I have tried this if clause but does not work - comes up as undefined method. Please could someone point me in the right direction or give me the simple solution of getting this idea to work. Thanks in advance ;)
There are various approaches to achieve what you're trying to do. Perhaps the cleanest with the least amount of code needed would be to first authenticate the user with Devise's own authenticate_user! filter and then check for a field that can only be there when the profile has been filled in.
# in user.rb
def has_completed_profile?
first_name.present?
end
# in application_controller.rb
before_action :authenticate_user!
before_action :require_user_profile
private
def require_user_profile
# nothing needs to be done if the profile was already filled in
return if user_signed_in? && current_user.has_completed_profile?
redirect_to edit_profile_url, alert: "Please complete your profile first!"
return false
end
Notice how I've extracted has_completed_profile? into the User model instead of putting the name check directly into the controller. This way, if you need to change the logic of what makes a profile "complete", you don't need to touch the controller at all ("complete profile" is a business concept, not a routing/HTTP concept and thus doesn't belong in the controller).
In controllers where you don't want the additional profile check – e.g. the controller where the user actually completes their profile, where they presumably need to be logged in but can't have a profile yet – you just skip the additional filter:
# in profiles_controller.rb
skip_before_action :require_user_profile
Side note: Over the years I've learned that it's best to keep things like profile data, address data, phone numbers and what not in a separate model and don't extend Devise's User model. This prevents various issues and keeps the already huge User model (Devise includes dozens of methods into it and turns it into a God Object as it is) a bit slimmer. And if you think about it, it also makes sense in terms of business logic: A user has_one profile, has_one address (or has_many addresses) etc.
Hope that's clear.
You can add a new method in the application_controller.rb
For eg
def current_member_profile
current_user.name.blank?
end
Check the params which should not be blank when creating a member profile. I have added 'name' here for example. if the params is blank, then it will redirect as you have specified in your application.html.erb
Instead for putting an if condition on the application.html.erb, You can use a before_action in your application_controller.
Something like this.
application_controller.rb:
before_action :check_for_profile_completion
def check_for_profile_completion
// your code of redirection to the page if the profile is incomplete
end
Also you can skip this action on controller which you don't wanna restrict user to go. Like
skip_before_action :check_for_profile_completion, only: [://actions you wanna skip seperated by comma]

Disable logged_in filter for precise URLs

I am quite new to Rails, so please let me know if I don't use the correct terms.
I am trying to create a website that lets people create their own elections. The latest version of the website is available here.
People creating the elections have to register. The elections are only viewable and editable by logged in members (You don't want anyone to be able to see your election).
For this reason, I have this at the beginning of my election controller :
class ElectionsController < ApplicationController
before_filter :logged_in?
Logged in users access the elections via this kind of url : http://spo2tu.be:8001/elections/:id
The person creating the election defines a list of voters that will have to vote for a candidate.
What happens is that for each voter, a unique url is generated and sent by mail. The URL are of form : http://spo2tu.be:8001/elections/:id/votes/:long_generated_id
Example : http://spo2tu.be:8001/elections/1/votes/Y2_8TDL8Hkpz2MzzV_bpgw
My issue is that due to the heritage of the before_filter, non logged_in users see an error when reaching this URL .
The error is the following :
undefined method `elections' for nil:NilClass
#election = current_user.elections.find(params[:election_id])
What I think happens is that since the election has a logged_in before filter, it is impossible to get a reference to it in a deeper URL.
Since those URLs are unique, temporary and generated, I would like to let the persons having access to them also access the elections; hence bypassing the before_filter.
What would be the proper way to achieve this?
One possible solution I see would be to avoid nesting and change the URL to be of kind http://spo2tu.be:8001/votes/Y2_8TDL8Hkpz2MzzV_bpgw directly, but that wouldn't solve the issue since voters still need to have access to the election to see the description.
Thanks for the help!
You should specify for with method you want to check if the user is logged in.
so change before_filter :logged_in? to before_filter :logged_in?, only: [:edit, :new, :destroy]
And you are probably using this method of logging in.
And this depends on a session being present for current_user method to work.
So I suggest you don't use this to look it up but you use a params in url to specify which user is trying to access the election.
Hope it helps. :)
So, after the suggestion from Ramon Gebben, the problem comes indeed from this line :
#election = current_user.elections.find(params[:election_id])
Since users are not logged_in, current_user is not defined, which lead to the error.
Changing this line to
#election = Election.find(params[:election_id])
solve the issue.

authenticate_user! from Devise, in spree (spree commerce), does not work as expected when used as a before_filter

I am using Rails (3.2.6) with devise (2.1.2) and have a controller where I would like to make sure users are authenticated before proceeding.
Optimistically, I tried...
module Spree
class MyAppController < Spree::BaseController
before_filter :authenticate_user!
...remainder of MyAppController code...
and I am NOT redirected to a login or sign-in page. I am redirected to the 'products' page, with a polite message at the top saying I need to sign-in or sign-up to continue.
What I would like to happen is that I am redirected to sign-up / sign-in and when that is completed successfully, the original controller path resumes.
Searching around, I have read that authenticate_user! from Devise interacts with Spree in such a way as to cause an infinite redirection, so something in Spree disables this, resulting in the lame behavior I describe above.
Has anyone managed to get this to work or have suggestions for a good work-around?
I have found a work around, but I am not an experienced enough Rails developer to know how reasonable this solution is.
I wrote a method to do the filtering and used it instead of authenticate_user!...
def require_authentication
unless current_user
# setting this in the session allows devise to return us to
# the original invocation path, once sign up / sign in is complete
session[:user_return_to] = request.env['PATH_INFO']
redirect_to new_user_session_url and return
end
end
did you try adding
before_filter :check_authorization
to your controller?
I think that may do what your looking for.
Thanks
Ash

Resources