Devise not calling overridden function - Ruby on Rails - ruby-on-rails

I'm trying to store a list of permissions within a session variable after a user signs up or logs in to their account. The following code only works when a user logs into a currently saved account. What method do I need to override to save the permissions when the account is created via sign up?
class UserSessionsController < Devise::SessionsController
after_action :after_login, :only => :create
def after_login
session[:permissions] = current_user.list_permissions
end
end

class Users::RegistrationsController < Devise::RegistrationsController
after_action :after_signup, :only => :create
def after_signup
## your data permissions
end
It might be necessary to inject via resource. In this case try (i commented it out quickly, just in case you wondering whats going on):
class Users::RegistrationsController < Devise::RegistrationsController
def create
## GET THE SIGN UP DATA
super do |resource|
##CHECK IF DATA IS VALID
if params[:your_data]
##SET THE RESOURCE TO THE DATA
resource.your_data = params[:your_data]
##CHECK (IF NECESSARY) DATA TO TYPE
if resource.you == 2
## SAVE IT
resource.save_with_your_data
## SAVE WITH STANDARD SETTINGS
else
resource.save
end
end
end
end
end

What method do I need to override to save the permissions when the account is created via sign up?
It seems like you want to store this information in the database when the user is created. Remember that current_user will give you the entire database record in the object, and then you can do whatever you need with it. I wouldn't recommend messing the session directly, instead, work with the current_user object that Devise provides.

Related

How to enable guest account for a specific controller

I'm stuck with an implementation for a project I'm working on. Using rails 7 with devise, all users can sign in so I have the methods authenticate_user! and current_user, which, again, works fine.
I've introduced a Customer model. For now, I do not want the customer to register with password etc, I want them to click a link, sent to them from my application, then visit a page:
# Observer
class CustomerObserver < ActiveRecord::Observer
def after_create(customer)
secret_param = customer.to_sgid(expires_in: nil).to_s
url = ENV['HOST'] + "?csig=#{secret_param}"
# Send email with the url...
end
end
Once customer clicks that link, they should be taken to a special page and should have access to this controller only.
Been looking at GlobalID and not sure how to use the for: name in .to_sgid so that I could restrict the customer to access only the CustomersController and the show action.
In all controllers I have before_action :authenticate_user!. First thing came to mine is overriding that method. Feels wrong. How to have a customer access the CustomersController via the signed link and still be protected from unauthorize users?
I'm currently exploring this method where I could potentially have the customer model authenticatable? This means having two logins, one for user and other for customer. Would be nice to have one sign in path /sign_in/ for both user and customer. I feel this post is changing its direction.
How about some lazy check like this;
before_action :authenticate_user!, :if => :check_csig
# ...
private
def check_csig
# if csig param do not exists, call the authenticate_user!
true unless params[:csig].exists?
# if csig param exists and csig is valid do not call the authenticate_user!
false if check_csig
true
end
OR
before_action :authenticate_user!, :except => [:csgi]
before_action :check_csgi, :only => [:csgi]
# ...
private
def check_csig
#check if csgi is valid
end

How to differentiate a rails / devise login if it is made by providing password or via cookie?

It seems the most close method is to hook the devise model User to override after_database_authentication method.
But within that model method I was unable to send a flag to the controller which handles the root route. (Somehow current_user could not reach atr_accessor enabled parameter).
So wrapping up, what is the most rails way to differentiate devise authentication, if it is made by providing a password or if it is made by a cookie?
In the rails MVC model the model is not session aware. Your User model thus does and should not know if there is signed in user.
If you want to differentiate from when the user is redirected from the sign in vs other hits on the root path you can set a value in the session:
# routes.rb
devise_for :users, controllers: { sessions: 'sessions' }
# app/controllers/sessions_controller.rb
class SessionsController < Devise::SessionsController
def create
super do
session[:just_signed_in] = true
end
end
end
# this would be whatever controller you have that handles the route path
class HomeController
after_action :cleanup!
def index
if session[:just_signed_in]
# ...
else
# ...
end
end
private
def cleanup!
session.delete(:just_signed_in)
end
end
Another way to do this is by adding a query param to the redirect path after sign in.

disable all logins except admin in ruby on rails

