Skip to content
This repository has been archived by the owner on Jul 21, 2020. It is now read-only.

Commit

Permalink
Added simple roles, simple automatic role assignment hook; minor fixes:
Browse files Browse the repository at this point in the history
* handle_login_error lives in sessions_controller, as it should
* get_authorization takes :context => /anything extra/ (was spelled :extra)
* security_components uses .camelize, not .classify (so that pluralization remains intact)
* some notes on existing rails plugins, and on rule resolution / policy / authz
  • Loading branch information
Philip (flip) Kromer committed Jun 2, 2008
1 parent 682c8f9 commit 673fcf8
Show file tree
Hide file tree
Showing 21 changed files with 424 additions and 83 deletions.
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -67,6 +67,4 @@ def stub_auth!(ctrlr, val)
ctrlr.stub!(:get_authorization).and_return(val) ctrlr.stub!(:get_authorization).and_return(val)
end end
<% end %> <% end %>

end end

22 changes: 21 additions & 1 deletion generators/authenticated/templates/security_policy.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module SecurityPolicy
# Use req[:for], not current_user, to make your decision # Use req[:for], not current_user, to make your decision
# * :to => the requested action # * :to => the requested action
# * :on => the resource or resources request will act on # * :on => the resource or resources request will act on
# * :extra => any extra information passed by the access control request # * :context => any extra information passed by the access control request
# #
# get_authorization can return # get_authorization can return
# * nil/false will raise AccessDenied (demands) or deny access (requests) # * nil/false will raise AccessDenied (demands) or deny access (requests)
Expand All @@ -40,3 +40,23 @@ def get_authorization req
<%= model_name %>.is_a?(<%= class_name %>) <%= model_name %>.is_a?(<%= class_name %>)
end end
end end

User.class_eval do
protected
#
# Most roles/privileges are assigned explicitly: designating a user to be a
# moderator, granting 'push' permissions to a newly-hired programmer.
#
# Some are granted and revoked automatically, though. Many sites don't make a
# user active until they've verified their email address. A communal blog
# might not allow 'front-page posting' for the first month after joining.
#
# reconcile_privileges! lets the Policy module assign or revoke privileges
# based on the subject's current state.
#
def reconcile_privileges! occasion='', *more_info
logger.info "Reassigning privileges for #{self.class} id #{self.id}: #{occasion} #{more_info.to_json}"
# user is active if and only if email is verified.
# set_role!(:active, email_verified?)
end
end
20 changes: 19 additions & 1 deletion generators/authenticated/templates/sessions_controller.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def create
begin begin
login_by_password! params[:login], params[:password] login_by_password! params[:login], params[:password]
rescue Exception => error rescue Exception => error
handle_signin_error error handle_login_error error
else # success! else # success!
remember_me_flag = (params[:remember_me] == "1") remember_me_flag = (params[:remember_me] == "1")
handle_remember_cookie! remember_me_flag handle_remember_cookie! remember_me_flag
Expand Down Expand Up @@ -46,4 +46,22 @@ def log_failed_signin error
logger.warn "Failed login for '#{params[:login]}' from #{request.remote_ip} at #{Time.now.utc}: #{error}" logger.warn "Failed login for '#{params[:login]}' from #{request.remote_ip} at #{Time.now.utc}: #{error}"
end end


# react to login failures
def handle_login_error error
logout_keeping_session!
begin
raise error
rescue AccountNotActive => error
log_failed_signin error
redirect_back_or_default('/')
rescue AccountNotFound, BadPassword => error
log_failed_signin error
try_again
rescue AuthenticationError, SecurityError => error
log_failed_signin error
redirect_back_or_default('/')
end
# general exceptions are uncaught
end

