Skip to content

Commit

Permalink
Added role requirement. Also created a default admin user and admin role
Browse files Browse the repository at this point in the history
  • Loading branch information
Jim Neath committed Sep 12, 2008
1 parent 0f692d1 commit bf86685
Show file tree
Hide file tree
Showing 34 changed files with 1,542 additions and 4 deletions.
3 changes: 2 additions & 1 deletion app/controllers/application.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
class ApplicationController < ActionController::Base
include ExceptionNotifiable
include AuthenticatedSystem

include RoleRequirementSystem

helper :all # include all helpers, all the time
protect_from_forgery :secret => 'b0a876313f3f9195e9bd01473bc5cd06'
filter_parameter_logging :password, :password_confirmation
Expand Down
3 changes: 3 additions & 0 deletions app/models/role.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class Role < ActiveRecord::Base

end
14 changes: 12 additions & 2 deletions app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ class User < ActiveRecord::Base
include Authentication::ByCookieToken
include Authorization::AasmRoles

# Validations
validates_presence_of :login
validates_length_of :login, :within => 3..40
validates_uniqueness_of :login, :case_sensitive => false
Expand All @@ -16,6 +17,9 @@ class User < ActiveRecord::Base
validates_length_of :email, :within => 6..100 #r@a.wk
validates_uniqueness_of :email, :case_sensitive => false
validates_format_of :email, :with => RE_EMAIL_OK, :message => MSG_EMAIL_BAD

# Relationships
has_and_belongs_to_many :roles

# HACK HACK HACK -- how to do attr_accessible from here?
# prevents a user from submitting a crafted form that bypasses activation
Expand All @@ -32,11 +36,17 @@ def self.authenticate(login, password)
u = find_in_state :first, :active, :conditions => { :login => login } # need to get the salt
u && u.authenticated?(password) ? u : nil
end

# Check if a user has a role.
def has_role?(role)
list ||= self.roles.map(&:name)
list.include?(role.to_s) || list.include?('admin')
end

protected

def make_activation_code
self.deleted_at = nil
self.activation_code = self.class.make_token
self.deleted_at = nil
self.activation_code = self.class.make_token
end
end
12 changes: 12 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
development: &non_production_settings
site_url: http://localhost:3000
site_name: Bort
admin_email: bort@bort.com

test:
<<: *non_production_settings

production:
site_url: http://www.example.com
site_name: Bort
admin_email: bort@bort.com
17 changes: 17 additions & 0 deletions db/migrate/20080912160708_create_roles.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class CreateRoles < ActiveRecord::Migration
def self.up
create_table :roles do |t|
t.string :name
end

create_table :roles_users, :id => false do |t|
t.belongs_to :role
t.belongs_to :user
end
end

def self.down
drop_table :roles
drop_table :roles_users
end
end
25 changes: 25 additions & 0 deletions db/migrate/20080912160936_add_default_admin_user.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
class AddDefaultAdminUser < ActiveRecord::Migration
def self.up
# Create admin role
admin_role = Role.create(:name => 'admin')

# Create default admin user
user = User.create do |u|
u.login = 'admin'
u.password = u.password_confirmation = 'chester'
u.email = APP_CONFIG[:admin_email]
end

# Activate user
user.register!
user.activate!

# Add admin role to admin user
user.roles << admin_role
end

def self.down
Role.delete_all
User.delete_all
end
end
11 changes: 10 additions & 1 deletion db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20080811135317) do
ActiveRecord::Schema.define(:version => 20080912160936) do

create_table "passwords", :force => true do |t|
t.integer "user_id"
Expand All @@ -19,6 +19,15 @@
t.datetime "updated_at"
end

create_table "roles", :force => true do |t|
t.string "name"
end

create_table "roles_users", :id => false, :force => true do |t|
t.integer "role_id"
t.integer "user_id"
end

create_table "sessions", :force => true do |t|
t.string "session_id", :null => false
t.text "data"
Expand Down
72 changes: 72 additions & 0 deletions lib/hijacker.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Hijacker class
#
# This class is used by RoleRequirementTestHelper to temporarily hijack a controller action for testing
#
# Example usage:
# hijacker = Hijacker.new(ListingsController)
# hijacker.hijack_instance_method("index", "render :text => 'hello world!'" )
# get :index # will return "hello world"
# hijacker.restore # put things back the way you found it

class Hijacker
def initialize(klass)
@target_klass = klass
@method_stores = {}
end

def hijack_class_method(method_name, eval_string = nil, arg_names = [], &block)
hijack_method(class_self_instance, method_name, eval_string, arg_names, &block )
end

def hijack_instance_method(method_name, eval_string = nil, arg_names = [], &block)
hijack_method(@target_klass, method_name, eval_string, arg_names, &block )
end

# restore all
def restore
@method_stores.each_pair{|klass, method_stores|
method_stores.reverse_each{ |method_name, method|
klass.send :undef_method, method_name
klass.send :define_method, method_name, method if method
}
}
@method_stores.clear
true
rescue
false
end

