I have implemented Devise for Authentication and Authorization in ROR application everything seems fine but getting one issue.
I have two modals "Account" and "Transactiona" , and so two controllers respectively.
My Transaction Index view call one of Account Controller method like this
$.post("accounts/our_miles_balance/?account_number="+$("#account_number").val(),function(data)
{
$("#our_miles_balance").val(data);
});
When this ajax post run it gives following error and sign out admin user
You need to sign in or sign up before continuing
Here is my Ability Class
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user
if user.role == 1 #admin
can :manage, :all
can :read, :all
elsif user.role == 2 #Vendor
can :manage, VendorTransaction
can :index, Account
end
end
end
What i m doing wrong her, please help....
Edit
Ok Here is my Transaction controller
require 'csv'
class TransactionsController < ApplicationController
load_and_authorize_resource
helper_method :sort_column, :sort_direction
respond_to :html, :js
def index
per_page = 40
#transactions = Transaction.search(params[:id]).order(sort_column + " " + sort_direction)
respond_to do |format|
format.html # index.html.erb
format.csv { render :csv => #transactions}
end
AND Account Controller
class AccountsController < ApplicationController
load_and_authorize_resource
helper_method :sort_column, :sort_direction
def index
#accounts = Account.search(params[:program_id]
respond_to do |format|
format.html # index.html.erb
format.json { render :json => #accounts}
end
def our_miles_balance
a = Account.find_by_account_number(params[:account_number])
#miles = Account.our_miles_balance(a.id) if ?a!=nil
respond_to do |format|
format.json { render json: #miles}
end
end
end
When using load_and_authorize_resource, it's calling authorize!(:our_miles_balance, #account) before the our_miles_balance controller action. Documentation.
Option 1
Add
can :our_miles_balance, Account
to your ability class.
Option 2
In the controller do
load_and_authorize_resource :except => :our_miles_balance
and in the our_miles_balance action do
authorize! :read, #account
Related
I am currently building a simple web app with Ruby on Rails that allows logged in users to perform CRUD actions to the User model. I would like to add a function where:
Users can select which actions they can perform per controller;
Ex: User A can perform actions a&b in controller A, whereas User B can only perform action B in controller A. These will be editable via the view.
Only authorized users will have access to editing authorization rights of other users. For example, if User A is authorized, then it can change what User B will be able to do, but User B, who is unauthorized, will not be able to change its own, or anyone's performable actions.
I already have my users controller set up with views and a model
class UsersController < ApplicationController
skip_before_action :already_logged_in?
skip_before_action :not_authorized, only: [:index, :show]
def index
#users = User.all
end
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to users_path
else
render :new
end
end
def show
set_user
end
def edit
set_user
end
def update
if set_user.update(user_params)
redirect_to user_path(set_user)
else
render :edit
end
end
def destroy
if current_user.id == set_user.id
set_user.destroy
session[:user_id] = nil
redirect_to root_path
else
set_user.destroy
redirect_to users_path
end
end
private
def user_params
params.require(:user).permit(:email, :password)
end
def set_user
#user = User.find(params[:id])
end
end
My sessions controller:
class SessionsController < ApplicationController
skip_before_action :login?, except: [:destroy]
skip_before_action :already_logged_in?, only: [:destroy]
skip_before_action :not_authorized
def new
end
def create
user = User.find_by(email: params[:email])
if user && user.authenticate(params[:password])
session[:user_id] = user.id
redirect_to user_path(user.id), notice: 'You are now successfully logged in.'
else
flash.now[:alert] = 'Email or Password is Invalid'
render :new
end
end
def destroy
session[:user_id] = nil
redirect_to root_path, notice: 'You have successfully logged out'
end
end
The login/logout function works, no problem there.
I started off by implementing a not_authorized method in the main application controller which by default prevents users from accessing the respective actions if the user role is not equal to 1.
def not_authorized
return if current_user.nil?
redirect_to users_path, notice: 'Not Authorized' unless current_user.role == 1
end
the problem is that I would like to make this editable. So users with role = 1 are able to edit each user's access authorization, if that makes sense.
How would I go about developing this further? I also do not want to use gems, as the sole purpose of this is for me to learn.
Any insights are appreciated. Thank you!
The basics of an authorization system is an exception class:
# app/errors/authorization_error.rb
class AuthorizationError < StandardError; end
And a rescue which will catch when your application raises the error:
class ApplicationController < ActionController::Base
rescue_from 'AuthorizationError', with: :deny_access
private
def deny_access
# see https://stackoverflow.com/questions/3297048/403-forbidden-vs-401-unauthorized-http-responses
redirect_to '/somewhere', status: :forbidden
end
end
This avoids repeating the logic all over your controllers while you can still override the deny_access method in subclasses to customize it.
You would then perform authorization checks in your controllers:
class ThingsController
before_action :authorize!, only: [:update, :edit, :destroy]
def create
#thing = current_user.things.new(thing_params)
if #thing.save
redirect_to :thing
else
render :new
end
end
# ...
private
def authorize!
#thing.find(params[:id])
raise AuthorizationError unless #thing.user == current_user || current_user.admin?
end
end
In this pretty typical scenario anybody can create a Thing, but the users can only edit things they have created unless they are admins. "Inlining" everything like this into your controllers can quickly become an unwieldy mess through as the level of complexity grows - which is why gems such as Pundit and CanCanCan extract this out into a separate layer.
Creating a system where the permissions are editable by users of the application is several degrees of magnitude harder to both conceptualize and implement and is really beyond what you should be attempting if you are new to authorization (or Rails). You would need to create a separate table to hold the permissions:
class User < ApplicationRecord
has_many :privileges
end
class Privilege < ApplicationRecord
belongs_to :thing
belongs_to :user
end
class ThingsController
before_action :authorize!, only: [:update, :edit, :destroy]
# ...
private
def authorize!
#thing.find(params[:id])
raise AuthorizationError unless owner? || admin? || privileged?
end
def owner?
#thing.user == current_user
end
def admin?
current_user.admin?
end
def privileged?
current_user.privileges.where(
thing: #thing,
name: params[:action]
)
end
end
This is really a rudimentary Role-based access control system (RBAC).
I have a link in my application.html.erb file
<%= link_to 'Sprzedaż', sell_path, remote: true %>
In the controller I authenticate user with before_action :authenticate_user!. Below is my authenticate_user! method.
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
Basically it works correctly if the user isn't authorized. If the user has enabled Javascript it shows nice notification, and if the user hasn't enabled Javascript it shows alert and redirect to root_path which is good. The problem is that when the user is signed in and click the link nothing happens. It should redirect to the sell_path.
This is my ItemsController
class ItemsController < ApplicationController
before_action :authenticate_user!
def sell
#user = current_user
#items = JSON.parse(HTTParty.get("http://steamcommunity.com/profiles/#{#user.uid}/inventory/json/730/2?l=polish").body)
end
end
This is my ApplicationController
class ApplicationController < ActionController::Base
# Prevent CSRF attacks by raising an exception.
# For APIs, you may want to use :null_session instead.
protect_from_forgery with: :exception
before_action :steam_informations
def steam_informations
#steam = session[:steam]
end
protected
def authenticate_user!
if user_signed_in?
super
else
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
end
end
You are trying to override helpers which defined in runtime. This is not how you must to do it.
In your case I recommend you to define for example authenticate! method like this:
def authenticate!
return true if user_signed_in?
respond_to do |format|
format.js { render nothing: true, status: :unauthorized }
format.html { redirect_to root_path, :alert => 'Aby przejść do tej strony, musisz być zalogowany.' }
end
end
This method will do nothing if user signed in and redirect to root page if user not signed in. Just define this method in ApplicationController and then use before_filter :authenticate! hook to execute it.
When before_filter \ before_action method returns false - rails won't execute your action. And if return true or just return - rails will execute action after hooks like authenticate! and render your views. To make it clear I'll show you some examples.
class FooController << ApplicationController
before_filter :dead_hook, only: :index
before_filter :nice_hook, only: :show
def index
# this action will be never executed because dead_hook method returns false.
end
def show
# this action will be executed right after nice_hook method because of 'return true' command in nice_hook method
end
def dead_hook
return false
end
def nice_hook
return true
end
end
Another way to do just like you trying to do - monkey-patch devise helper. You can do it like this:
module Devise
module Controllers
module Helpers
def authenticate_user!
# implement your logic here
end
end
end
end
Here you can check out whats going on in devise helpers:
Github Devise Helpers source code
Just for clarification: there is no difference between before_filter and before_action. Feel free to use any of them. before_action newer but before_filter not deprecated.
Currently I'm implementing a simple demo for a school management system. After I login I get the following exception:
undefined method `role' for nil:NilClass
app/models/ability.rb:5:in `initialize'
app/controllers/application_controller.rb:10:in `new'
app/controllers/application_controller.rb:10:in `current_ability'
Here's ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
if user.role.name=='admin'
can :manage, :all
end
if user.role.name=='teacher'
can :read, Course
end
end
end
Here's application_controller.rb:
def set_current_user(user)
#current_user=user
end
def current_ability
#current_ability||= Ability.new(current_user)
end
def current_user
#current_user
end
I authenticate the user in the users_controller.rb as:
def authenticate
#user=User.find_by_name_and_password(params[:name],params[:password])
if #user
set_current_user(#user)
respond_to do |format|
format.html { redirect_to courses_path }
format.json { head :no_content }
end
end
end
finally here you are the method index in courses_controllers.rb, it's very simple as you can see
class CoursesController < ApplicationController
load_and_authorize_resource
def index
#courses = Course.all
respond_to do |format|
format.html # index.html.erb
format.json { render json: #courses }
end
end
end
Any help would be greatly appreciated!
The error tells you that "current_user" is nil, and this is the case.
I assume that you authenticate users and then redirect them to courses_path, and it's all fine because you actually assign an instance of User to current_user. However, after the redirect it doesn't know about user and seems like session is not created.
I'd recommend you to assign session[:user_id] in authenticate, therefore you will be able to allow current_user return an instance of user during the session.
Also, you might just use Devise to handle all this logic for you.
I have a Ckeditor textarea in Rails 4, using gem "ckeditor". All works fine, and it's placed in an administration interface. So, when I click 'browse server' to upload the assets, it links to the url:
http://localhost:3000/ckeditor/pictures?CKEditor=skill_description&CKEditorFuncNum=1&langCode=es
The problem is that I don't want anybody to be able to access this page, only the administrator. So I use the gem cancan (supported by the ckeditor gem) to do it.
class Ability
include CanCan::Ability
def initialize(user)
can :access, :ckeditor # needed to access Ckeditor filebrowser
can [:access, :read, :create, :destroy], Ckeditor::Picture
can [:access, :read, :create, :destroy], Ckeditor::AttachmentFile
end
end
The problem is that the logic for cancan goes in a Model, so I can't get sessions there. If a make a before_filter in ApplicationController, somehow the app doesn't pass through it when going to the previous url. I think that with cancan the best way is to create a User model and create a is_admin field, but that's no what I want at this moment. Any ideas for how to handle this?
I found a solution for this issue.
Create a controller under controller with name ckeditor/pictures_controller.rb.
class Ckeditor::PicturesController < Ckeditor::ApplicationController
before_action: something
def index
#pictures = Ckeditor.picture_adapter.find_all(ckeditor_pictures_scope)
#pictures = Ckeditor::Paginatable.new(#pictures).page(params[:page])
respond_to do |format|
format.html { render :layout => #pictures.first_page? }
end
end
def create
#picture = Ckeditor.picture_model.new
respond_with_asset(#picture)
end
def destroy
#picture.destroy
respond_to do |format|
format.html { redirect_to pictures_path }
format.json { render :nothing => true, :status => 204 }
end
end
protected
def find_asset
#picture = Ckeditor.picture_adapter.get!(params[:id])
end
def authorize_resource
model = (#picture || Ckeditor.picture_model)
#authorization_adapter.try(:authorize, params[:action], model)
end
end
Or you can find the ckeditor controller in Ruby2.1.0\lib\ruby\gems\x.x.x\gems\ckeditor-x.x.x\app\controllers\ckeditor. In there, you can custom it.
I am using authlogic and cancan on a rails 3 application, I want to allow all logged in users to access the users index page, i have tried something like this but it dosent seem to be working:
ability class:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new
can :index, User if UserSession.find
can :read, User if UserSession.find
end
Controller:
def index
#users = User.search(params[:search]).order('username').page(params[:page]).per(1)
authorize! :index, #users
end
def show
#user = User.find(params[:id])
authorize! :read, #user
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #user }
end
end
thanks
I find it's easier to use load_and_authorize_resource at the top of my controllers. Then your ability class contains all the ability logic instead of having it strewn about your controllers.
ability.rb
class Ability
include CanCan::Ability
def initialize(user)
if user
can :index, User
can [:show, :edit, :update, :destroy], User, :id => user.id
end
end
end
users_controller.rb
class UsersController < ApplicationController
load_and_authorize_resource
def index
#users = User.search(params[:search]).order('username').page(params[:page]).per(1)
end
def show
end
...
end
I haven't used authlogic in a while as I tend to use devise now, so I'm not sure if my sample code is authlogic ready. If you don't want to use load_and_authorize_resource, my code shows how to limit what users can see in the ability class, but in your code I'd change :read to :show.
Continuing from my comment, the problem was in the following code
authorize! :index, #users
Here, you're passing an Array of users to the CanCan's method, while your can :index, User declaration defines the authorization for a User object.