while doing admin work, i'd like to disable user logins --
is there some way to use devise for this -- I don't THINK this
is suitable for rolify -- because this is a temporary disablement --
thanks in advance for any help,
rick
Back-End
If you wanted to create a "maintenance" mode, you'll be best doing something like this:
#app/models/user.rb
class User < ActiveRecord::Base
end
#app/models/admin.rb
class Admin < User
def maintainance!
self.toggle! :maintainance
end
end
This will need a maintenance column in the users table, and you'll have to add a type column in the users table, too.
You could get away with keeping this in the User model, however, you'd need some conditions to determine whether the user is an admin. Since you didn't specify how you're differentiating, above is how we do it.
--
You'd be able to call it like this:
#app/controllers/users_controller.rb
class SettingsController < ApplicationController
before_action :authenticate_user!
def maintenance
current_user.maintenance! #-> toggles so you'll just be able to call this as you need.
end
end
#config/routes.rb
resources :settings, only: [] do
put :maintenance #-> url.com/settings/maintenance (considering current_user present)
end
This will allow you to set the "maintenance" mode through your user settings area. If you don't have one, you'll be able to use the above code to get it working.
Front-End
With the backend in place, you'll be able to then manage the front-end.
To do this, you'll need a helper to determine if any user has set the "maintenance" mode...
#app/helpers/application_helper.rb
class ApplicationHelper
def maintenance_mode?
Admin.exists? maintenance: true
end
end
This will allow you to use this helper to determine whether you should allow Devise to accept logins or not:
#app/views/devise/sessions/new.html.erb
<% unless maintenance_mode? %>
... devise form ...
<% end %>
The helper will execute a DB request, but keeping it in the devise areas only (IE it's not "site wide") should make it okay.
#app/controllers/devise/sessions_controller.rb
class SessionsController < Devise::SessionsController
before_action :check_maintenance
private
def check_maintenance
redirect_to root_path, notice: "Sorry, maintenance mode is in effect; no logins." if maintenance_mode?
end
end
This will prevent any controller-based actions from firing.
Finally, if you want to get rid of any logged-in users, you'll need to do something quirky, like resetting the sessions or something similar:
How can I reset all devise sessions so every user has to login again?
Devise force signout
Here's what I'd do:
1. Create a method for your User model. It could be something like active, or able_to_login.
2. Set this attribute to :boolean.
3. Use rails console. Use the console to set the active method to true or false, enabling or disabling your users to access your application:
user = User.all
user.each do |u|
u.active = false # or
u.able_to_login = false
u.save
end
I don't think this is the best method, but it should work without installing another gem or heavy code.
In your /models/user.rb add this method
def active_for_authentication?
super && is_admin?
end
def is_admin?
# returns true if user is admin
end
This is the "Devise way" of doing this :)

How to add this specific authorization feature to my rails app?

