The personal blog of Steve Alex
Gadsden, Alabama
Everyone knows engineers can’t write! I
is
an
engineeer!
But...
I Wish I Could Write!
Home
About This Site
Articles Categories
All
moneypit
code
thoughts
golf
other
rails
food
vfw
Recent Posts
Rails 8 Basic Auth - Missing Pi
March 20, 2025
test 4 col
March 11, 2025
Just a test
March 08, 2025
Bottom of the Ladder
March 08, 2025
Rail Basic Authorization (after
March 07, 2025
### 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: ```ruby 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. ```ruby 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: ```ruby 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: ```ruby 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.. ```ruby def can?(action,model) Current.user && Current.user.can?(action,model) end helper_method :can? ``` Then add it to links in the view: ```ruby 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. ***EDIT*** Using the **before_action :set_auth** stuff in the controller may not be a good idea. It may be easier add the Authorization to the controller actions. First add another helper to application_controller.rb. ```ruby def flashit(msg=nil) if msg redirect_to dashboard_path, alert: msg else redirect_to dashboard_path, alert:"I'm sorry, Dave. I'm afraid I can't do that!" end end helper_method # A little 2001: A Space Odyssey flavor! ``` Now add the can? test to each controller action that needs authorized. ```ruby # GET /articles or /articles.json def index flashit unless can?(:index,:article) @articles = Article.all end # GET /articles/1 or /articles/1.json def show flashit unless can?(:show,:article) end # GET /articles/new def new flashit unless can?(:new,:article) @article = Article.new end ... ``` You can even uses this to non-CRUD actions if they modify something.
Rail Basic Authorization (after basic Authentication!)
March 07, 2025