Rails Basic Authorization (after Basic Authentication)
Hi! The hobby developer is back. I've been doing RoR since version 0.9. I have a few semi-private apps deployed and try to keep up with versions. When DHH released the Basic Authentication Scaffold I played with it and got it working. It did bother me that there is stuff in there that I have no idea on what it does. Like: def start_new_session_for(user) user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session| Current.session = session cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax } end end Have no idea what .tap does. Anyhow it works! Now on to a new challenge - Authorization. I only have one app that has some form of role based Authorization. It's a golf group management app. It just kept track of groups, players, points, quotas etc. Kinda like a Handicap. I have several groups that use it. Each group has a user/manager that can do anything and a few that can do most things - form team - score teams etc. It turned out that they just share the User account and have two or three players that can do anything! I think I tried Devise but didn't like the pain. Later tried CanCan and then CanCanCan. It was ok, but then I rolled my own. Didn't have much to do so I spent the last week rolling another version! I forgot to say that I just turned 80 and don't have much to do. Just though I'd share it. Much like CanCanCan it has an Ability scheme that defines who can do what. In mine it's just a Constant wrapped in a class. class Can CRUD = { super:{ group:'1111', user:'1111', player:'1111', game:'1111', round:'1111', article:'1111' }, manager:{ group:'0110', user:'1111', player:'1111', game:'1111', round:'1111' }, trustee:{ group:'0100', user:'0110', player:'0110', game:'0110', round:'0110', }, ... other roles } def self.can(role) # called from User model - returns user roles cans = CRUD[role.downcase.to_sym] end ... some other utilities end The CRUD hash defines what Roles are used and what CRUD action each model implements. Each model defines a 4 character string of 0's or 1's - which equates to True or False. The manager role above: cannot Create or Destroy a Group, only Read or Update a group. Kind of simple . Every thing is controlled from the User model: class User (e) { e.strip.downcase } attribute :permits after_initialize :set_attributes def set_attributes self.permits = Can.can(self.role) unless self.role.blank? end def can?(action,model) return false if self.role.nil? || self.permits.nil? action = action.to_s.downcase model = model.to_s.downcase permit = permits[model.to_sym] return false if permit.nil? if ['create','new'].include?(action) return permit[0] == '1' elsif ['index','show','read'].include?(action) return permit[1] == '1' elsif ['edit','update'].include?(action) return permit[2] == '1' elsif ['delete','destroy'].include?(action) return permit[3] == '1' else return false end end end The User model has an attribute:permits defined and set after_initialize by calling set_attributes. set_attributes extracts the CRUD hash for the User's role and stuffs it into the permits attribute. It is there for the duration of the Session. Authorization takes place in the Model controller or a View. You add 9 lines to each Controller that is going to use Authorization: class UsersController

Hi! The hobby developer is back.
I've been doing RoR since version 0.9. I have a few semi-private apps deployed and try to keep up with versions.
When DHH released the Basic Authentication Scaffold I played with it and got it working. It did bother me that there is stuff in there that I have no idea on what it does. Like:
def start_new_session_for(user)
user.sessions.create!(user_agent: request.user_agent, ip_address: request.remote_ip).tap do |session|
Current.session = session
cookies.signed.permanent[:session_id] = { value: session.id, httponly: true, same_site: :lax }
end
end
Have no idea what .tap does. Anyhow it works! Now on to a new challenge - Authorization.
I only have one app that has some form of role based Authorization. It's a golf group management app. It just kept track of groups, players, points, quotas etc. Kinda like a Handicap. I have several groups that use it. Each group has a user/manager that can do anything and a few that can do most things - form team - score teams etc. It turned out that they just share the User account and have two or three players that can do anything!
I think I tried Devise but didn't like the pain. Later tried CanCan and then CanCanCan. It was ok, but then I rolled my own. Didn't have much to do so I spent the last week rolling another version! I forgot to say that I just turned 80 and don't have much to do. Just though I'd share it.
Much like CanCanCan it has an Ability scheme that defines who can do what. In mine it's just a Constant wrapped in a class.
class Can
CRUD = {
super:{
group:'1111',
user:'1111',
player:'1111',
game:'1111',
round:'1111',
article:'1111'
},
manager:{
group:'0110',
user:'1111',
player:'1111',
game:'1111',
round:'1111'
},
trustee:{
group:'0100',
user:'0110',
player:'0110',
game:'0110',
round:'0110',
},
... other roles
}
def self.can(role)
# called from User model - returns user roles
cans = CRUD[role.downcase.to_sym]
end
... some other utilities
end
The CRUD hash defines what Roles are used and what CRUD action each model implements. Each model defines a 4 character string of 0's or 1's - which equates to True or False. The manager role above: cannot Create or Destroy a Group, only Read or Update a group. Kind of simple .
Every thing is controlled from the User model:
class User < ApplicationRecord
has_secure_password
has_many :sessions, dependent: :destroy
normalizes :email_address, with: ->(e) { e.strip.downcase }
attribute :permits
after_initialize :set_attributes
def set_attributes
self.permits = Can.can(self.role) unless self.role.blank?
end
def can?(action,model)
return false if self.role.nil? || self.permits.nil?
action = action.to_s.downcase
model = model.to_s.downcase
permit = permits[model.to_sym]
return false if permit.nil?
if ['create','new'].include?(action)
return permit[0] == '1'
elsif ['index','show','read'].include?(action)
return permit[1] == '1'
elsif ['edit','update'].include?(action)
return permit[2] == '1'
elsif ['delete','destroy'].include?(action)
return permit[3] == '1'
else
return false
end
end
end
The User model has an attribute:permits
defined and set after_initialize by calling set_attributes.
set_attributes extracts the CRUD hash for the User's role and stuffs it into the permits attribute. It is there for the duration of the Session.
Authorization takes place in the Model controller or a View.
You add 9 lines to each Controller that is going to use Authorization:
class UsersController < ApplicationController
before_action :set_auth
before_action :set_user, only: %i[ show edit update destroy ]
# GET /users or /users.json
def index
@users = User.all
end
...
private
def set_auth
if ['index','show','new','edit','create','update','destroy'].include?(params["action"])
permitted = Current.user.can?(params["action"],"user")
unless permitted
redirect_to dashboard_path, alert: "I'm sorry, but you can't do that!"
end
end
end
...
The before_action :set_auth
calls the User.can?
method with the params['action'] and the model name. Only the basic crud actions are filtered. Not sure if other action need to be included?? or needed?? Remember Simple!
On second though, you can wrap a controller action in a if/else statement or begin/rescue/end statement. Just Current.user.can?(:update,:model)
It's in the controller to prevent someone, who is not signed in, the typing in the browser /article/5/edit
On the View side you add code to links or buttons to hide them. You can add a helper to the application_controller and use it..
def can?(action,model)
Current.user && Current.user.can?(action,model)
end
helper_method :can?
Then add it to links in the view:
div
= link_to icon('fas fa-eye',"Show User"), user,class:"btn-sm-green mr-4"
= link_to icon('fas fa-edit',"Edit User"), edit_user_path(user),class:"btn-sm-orange mr-4" if can?('edit',"user")
Going back to the User model, the can? method is called with an action and a model.
The action is more or less converted to CRUD. The show and index actions are just the R in CRUD or Read. That is done in the can? method.
Again I'm just a hobby developer. This was just a sharing effort. Maybe it will give someone some ideas.