Permalink
Browse files

Added simple roles, simple automatic role assignment hook; minor fixes:

* 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
Philip (flip) Kromer committed Jun 2, 2008
1 parent 682c8f9 commit 673fcf889fe420a1dd20ec7e9306af9287286d62
@@ -67,6 +67,4 @@ def stub_auth!(ctrlr, val)
ctrlr.stub!(:get_authorization).and_return(val)
end
<% end %>
-
end
-
@@ -14,7 +14,7 @@ module SecurityPolicy
# Use req[:for], not current_user, to make your decision
# * :to => the requested action
# * :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
# * nil/false will raise AccessDenied (demands) or deny access (requests)
@@ -40,3 +40,23 @@ def get_authorization req
<%= model_name %>.is_a?(<%= class_name %>)
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
@@ -17,7 +17,7 @@ def create
begin
login_by_password! params[:login], params[:password]
rescue Exception => error
- handle_signin_error error
+ handle_login_error error
else # success!
remember_me_flag = (params[:remember_me] == "1")
handle_remember_cookie! remember_me_flag
@@ -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}"
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
@@ -55,8 +55,8 @@ def do_login options={}
describe "successfully" do
it_should_behave_like "successful login"
# 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 "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 "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 "asks to authenticate me" do <%= class_name %>.should_receive(:authenticate_by_password).with('test_login', 'monkey'); do_login end
# cookies
it "sets cookie with remember me checked" do controller.should_receive(:handle_remember_cookie!).with(true); do_login(:remember_me => "1"); end
@@ -40,7 +40,7 @@ def do_signup(options = {})
it "welcomes me nicely" do do_signup; response.flash[:notice].should =~ /Thank.*sign.*up/i end
# 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 "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 "does fail if other errors" do controller.stub!(:get_authorization).and_raise("frobnozz"); lambda{ do_signup }.should raise_error("frobnozz") end
end
@@ -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)
end
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 %>
end
it "raises the given error if authorization fails" do
@@ -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
def b_l_i_a_no_raise(u) @mock_controller.send(:become_logged_in_as, u) end
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 %>
end
it "raises an AuthenticationError unless <%= model_name %>" do
@@ -1,5 +1,5 @@
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
validates_presence_of :login
@@ -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
View
@@ -1,8 +1,5 @@
%w[
security_components
- authentication
- access_control
- identity
].each do |f|
require f
end
View
@@ -18,7 +18,7 @@ module AccessControl
# Call with positional args (assumes current_user as the subject)
# authorized? action, resource, *args
# 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:
# authorized? :for => user, :to => :log_in_as_user # check if user is activated
@@ -45,7 +45,7 @@ def demand_authorization! *args
# Best for use with before_filter
#
# 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
# handle_access_denied, below.
@@ -56,7 +56,7 @@ def authorization_filter!
decision = get_authorization_with_args :for => current_user,
:to => params[:action],
:on => resource_guess,
- :extras => params
+ :context => params
raise(decision||AccessDenied) if is_denial?(decision)
decision
end
@@ -71,10 +71,11 @@ def get_authorization_with_args *args
end
def parse_access_req_args *args
req = args.extract_options!
+ req.assert_valid_keys(:for, :to, :on, :context)
if args
# ordered params
- action, resource, extra = args
- req.reverse_merge! :to => action, :on => resource, :extra => extra
+ action, resource, context = args
+ req.reverse_merge! :to => action, :on => resource, :context => context
end
# request on behalf of current user if none specified
# (note that an explicit :for => nil or false is left untouched)
View
@@ -1,18 +1,5 @@
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
#
# Create a secure one-way hash of the input.
@@ -32,6 +19,23 @@ def make_token
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
# validation formats were chosen.
@@ -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
@@ -0,0 +1,2 @@
+# I'm actually implemented in authentication/by_cookie_token
+require_dependency 'authentication/by_cookie_token'
View
@@ -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
View
@@ -0,0 +1,2 @@
+# I'm actually implemented in authentication/by_password
+require_dependency 'authentication/by_password'
@@ -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
@@ -23,6 +23,7 @@ def self.walk_reqs(*trees)
def security_components(*args)
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
@@ -0,0 +1,2 @@
+module Trustification::EmailValidation
+end
Oops, something went wrong.

0 comments on commit 673fcf8

Please sign in to comment.