end end
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -55,8 +55,8 @@ def do_login options={}
describe "successfully" do describe "successfully" do
it_should_behave_like "successful login" it_should_behave_like "successful login"
# password # password
# it "tries login" do controller.should_receive(:login_by_password!).with('test_login', 'monkey'); controller.stub!(:current_user).and_return(@user); do_login end it "tries login" do controller.should_receive(:login_by_password!).with('test_login', 'monkey'); controller.stub!(:current_user).and_return(@user); do_login end
# it "becomes logged in through the front door" do controller.should_receive(:become_logged_in_as!).with(@user); controller.stub!(:current_user).and_return(@user); do_login end it "becomes logged in through the front door" do controller.should_receive(:become_logged_in_as!).with(@user); controller.stub!(:current_user).and_return(@user); do_login end
it "asks to authenticate me" do <%= class_name %>.should_receive(:authenticate_by_password).with('test_login', 'monkey'); do_login end it "asks to authenticate me" do <%= class_name %>.should_receive(:authenticate_by_password).with('test_login', 'monkey'); do_login end
# cookies # cookies
it "sets cookie with remember me checked" do controller.should_receive(:handle_remember_cookie!).with(true); do_login(:remember_me => "1"); end it "sets cookie with remember me checked" do controller.should_receive(:handle_remember_cookie!).with(true); do_login(:remember_me => "1"); end
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def do_signup(options = {})
it "welcomes me nicely" do do_signup; response.flash[:notice].should =~ /Thank.*sign.*up/i end it "welcomes me nicely" do do_signup; response.flash[:notice].should =~ /Thank.*sign.*up/i end
# auto login if authorized to do so # auto login if authorized to do so
it "logs me in" do controller.should_receive(:become_logged_in_as).with(@user).and_return(true); do_signup; end it "logs me in" do controller.should_receive(:become_logged_in_as).with(@user).and_return(true); do_signup; end
it "only logs me in if authorized" do controller.should_receive(:get_authorization).with({:for => @user, :to => :login,:on=>nil,:extra=>nil}).and_return(true); do_signup; end it "only logs me in if authorized" do controller.should_receive(:get_authorization).with({:for => @user, :to => :login,:on=>nil,:context=>nil}).and_return(true); do_signup; end
it "doesn't fail if not authorized" do stub_auth!(controller, false); lambda{ do_signup }.should_not raise_error end it "doesn't fail if not authorized" do stub_auth!(controller, false); lambda{ do_signup }.should_not raise_error end
it "does fail if other errors" do controller.stub!(:get_authorization).and_raise("frobnozz"); lambda{ do_signup }.should raise_error("frobnozz") end it "does fail if other errors" do controller.stub!(:get_authorization).and_raise("frobnozz"); lambda{ do_signup }.should raise_error("frobnozz") end
end end
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def b_l_i_a!(u) @mock_controller.send(:become_logged_in_as!, u) end
lambda{ b_l_i_a! false}.should raise_error(AuthenticationError) lambda{ b_l_i_a! false}.should raise_error(AuthenticationError)
end end
it "asks for authorization" do it "asks for authorization" do
@mock_controller.should_receive(:get_authorization).at_least(:once).with({:for => @<%= model_name %>, :to => :login, :on => nil, :extra => nil}).and_return(true) @mock_controller.should_receive(:get_authorization).at_least(:once).with({:for => @<%= model_name %>, :to => :login, :on => nil, :context => nil}).and_return(true)
b_l_i_a! @<%= model_name %> b_l_i_a! @<%= model_name %>
end end
it "raises the given error if authorization fails" do it "raises the given error if authorization fails" do
Expand All @@ -58,7 +58,7 @@ def b_l_i_a!(u) @mock_controller.send(:become_logged_in_as!, u) end
describe "become_logged_in_as!" do describe "become_logged_in_as!" do
def b_l_i_a_no_raise(u) @mock_controller.send(:become_logged_in_as, u) end def b_l_i_a_no_raise(u) @mock_controller.send(:become_logged_in_as, u) end
it "asks for authorization" do it "asks for authorization" do
@mock_controller.should_receive(:get_authorization).at_least(:once).with({:for => @<%= model_name %>, :to => :login, :on => nil, :extra => nil}).and_return(true) @mock_controller.should_receive(:get_authorization).at_least(:once).with({:for => @<%= model_name %>, :to => :login, :on => nil, :context => nil}).and_return(true)
b_l_i_a_no_raise @<%= model_name %> b_l_i_a_no_raise @<%= model_name %>
end end
it "raises an AuthenticationError unless <%= model_name %>" do it "raises an AuthenticationError unless <%= model_name %>" do
Expand Down
2 changes: 1 addition & 1 deletion generators/authenticated/templates/user.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,5 +1,5 @@
class <%= class_name %> < ActiveRecord::Base class <%= class_name %> < ActiveRecord::Base
security_components :identity => [:password, :cookie_token] security_components :security_policy, :identity => [:password, :cookie_token, :simple_roles]


