I am trying to make an app with Rails 4 and devise and pundit (with rolify for roles)
In the:
class ApplicationPolicy
attr_reader :user, :record
def initialize(user, record)
#user = user
#record = record
end
According to this Go Rails video: https://gorails.com/episodes/authorization-with-pundit?autoplay=1
This attr_reader section in the application policy covers all of the rest of the policies - so you don't need to repeat in each one.
However, my current question is given that I use devise, should I change the application policy to use current_user instead of user? Eg:
class ApplicationPolicy
attr_reader :current_user, :record
def initialize(current_user, record)
#current_user = current_user
#record = record
end
None of the examples I've found do it this way, but I don't understand why not.
I was hoping to figure out if I'm off the the right start before I start writing rules for each policy. Does every controller action that i make a policy for need to refer to user or should I change all of them to current_user?
You can use whatever you want to do the authorization logic.
Pundit will send in current_user from your controller when you call something like authorize #object.
From in your class you will just have to do your logic with current_user instead of user.
Why would you want to change it? From your applications perspective, you are really authorizing a user to do something, not necessarily a current_user. So keeping it as user follows conventions
Related
I'm using the pundit gem and trying to figure out how to use it to prevent access to an index page that belongs to a user other than the current_user.
The examples only talk about how to scope the results to the current_user but no how to actually prevent access to the page itself if the current_user is NOT the owner of the record.
Any help appreciated
Thanks
Maybe you want something like this? (For class ModelName)
# /policies/model_name_policy.rb
class ModelNamePolicy
attr_reader :current_user, :resource
def initialize(current_user, resource)
#current_user = current_user
#resource = resource
end
def index?
current_user.authorized_to_edit?(resource)
end
end
# /models/user.rb
class User < ActiveRecord::Base
def authorized_to_edit?(resource)
admin? | (id == resource.created_by) # Or whatever method you want to call on your model to determine ownership
end
end
EDIT: Note that you will also need to call authorize from your controller to invoke the policy.
I have a Rails-Application, that serves different sites. E.g.
www.example1.com
www.example2.com
These sites are stored in the Site-model. Also I have set up a User-Role system, using Devise, Rolify, and cancancan
Now one user can have different Roles on different site. E.g he can be an Administrator on www.example1.com, but only a simple user on www.example2.com
I am loading the users permissions in the ability-model.
Now my question is: Where is this "initialize"-function called?
I need to give this function an additional parameter site_id, so that only the appropriate rights of the site are loaded, not the one of the other side.
How can I do this?
models/ability.rb
class Ability
include CanCan::Ability
# From where is this function called, and how can I adjust this call?
def initialize(user, site_id)
return false unless user.present?
user_role = user.users_roles.find_by(site_id: site_id).try(:role)
user_role.permissions.each do |p|
if p.permission_subject_id.nil?
can p.permission_action.to_sym, p.permission_subject_class.constantize
else
can p.permission_action.to_sym, p.permission_subject_class.constantize, id: p.subject_id
end
end unless user_role.nil?
end
end
That's initialized using the current_ability method. So you need to overwrite that helper in the application_controller like so
class ApplicationController < ActionController::Base
#...
private
def current_ability
#current_ability ||= Ability.new(current_user, request. original_url)
end
end
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 :)
I have a Rails 4 app. It is working with devise 3.2.3. devise is properly integrated. At this point, users can register with email and password, sign in and perform CRUD operations.
Now here is what I would like to do: Instead of having any user to sign up by themselves, I want to create an admin. The admin would retain the responsibility of creating users. I don't want users to sign up by themselves. Basically the admin will create the user, issue them their log-in credentials, and email it to them.
I read this post and similar ones in SO and in devise wikis to no avail.
I have added a boolean field to users table to identify admin users.
class AddAdminToUser < ActiveRecord::Migration
def change
add_column :users, :admin, :boolean, :default => false
end
end
I have read about managing users using cancan but I don't know how to use it to achieve my objective
The solution i'm looking for would probably require a combination of devise and cancan.
I would appreciate any guidance on this matter.
Make sure that the boolean :admin is not in your params.permit() area for strong parameters.
Use the pundit gem, it is maintained and pretty much plain old ruby objects.
Then in your UserPolicy you would do something like this
class UserPolicy < ApplicationPolicy
def create?
user.admin?
end
end
And your model would look something like this
class User < ActiveRecord::Base
def admin?
admin
end
end
Last in your controller you make sure that the user is authorized to do the action
class UserController < ApplicationController
def create
#user = User.new(user_params)
authorize #user
end
end
You would probably also want to restrict the buttons that are shown that would give access to the admin user creation section. Those can be done with pundit as well.
I've been looking around recently into Rails and notice that there are a lot of references to current_user. Does this only come from Devise? and do I have to manually define it myself even if I use Devise? Are there prerequisites to using current_user (like the existence of sessions, users, etc)?
It is defined by several gems, e.g. Devise
You'll need to store the user_id somewhere, usually in the session after logging in. It also assumes your app has and needs users, authentication, etc.
Typically, it's something like:
class ApplicationController < ActionController::Base
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end
end
This assumes that the User class exists, e.g. #{Rails.root}/app/models/user.rb.
Updated: avoid additional database queries when there is no current user.
Yes, current_user uses session. You can do something similar in your application controller if you want to roll your own authentication:
def current_user
return unless session[:user_id]
#current_user ||= User.find(session[:user_id])
end