My rails app has a few cab operators and they have a few cabs associated with them, and they are related as follows:
class Operator < ActiveRecord::Base
has_many :cabs
end
I have used Devise as my authentication gem. It authenticates users, admins and super admins in my app. I have created separate models for users, admins and super admins (and have not assigned roles to users per se).
I now wish to add the authorization feature to the app, so that an admin (who essentially would be the cab operator in my case) can CRUD only its own cabs. For e.g., an admins belonging to operator# 2 can access only the link: http://localhost:3000/operators/2/cabs and not the link: http://localhost:3000/operators/3/cabs.
My admin model already has an operator_id that associates it to an operator when an admin signs_up. I tried to add the authorization feature through CanCan, but I am unable to configure CanCan to provide restriction such as the one exemplified above.
I also tried to extend my authentication feature in the cabs_controller, as follows:
class CabsController < ApplicationController
before_action :authenticate_admin!
def index
if current_admin.operator_id != params[:operator_id]
redirect_to new_admin_session_path, notice: "Unauthorized access!"
else
#operator = Operator.find(params[:operator_id])
#cabs = Operator.find(params[:operator_id]).cabs
end
end
But this redirects me to the root_path even if the operator_id of the current_admin is equal to the params[:operator_id]. How should I proceed?
EDIT:
Following is my routes.rb file:
Rails.application.routes.draw do
devise_for :super_admins
devise_for :users
resources :operators do
resources :cabs
end
scope "operators/:operator_id" do
devise_for :admins
end
end
I have three tables: users, admins and super_admins. I created these coz I wanted my admins to hold operator_ids so that the admins corresponding to an operator can be identified. Also, I wanted the admin sign_in paths to be of the type /operators/:operator_id/admins/sign_in, hence the tweak in the routes file.
Unfortunately, initially I didn't understand that you actually have 3 different tables for users and (super)admins... Not sure that Pundit can help you in this case, but I'll keep the old answer for future visitors.
Coming back to your problem, let's try to fix just the unexpected redirect.
Routes seems fine, so the problem can be one of this:
You're getting redirected because you're currently not logged in as an admin, so you don't pass the :authenticate_admin! before_action.
You say "even if the operator_id of the current_admin is equal to the params[:operator_id]", but this condition is probably not true. Can you debug or print somewhere the value of both current_admin.operator_id and params[:operator_id] to see if they're actually equals?
Another interesting thing, is that you have a redirect for new_admin_session_path in your code, but then you say "this redirects me to the root_path". Can you please double check this?
OLD ANSWER
If you want to setup a good authorization-logic layer, I advice you to use pundit.
You've probably heard about cancan, but it's not supported anymore...
Leave Devise managing only the authentication part and give it a try ;)
PUNDIT EXAMPLE
First of all, follow pundit installation steps to create the app/policies folder and the base ApplicationPolicy class.
Then, in your case, you'll need to create a CabPolicy class in that folder:
class CabPolicy < ApplicationPolicy
def update?
user.is_super_admin? or user.cabs.include?(record)
end
end
This is an example for the update action. The update? function have to return true if the user has the authorisation to update the cab (You'll see later WHICH cab), false otherwise. So, what I'm saying here is "if the user is a super_admin (is_super_admin? is a placeholder function, use your own) is enough to return true, otherwise check if the record (which is the cab your checking) is included in the cabs association of your user".
You could also use record.operator_id == record.id, but I'm not sure the association for cab is belongs_to :operator. Keep in mind that in CabPolicy, record is a Cab object, and user is the devise current_user, so implement the check that you prefer.
Next, in your controller, you just need to add a line in your update function:
def update
#cab = Cab.find(params[:id]) # this will change based on your implementation
authorize #cab # this will call CabPolicy#update? passing current_user and #cab as user and record
#cab.update(cab_params)
end
If you want to make things even better, I recommend you to use a before_action
class CabsController < ApplicationController
before_action :set_cab, only: [:show, :update, :delete]
def update
#cab.update(cab_params)
end
#def delete and show...
private
def set_cab
#cab = Cab.find(params[:id])
authorize #cab
end
And of course, remember to define also show? and delete? methods in your CabPolicy.

Rails 3 authorization with default auth

I working on an app with user authorization. It has a List and User classes. The authentication was built with Ryan Bates http://railscasts.com/episodes/270-authentication-in-rails-3-1
I'm not sure about authorization process. I read about cancan gem. But i could not understand.
I want to achieve this:
User only able to view/edit/delete his own list.
User only able to view/edit/delete his own profile(user class).
I don't implement user level right now. No guess or admin.
How to use before_filter method in list and User controller with current_user instance?
Since you are defining current_user in the application controller, this is easy. You can use before_filter like this in the Users controller:
class ItemsController < ApplicationController
before_filter :check_if_owner, :only => [:edit, :update, :show, :destroy]
def check_if_owner
unless current_user.admin? # check whether the user is admin, preferably by a method in the model
unless # check whether the current user is the owner of the item (or whether it is his account) like 'current_user.id == params[:id].to_i'
flash[:notice] = "You dont have permission to modify this item"
redirect_to # some path
return
end
end
end
###
end
You should add a similar method to UsersController to check if it is his profile, he is editing.
Also, have a look at Devise which is the recommended plugin for authentication purposes.
For this I'd not use devise. It's way to much for this simple use.
I'd make a seperate controller for the public views and always refere to current_user
Remember to make routes for the actions in the PublicController
class PublicController < ApplicationController
before_filter :login_required?
def list
#list = current_user.list
end
def user
#user = current_user
end
def user_delete
#user = current_user
# do your magic
end
def user_update
#user = current_user
# do your magic
end
# and so on...
end

Resources