rails administrate with cancancan - ruby-on-rails-5

Im using rails administrate for my application, but I want to limit access via the administrate dashboard to the resources being administered.
Im also using cancancan in the other parts of my rails app to manage access and permissions.
Has anyone managed to use cancancan within administrate, so that the administrate dashboard can use the abilities defined in cancancan, do display the resources and apply the same persmissions ?
Thanks

You can find some info about what needs to be done here: https://administrate-prototype.herokuapp.com/authorization
What is mentioned there works well for filtering collections of records, but breaks when trying to authorize individual resources. The solution is to override the find_resource method. Here is the final working code:
# app/controllers/admin/application_controller.rb
rescue_from CanCan::AccessDenied do |exception|
flash[:notice] = "Access Denied"
redirect_to admin_root_path
end
# Override find_resource, because it initially calls scoped_resource.find(param)
# which breaks since we are overriding that method as well.
def find_resource(param)
resource_class.default_scoped.find(param)
end
# Limit the scope of the given resource
def scoped_resource
super.accessible_by(current_ability)
end
# Raise an exception if the user is not permitted to access this resource
def authorize_resource(resource)
raise CanCan::AccessDenied unless show_action?(params[:action], resource)
end
# Hide links to actions if the user is not allowed to do them
def show_action?(action, resource)
# translate :show action to :read for cancan
if ["show", :show].include?(action)
action = :read
end
can? action, resource
end
This will get you started for basic resource authorization with CanCan. Further customization of field views might be needed if you need to restrict access to nested resources etc. But that should be pretty standard from that point forward. Hope this helps. :)

Related

Using before_filter to check that an admin or a user is signed in

Is it possible to use Ruby on Rails' before_filter method to check that one of multiple things is true? Specifically, I am using Devise in which I have defined a user and an admin and I would like to ensure that whoever is accessing a specific controller is one or the other. I have looked for answers in documentation and SO and haven't had any luck.
Thanks, here are my versions (if it helps):
Ruby 1.9.3
Rails 3.2.6
Devise 2.2.3
It is possible. I'm hesitant to actually place this as an answer because it doesn't get any simpler than this. I am assuming that you have an admin? method that returns true or false depending on the user's role.
before_filter :check_if_admin
protected
def check_if_admin
if signed_in?
raise 'Only admins allowed!' unless current_user.admin?
else
# or you can use the authenticate_user! devise provides to only allow signed_in users
raise 'Please sign in!'
end
end

Merging ActiveAdmin users with existing user model

I've set up ActiveAdmin early in my project and used the default admin_users model for authentication. I've since used Devise to set up a separate User model and have realized it would probably be much smarter to merge the two tables, such that an Administrator can have administrative actions both in Activeadmin and in the front end of the site. How can I configure ActiveAdmin to use the Users model with maybe a column to flag an administrator (eg. is_admin or event a permissions level to make Administrators and Moderators)?
Rails 3.1
ActiveAdmin 0.3.3
Devise 1.4.9
For a quick code block of how to do this using an existing "User" model with activeadmin, the answer is actually really easy. In the ApplicationController:
class ApplicationController < ActionController::Base
def authenticate_admin_user! #use predefined method name
redirect_to '/' and return if user_signed_in? && !current_user.is_admin?
authenticate_user!
end
def current_admin_user #use predefined method name
return nil if user_signed_in? && !current_user.is_admin?
current_user
end
end
And just use what Devise already has set up for authentication. The redirect_to is where you want to send users who ARE signed in and DO NOT have administrative privileges.
ActiveAdmin let's you define your own authentication methods. You can migrate your user tables to have an additional admin column and mark the existing admins as such in it, then set your authentication methods (as specified) in config/initializers/active_admin.rb.

cancan - "can :manage, all". I haven't been able to get access to all in rails 3 app with devise

I am using "check authorization" in the application controller so every action will require a permission. I'm starting with giving me, the superadmin :=], permissions to manage all. I thought manage all would give me access to the whole app without naming a resource.
user model:
def role?(role)
roles.include? role.to_s
end
application controller:
check_authorization
cancan's ability model:
def initialize(user)
if user.role? :superadmin
can :manage, :all
end
end
error message:
This action failed the check_authorization because it does not authorize_resource. Add skip_authorization_check to bypass this check.
Thank you.
As far as I am aware, you're going to need to call authorize_resource in your controller as a before filter so that this works.