protected

def class_self_instance
@target_klass.send :eval, "class << self; self; end;"
end

def hijack_method(klass, method_name, eval_string = nil, arg_names = [], &block)
method_name = method_name.to_s
# You have got love ruby! What other language allows you to pillage and plunder a class like this?

(@method_stores[klass]||=[]) << [
method_name,
klass.instance_methods.include?(method_name) && klass.instance_method(method_name)
]

klass.send :undef_method, method_name
if Symbol === eval_string
klass.send :define_method, method_name, klass.instance_methods(eval_string)
elsif String === eval_string
klass.class_eval <<-EOF
def #{method_name}(#{arg_names * ','})
#{eval_string}
end
EOF
elsif block_given?
klass.send :define_method, method_name, block
end

true
rescue
false
end

end
142 changes: 142 additions & 0 deletions lib/role_requirement_system.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
# Main module for authentication.
# Include this in ApplicationController to activate RoleRequirement
#
# See RoleSecurityClassMethods for some methods it provides.
module RoleRequirementSystem
def self.included(klass)
klass.send :class_inheritable_array, :role_requirements
klass.send :include, RoleSecurityInstanceMethods
klass.send :extend, RoleSecurityClassMethods
klass.send :helper_method, :url_options_authenticate?

klass.send :role_requirements=, []

end

module RoleSecurityClassMethods

def reset_role_requirements!
self.role_requirements.clear
end

# Add this to the top of your controller to require a role in order to access it.
# Example Usage:
#
# require_role "contractor"
# require_role "admin", :only => :destroy # don't allow contractors to destroy
# require_role "admin", :only => :update, :unless => "current_user.authorized_for_listing?(params[:id]) "
#
# Valid options
#
# * :only - Only require the role for the given actions
# * :except - Require the role for everything but
# * :if - a Proc or a string to evaluate. If it evaluates to true, the role is required.
# * :unless - The inverse of :if
#
def require_role(roles, options = {})
options.assert_valid_keys(:if, :unless,
:for, :only,
:for_all_except, :except
)

# only declare that before filter once
unless (@before_filter_declared||=false)
@before_filter_declared=true
before_filter :check_roles
end

# convert to an array if it isn't already
roles = [roles] unless Array===roles

options[:only] ||= options[:for] if options[:for]
options[:except] ||= options[:for_all_except] if options[:for_all_except]

# convert any actions into symbols
for key in [:only, :except]
if options.has_key?(key)
options[key] = [options[key]] unless Array === options[key]
options[key] = options[key].compact.collect{|v| v.to_sym}
end
end

self.role_requirements||=[]
self.role_requirements << {:roles => roles, :options => options }
end

# This is the core of RoleRequirement. Here is where it discerns if a user can access a controller or not./
def user_authorized_for?(user, params = {}, binding = self.binding)
return true unless Array===self.role_requirements
self.role_requirements.each{| role_requirement|
roles = role_requirement[:roles]
options = role_requirement[:options]
# do the options match the params?

# check the action
if options.has_key?(:only)
next unless options[:only].include?( (params[:action]||"index").to_sym )
end

if options.has_key?(:except)
next if options[:except].include?( (params[:action]||"index").to_sym)
end

if options.has_key?(:if)
# execute the proc. if the procedure returns false, we don't need to authenticate these roles
next unless ( String===options[:if] ? eval(options[:if], binding) : options[:if].call(params) )
end

if options.has_key?(:unless)
# execute the proc. if the procedure returns true, we don't need to authenticate these roles
next if ( String===options[:unless] ? eval(options[:unless], binding) : options[:unless].call(params) )
end

# check to see if they have one of the required roles
passed = false
roles.each { |role|
passed = true if user.has_role?(role)
} unless (! user || user==:false)

return false unless passed
}

return true
end
end

module RoleSecurityInstanceMethods
def self.included(klass)
raise "Because role_requirement extends acts_as_authenticated, You must include AuthenticatedSystem first before including RoleRequirementSystem!" unless klass.included_modules.include?(AuthenticatedSystem)
end

def access_denied
if logged_in?
render :nothing => true, :status => 401
return false
else
super
end
end

def check_roles
return access_denied unless self.class.user_authorized_for?(current_user, params, binding)

true
end

protected
# receives a :controller, :action, and :params. Finds the given controller and runs user_authorized_for? on it.
# This can be called in your views, and is for advanced users only. If you are using :if / :unless eval expressions,
# then this may or may not work (eval strings use the current binding to execute, not the binding of the target
# controller)
def url_options_authenticate?(params = {})
params = params.symbolize_keys
if params[:controller]
# find the controller class
klass = eval("#{params[:controller]}_controller".classify)
else
klass = self.class
end
klass.user_authorized_for?(current_user, params, binding)
end
end
end
Loading

0 comments on commit bf86685

Please sign in to comment.