0
votes

I'm new to Ruby and Rails, so I'm trying to do something that might be easy on other languages but I can't deal with it in Rails:

I have designed a couple tables in the database that match each controller and action name. That way, if I wand to administer special permissions for a user, I just ad the proper action to the proper controller in the database, and with the method check_permissions, I know if the user can execute the action or is redirected to a standard "forbidden" page.

So for example, here's my code for the users CRUD:

First I created a check_permissions method in application controller, which grabs the current action, user and controller, and checks authorization rights against the DB:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  include SessionsHelper

  def self.check_permissions 
    remember_token = User.digest(cookies[:remember_token]) # encripto el token para poder buscarlo en la DB, ya que ahi se guarda encriptado.
    @current_user ||= User.find_by(remember_token: remember_token)

    redirect_to signin_path if (@current_user == nil) or (User.find_by_sql("select * from view_userpermissions where user_id = #{@current_user.id} and pagecontroller='#{self.controller_name}' and accion='#{self.action}' ").count < 1)
  end
  ...
  ...

Then in each controller, I use the before_action :check_permissions filter to redirect the user in case is not authorized to access the required content:

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  before_action self.comprobar_permisos 

  def index
    @users = User.paginate(page: params[:page])
  end
  ...
  ...

The code flow seems to work very well, but the problem is rails cookies hash is not recognized when before_action filter is called.

I also tried using session, but happens exactly the same.

The first thing that comes to my mind is that in Rails architecture, neither session or cookies hashes are instanced by the moment the filter is executed, and then when the application controller tries to access them, an error is displayed:

Routing Error

undefined local variable or method `cookies' for UsersController:Class

Rails.root: C:/Users/daniel.tenzi/proyectos/MicroEstudio

Application Trace | Framework Trace | Full Trace 
    app/controllers/application_controller.rb:11:in `check_permissions'
    app/controllers/users_controller.rb:3:in `'
    app/controllers/users_controller.rb:1:in `'

Routes 

Routes match in priority from top to bottom 


Helper

HTTP Verb

Path

Controller#Action


Path / Url 

pagecontrollers_path  GET  /pagecontrollers(.:format)  pagecontrollers#index  
 POST  /pagecontrollers(.:format)  pagecontrollers#create  
new_pagecontroller_path  GET  /pagecontrollers/new(.:format)  pagecontrollers#new  
edit_pagecontroller_path  GET  /pagecontrollers/:id/edit(.:format)  pagecontrollers#edit  
pagecontroller_path  GET  /pagecontrollers/:id(.:format)  pagecontrollers#show  
 PATCH  /pagecontrollers/:id(.:format)  pagecontrollers#update  
 PUT  /pagecontrollers/:id(.:format)  pagecontrollers#update  
 DELETE  /pagecontrollers/:id(.:format)  pagecontrollers#destroy  
permissions_path  GET  /permissions(.:format)  permissions#index  
 POST  /permissions(.:format)  permissions#create  
new_permission_path  GET  /permissions/new(.:format)  permissions#new  
edit_permission_path  GET  /permissions/:id/edit(.:format)  permissions#edit  
permission_path  GET  /permissions/:id(.:format)  permissions#show  
 PATCH  /permissions/:id(.:format)  permissions#update  
 PUT  /permissions/:id(.:format)  permissions#update  
 DELETE  /permissions/:id(.:format)  permissions#destroy  
groups_path  GET  /groups(.:format)  groups#index  
 POST  /groups(.:format)  groups#create  
new_group_path  GET  /groups/new(.:format)  groups#new  
edit_group_path  GET  /groups/:id/edit(.:format)  groups#edit  
group_path  GET  /groups/:id(.:format)  groups#show  
 PATCH  /groups/:id(.:format)  groups#update  
 PUT  /groups/:id(.:format)  groups#update  
 DELETE  /groups/:id(.:format)  groups#destroy  
users_path  GET  /users(.:format)  users#index  
 POST  /users(.:format)  users#create  
new_user_path  GET  /users/new(.:format)  users#new  
edit_user_path  GET  /users/:id/edit(.:format)  users#edit  
user_path  GET  /users/:id(.:format)  users#show  
 PATCH  /users/:id(.:format)  users#update  
 PUT  /users/:id(.:format)  users#update  
 DELETE  /users/:id(.:format)  users#destroy  
empresas_path  GET  /empresas(.:format)  empresas#index  
 POST  /empresas(.:format)  empresas#create  
new_empresa_path  GET  /empresas/new(.:format)  empresas#new  
edit_empresa_path  GET  /empresas/:id/edit(.:format)  empresas#edit  
empresa_path  GET  /empresas/:id(.:format)  empresas#show  
 PATCH  /empresas/:id(.:format)  empresas#update  
 PUT  /empresas/:id(.:format)  empresas#update  
 DELETE  /empresas/:id(.:format)  empresas#destroy  
sessions_path  POST  /sessions(.:format)  sessions#create  
new_session_path  GET  /sessions/new(.:format)  sessions#new  
session_path  DELETE  /sessions/:id(.:format)  sessions#destroy  
root_path  GET  /  users#index  
signup_path  GET  /signup(.:format)  users#new  
signin_path  GET  /signin(.:format)  sessions#new  
signout_path  DELETE  /signout(.:format)  sessions#destroy  

But if my guess is true, why would Rails says routing error?

I've been stuck here at least a week trying to find anything that helps me move forward but no luck yet.

What am I doing wrong? Any help will be highly appreciated!

PS: I've seen auth frameworks like Cancan, but don't want to use any of that, this should be easier and more flexible.

Thank you!

1
As an aside, ...User.find_by_sql("select * from view_userpermissions where user_id = #{@current_user.id} and pagecontroller='#{self.controller_name}' and accion='#{self.action}' ").count < 1) can be rewritten as ... ViewUsercondition.where(user_id: @current_user.id).where(pagecontroller: self.controller_name).where(accion: self.action) This is shorter, cleaner, easier to read and survives changing the database technology should you ever need to do that.A Fader Darkly
Updated my answer with a solution, let me know if it's helpful.A Fader Darkly
Loved it, thank you Jason!DaniT

1 Answers

0
votes

You're calling cookies on the class, not an instance. This is because your method is defined as def self.xxx

Try this:

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception

  include SessionsHelper

  def check_permissions 
    ...

class UsersController < ApplicationController
  before_action :set_user, only: [:show, :edit, :update, :destroy]
  before_action :check_permissions 

  ...

You were trying to set the before_action to the result of a class method. Looking at the method, not a good idea - it was redirecting rather than generating a symbol (which before_action expects, to turn into an instance method name). Also, as you found, you don't have access to the session, request, cookies etc. in the class scope.

On another point, you shouldn't use 'or' for conditional operations, only for program flow control. For example:

if a == 1 || b == 1 # good
if a == 1 or b == 1 # bad

thing = Thing.find(id) and thing.save # good
thing = Thing.find(id) && thing.save # bad

'or' and 'and' have lower precedence than you might expect, so could screw up your conditionals.