# Validation constants are in config/initializers/rest_auth_config.rb # Validation constants are in config/initializers/rest_auth_config.rb
validates_presence_of :login validates_presence_of :login
Expand Down
11 changes: 11 additions & 0 deletions generators/simple_roles/templates/migration.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,11 @@
class <%= migration_name %> < ActiveRecord::Migration
def self.up
add_column :<%= user_model_table_name %>, :roles, :text, :default => '[:user, :active]'
Identity::AddOrMakeAdminUser.add_or_make_admin_user
end

def self.down
remove_column :users, :roles
end

end
3 changes: 0 additions & 3 deletions init.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,8 +1,5 @@
%w[ %w[
security_components security_components
authentication
access_control
identity
].each do |f| ].each do |f|
require f require f
end end
Expand Down
11 changes: 6 additions & 5 deletions lib/access_control.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ module AccessControl
# Call with positional args (assumes current_user as the subject) # Call with positional args (assumes current_user as the subject)
# authorized? action, resource, *args # authorized? action, resource, *args
# or call with options # or call with options
# authorized? :for => user, :to => action, :on => resource, :extra => anything_extra # authorized? :for => user, :to => action, :on => resource, :context => any_extra_context
# #
# Examples: # Examples:
# authorized? :for => user, :to => :log_in_as_user # check if user is activated # authorized? :for => user, :to => :log_in_as_user # check if user is activated
Expand All @@ -45,7 +45,7 @@ def demand_authorization! *args
# Best for use with before_filter # Best for use with before_filter
# #
# Fills in request from controller action params: # Fills in request from controller action params:
# :for => current_user, :to => action, :on => self.class, :extras => params # :for => current_user, :to => action, :on => self.class, :context => params
# #
# If user is not authorized, raises an AccessDenied exception; see # If user is not authorized, raises an AccessDenied exception; see
# handle_access_denied, below. # handle_access_denied, below.
Expand All @@ -56,7 +56,7 @@ def authorization_filter!
decision = get_authorization_with_args :for => current_user, decision = get_authorization_with_args :for => current_user,
:to => params[:action], :to => params[:action],
:on => resource_guess, :on => resource_guess,
:extras => params :context => params
raise(decision||AccessDenied) if is_denial?(decision) raise(decision||AccessDenied) if is_denial?(decision)
decision decision
end end
Expand All @@ -71,10 +71,11 @@ def get_authorization_with_args *args
end end
def parse_access_req_args *args def parse_access_req_args *args
req = args.extract_options! req = args.extract_options!
req.assert_valid_keys(:for, :to, :on, :context)
if args if args
# ordered params # ordered params
action, resource, extra = args action, resource, context = args
req.reverse_merge! :to => action, :on => resource, :extra => extra req.reverse_merge! :to => action, :on => resource, :context => context
end end
# request on behalf of current user if none specified # request on behalf of current user if none specified
# (note that an explicit :for => nil or false is left untouched) # (note that an explicit :for => nil or false is left untouched)
Expand Down
30 changes: 17 additions & 13 deletions lib/identity.rb
Original file line number Original file line Diff line number Diff line change
@@ -1,18 +1,5 @@
module Identity module Identity


#
# Define any user roles here -- eg :moderator or :admin.
#
# This example gives every user two roles: :user and :active, and no other.
#
# This is just a stub called by the authorization routines. Add logic over
# there if you want these roles to do anything. For more complex needs, see
# notes/RailsPlugins.txt for role-based security plugins
#
def has_role? role
[:user, :active].include? role
end

module ModelClassMethods module ModelClassMethods
# #
# Create a secure one-way hash of the input. # Create a secure one-way hash of the input.
Expand All @@ -32,6 +19,23 @@ def make_token
end end
end end


#
# Define any user roles here -- eg :moderator or :admin.
#
# This example gives every user two roles: :user and :active, and no other.
#
# This is just a stub called by the authorization routines. Add logic over
# there if you want these roles to do anything. For more complex needs, see
# notes/RailsPlugins.txt for role-based security plugins
#
def has_role? role
[:user, :active].include? role
end


