Using Rokku with Hanami
In my previous post I’ve described how to use Tachiban for authentication in a Hanami 1.3 app. This post will be about using my authorization gem Rokku with Hanami applications.
Authorization application elements
There are currently four main application elements that drive authorization: users, roles, policies and a custom authorization module.
1. Users
1.1 Entities
There are only two prerequisites for the user. The user entity must have an attribute of roles
, which is later on specified in the corresponding policy. Below is an example of a user entity that fulfills prerequisites for both Rokku as well as Tachiban. Notice that roles
are of type Array. They could also be a String in case users may only have one role.
Hanami::Model.migration do
change do
create_table :users do
primary_key :id
column :created_at, DateTime, null: false
column :updated_at, DateTime, null: false
column :name, String, null: false
column :surname, String, null: false
column :username, String, null: false
column :email, String, null: false, unique: true
column :password_reset_sent_at, DateTime
column :hashed_pass, String, null: false
column :token, String
column :roles, "text[]", null: false
column :active_status, TrueClass, null: false
end
end
end
I’ll be using named roles like new_user
, admin
etc. The user assigned role will be compared to the role for a specific policy.
1.2 Templates
Here is a shortened example of the new and edit templates for the user. Strings such as labels are represented by symbols to leverage the internationalization.
1.2.1 NEW template
Since the example uses the same controller for admin users and all other users, to edit the template we first need to check if the logged in user has the admin authorization to display the editing roles section for users. Then we make the required checkboxes for assigning roles.
if current_user_roles.include?("admin")
div class: "my-class" do
div class: "my-form-field" do
h4 I18n.t :sn_user_attribute_label_roles
end
div class: "my-form-field" do
check_box :roles, name: 'user[roles][]', value: 'new_user', id: 'new_user'
label I18n.t :sn_list_user_roles_new_user
end
div class: "my-form-field" do
check_box :roles, name: 'user[roles][]', value: 'some-role', id: 'some-role'
label I18n.t :sn_list_user_roles_some_role
end
end
end
1.2.1 EDIT template
Here we also check for the logged in user authorization. Then we make the required checkboxes for assigning roles with checked options for existing role assignments.
if current_user_roles.include?("admin")
div class: "my-class-row" do
div class: "my-form-field" do
h4 I18n.t :sn_user_attribute_label_roles
end
div class: "my-form-field" do
user.roles.include?('new_user')? (check_box :roles, checked: 'checked', name: 'user[roles][]', value: 'new_user') : (check_box :roles, name: 'user[roles][]', value: 'new_user')
label I18n.t :sn_list_user_roles_new_user
end
div class: "my-form-field" do
user.roles.include?('some_role')? (check_box :roles, checked: 'checked', name: 'user[roles][]', value: 'some_role') : (check_box :roles, name: 'user[roles][]', value: 'some_role')
label I18n.t :sn_list_user_roles_some_role
end
end
end
2. Policies
Each application has its own set of policies. To create a policy for the app Web
and controller Notification
we run the following command in the project root folder.
rokku -n web -p notification
Rokku creates the policy file for us. As per instruction we need to uncomment the required roles and add the roles them. In the example below the role some_user
is authorized for all actions.
module Web
class NotificationPolicy
def initialize(roles)
@user_roles = roles
# Uncomment the required roles and add the
# appropriate user role to the @authorized_roles* array.
@authorized_roles_for_new = ["some_user"]
@authorized_roles_for_create = ["some_user"]
@authorized_roles_for_show = ["some_user"]
@authorized_roles_for_index = ["some_user"]
@authorized_roles_for_edit = ["some_user"]
@authorized_roles_for_update = ["some_user"]
@authorized_roles_for_destroy = ["some_user"]
end
def new?
(@authorized_roles_for_new & @user_roles).any?
end
def create?
(@authorized_roles_for_create & @user_roles).any?
end
def show?
(@authorized_roles_for_show & @user_roles).any?
end
def index?
(@authorized_roles_for_index & @user_roles).any?
end
def edit?
(@authorized_roles_for_edit & @user_roles).any?
end
def update?
(@authorized_roles_for_update & @user_roles).any?
end
def destroy?
(@authorized_roles_for_destroy & @user_roles).any?
end
end
end
3. Prepare the authorization check before call
To autmatically check for authorization for each request we can prepare a separate module for that. In such module Authorization
we then need to make two things.
- First we need to define all the controllers and their respective singluar forms to match the policy name.
- Check if the currently logged in user is authorized. We do this by calling the
authorized?
method of the Rokku. We get the arguments for the method by splitting the controller.
module AuthApp
module Authorization
def self.included(action)
action.class_eval do
before :check_authorization
end
end
private
def controllers_hash
{ 'Notifications' => 'Notification' }
end
def check_authorization
@user = UserRepository.new.find(session[:current_user])
app = self.class.to_s.split("::")[0]
controller = self.class.to_s.split("::")[2]
action = self.class.to_s.split("::")[3]
singular_controller = controllers_hash[controller]
if authorized?(app, singular_controller, action) == false
flash[:failed_notice] = "You tried to visit an URL you are not authorized for."
redirect_to '/'
end
end
end
end
Don’t forget to include this module in the application.rb
.
controller.prepare do
include AuthApp::Authorization
end
Last but not least, we need to override the check_authorization
method in all actions where we don’t require it.
def check_authorization; end