Rails 3 and Devise: authenticating a user while performing a POST action

Using Rails 3.0.6, Omniauth 0.2.0 and Devise 1.2.1, I'm encountering the following situation:
I want to offer users the option to authenticate via Facebook. I have a user system set up using Devise and I can successfully auth using Facebook. I've spent several hours trying to code the behavior I want for one specific situation:
user is not logged in
user has a site account
user authenticates via Facebook
I offer the user 2 choices at this point
create an account (can be a dummy account with no provided info)
link this Facebook authentication with an existing account
I'm having trouble with the latter option. The user has already authenticated but I still need him to log in with his site account. I have an action in my AuthenticationsController that will associate this authentication with a logged in user. Devise doesn't seem to offer a way for me to log the user in while staying in the same action, though. This was my first attempt to do this
class AuthenticationsController < ApplicationController
before_filter :authenticate_user!, :only => :auth_link_existing_user
...
def auth_link_existing_user
...
end
However, using this method, if the user logs in, they're simply redirected to my site's root page. I know I can change Devise's sign-in redirect, but that will be for all sign-ins. I wanted only this situation to have a separate redirect.
After reading through this mailing list question, I tried to extend SessionsController with my own custom behavior:
def create
resource = warden.authenticate!(:scope => resource_name, :recall => "#{controller_path}#new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(resource_name, resource)
if params[:redirect] #new
redirect_to params[:redirect].to_sym #new
else
respond_with resource, :location => redirect_location(resource_name, resource)
end
end
This doesn't work either. I've defined my auth_link_existing_user route to use a POST verb (which seems accurate) and redirects can only be GETs.
So now I do have a solution in mind: copy and paste code from Devise's authenticate_user! helper into a new function which can be called within a controller action without redirecting. This seems less than ideal to me because it's duplication of code and increases coupling--a Devise or Warden update that changes this behavior will break my code as well.
Has anyone else tried something like this and come up with a more elegant solution? Do you see a simpler way for me to offer this or similar behavior to my users?
UPDATE: For anyone who wants to use my dirty solution at the end, this is what I did:
def auth_link_existing_user
# FROM Devise/sessions/create
resource = warden.authenticate!(:scope => :user, :recall => "registrations#auth_new")
set_flash_message(:notice, :signed_in) if is_navigational_format?
sign_in(:user, resource)
# method defined in Ryan Bates' Railscast for Omniauth w/Devise
current_user.apply_omniauth(session[:omniauth])
current_user.save
end
note that this action MUST be placed in your sessions controller. If not, Warden will give you an "invalid email/password" error. It was an incredibly long debugging process to find the source.
With this in place, I use a login form to submit to this action after the user has authenticated.
I like how clean your solution is, though it goes deeper into the stack.
Here is how I've implemented something similar by following the Devise+Omniauth Facebook example on the Devise wiki and modifying the facebook method to pass on the session information to the Login form, with something like this:
if #user.persisted?
flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
sign_in_and_redirect #user, :event => :authentication
else
session["devise.facebook_data"] = request.env["omniauth.auth"]
redirect_to new_user_session_url
end
Then, in your case, I'd check in Login controller action for session["devise.facebook_data"], submit the uid + token with the form and apply_omniauth if present.

How do I implement basic authentication with sessions in Rails?

Just learning Rails via Michael Hartl's tutorial and one of the things we have to do is implement basic authentication with sessions instead of cookies.
I am trying to find any literature online that discusses it, but can't find anything.
The Rails Guides talk about sessions from a security point of view, so they assume you have your authentication working and everything - and are just interested in securing it.
But I would like to roll my own from scratch - a very simple version, nothing fancy at all.
Can someone explain to me, how a basic authentication system would work/look like in Rails 3 or show me some articles and stuff that explain how to roll my own.
Again, doesn't have to be fancy, I just want to understand how they work.
Also, assume that a User model has been created, and user data is stored in a db. So it's just a matter of confirming that there was a successful sign in, and showing them different content.
Thanks.
I figured it out, basically in my sessions controller I did this:
class SessionsController < ApplicationController
def create
user = User.authenticate(params[:session][:email], params[:session][:password])
if user.nil?
flash.now[:error] = "Invalid email/password combination."
render 'new'
else
session[:user_id] = user.id
redirect_to user
end
end
def destroy
session[:user_id] = nil
redirect_to root_path
end
end