#
# Validations
#
# restful-authentication/notes/Tradeoffs.txt has more information on how these # restful-authentication/notes/Tradeoffs.txt has more information on how these
# validation formats were chosen. # validation formats were chosen.


Expand Down
34 changes: 34 additions & 0 deletions lib/identity/add_or_make_admin_user.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,34 @@
module Identity::AddOrMakeAdminUser

def self.add_or_make_admin_user
puts "*"*70
admin = self.find_admin || self.make_admin
admin.assign_role! :admin
admin.reconcile_privileges!
puts " added 'admin' role"
puts "*"*70
admin
end

def self.find_admin
admin = User.find_by_login('admin') or return false
puts " On preexisting admin:"
admin
end

def self.make_admin
passwd = make_random_password
admin_params = {
:login=>'admin',
:email => 'admin@this-site.com',
:password => passwd, :password_confirmation => passwd }
admin = User.create!(admin_params)
puts " On newly created admin with password #{passwd}:"
admin
end

def self.make_random_password
User.make_token[1..8]
end

end
2 changes: 2 additions & 0 deletions lib/identity/cookie_token.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,2 @@
# I'm actually implemented in authentication/by_cookie_token
require_dependency 'authentication/by_cookie_token'
26 changes: 26 additions & 0 deletions lib/identity/nil_roles.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,26 @@
module Identity::NilRoles
#
# This example gives every user two roles: :user and :active, and no other,
# it satisfies the minimal
#
def has_role? role
[:user, :active].include? role
end

#
# Roles are fixed
#
def assign_role! role
raise "Can't assign or revoke roles: ever user is the same."
end
def revoke_role! role
raise "Can't assign or revoke roles: ever user is the same."
end
def set_role! role, should_assign
if should_assign
assign_role! role
else
revoke_role! role
end
end
end
2 changes: 2 additions & 0 deletions lib/identity/password.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,2 @@
# I'm actually implemented in authentication/by_password
require_dependency 'authentication/by_password'
51 changes: 51 additions & 0 deletions lib/identity/simple_roles.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,51 @@
module Identity::SimpleRoles
# Verifies that parent module is in place for us to override
def self.included(recipient)
recipient.serialize :roles
raise "Because #{self.class} extends Identity, #{recipient.class.to_S} must include it before first before #{self.class}" unless recipient.included_modules.include?(Identity)
end

#
# Define any user roles here -- eg :moderator or :admin.
#
# This example gives every user two roles: :user and :active, and no other.
#
# This is just a stub called by the authorization routines. Add logic over
# there if you want these roles to do anything. For more complex needs, see
# notes/RailsPlugins.txt for role-based security plugins
#
def has_role? role
[:user, :active].include? role
end

#
# Explicitly assign/revoke

# Adds role. No error if user already has role.
# returns updated user.roles
def assign_role! role, skip_save=false
self.roles << role
self.roles.uniq!
self.save(false) unless (skip_save==:skip_save)
self.roles
end

# Removes role. No error if user did not have role.
# returns updated user.roles
def remove_role! role, skip_save=false
self.roles.delete role
self.save(false) unless (skip_save==:skip_save)
self.roles
end

# give a role and true (to assign) and false (to revoke)
# returns updated user.roles
def set_role! role, should_assign, skip_save=false
if should_assign
assign_role! role, skip_save
else
revoke_role! role, skip_save
end
end

end
3 changes: 2 additions & 1 deletion lib/security_components.rb
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def self.walk_reqs(*trees)


def security_components(*args) def security_components(*args)
SecurityComponents.walk_reqs(args).each do |concern| SecurityComponents.walk_reqs(args).each do |concern|
include concern.to_s.classify.constantize # require_dependency concern.to_s # causes double includes ??
include concern.to_s.camelize.constantize
end end
end end
2 changes: 2 additions & 0 deletions lib/trustification/email_validation.rb
Original file line number Original file line Diff line number Diff line change
@@ -0,0 +1,2 @@
module Trustification::EmailValidation
end
Loading

0 comments on commit 673fcf8

Please sign in to comment.