Skip to content

Commit

Permalink
Add the option to load dsl_files via a method on the auth engine.
Browse files Browse the repository at this point in the history
This allow us to add custom rules in any part of an application,
for example a rails engine.
Also do not raise an exception if there isn't a auth rule file
under rails root config directory.
  • Loading branch information
dalecaru authored and stffn committed Oct 31, 2011
1 parent ff03e7e commit fb37826
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 51 deletions.
89 changes: 46 additions & 43 deletions lib/declarative_authorization/authorization.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Authorization
require File.dirname(__FILE__) + '/reader.rb'
require "set"

require "forwardable"

module Authorization
# An exception raised if anything goes wrong in the Authorization realm
Expand Down Expand Up @@ -66,49 +66,50 @@ def self.default_role= (role)
# a certain privilege is granted for the current user.
#
class Engine
attr_reader :roles, :omnipotent_roles, :role_titles, :role_descriptions, :privileges,
:privilege_hierarchy, :auth_rules, :role_hierarchy, :rev_priv_hierarchy,
:rev_role_hierarchy
extend Forwardable
attr_reader :reader

def_delegators :@reader, :auth_rules_reader, :privileges_reader, :load, :load!
def_delegators :auth_rules_reader, :auth_rules, :roles, :omnipotent_roles, :role_hierarchy, :role_titles, :role_descriptions
def_delegators :privileges_reader, :privileges, :privilege_hierarchy

# If +reader+ is not given, a new one is created with the default
# authorization configuration of +AUTH_DSL_FILES+. If given, may be either
# a Reader object or a path to a configuration file.
def initialize (reader = nil)
reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)

@privileges = reader.privileges_reader.privileges
# {priv => [[priv, ctx],...]}
@privilege_hierarchy = reader.privileges_reader.privilege_hierarchy
@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
@roles = reader.auth_rules_reader.roles
@omnipotent_roles = reader.auth_rules_reader.omnipotent_roles
@role_hierarchy = reader.auth_rules_reader.role_hierarchy

@role_titles = reader.auth_rules_reader.role_titles
@role_descriptions = reader.auth_rules_reader.role_descriptions
@reader = reader

# {[priv, ctx] => [priv, ...]}
@rev_priv_hierarchy = {}
@privilege_hierarchy.each do |key, value|
value.each do |val|
@rev_priv_hierarchy[val] ||= []
@rev_priv_hierarchy[val] << key
#@auth_rules = AuthorizationRuleSet.new reader.auth_rules_reader.auth_rules
@reader = Reader::DSLReader.factory(reader || AUTH_DSL_FILES)
end

def initialize_copy (from) # :nodoc:
[ :reader ].each {|attr| instance_variable_set(:"@#{attr}", from.send(attr).clone) }
end

# {[priv, ctx] => [priv, ...]}
def rev_priv_hierarchy
if @rev_priv_hierarchy.nil?
@rev_priv_hierarchy = {}
privilege_hierarchy.each do |key, value|
value.each do |val|
@rev_priv_hierarchy[val] ||= []
@rev_priv_hierarchy[val] << key
end
end
end
@rev_role_hierarchy = {}
@role_hierarchy.each do |higher_role, lower_roles|
lower_roles.each do |role|
(@rev_role_hierarchy[role] ||= []) << higher_role
@rev_priv_hierarchy
end

# {[priv, ctx] => [priv, ...]}
def rev_role_hierarchy
if @rev_role_hierarchy.nil?
@rev_role_hierarchy = {}
role_hierarchy.each do |higher_role, lower_roles|
lower_roles.each do |role|
(@rev_role_hierarchy[role] ||= []) << higher_role
end
end
end
end

def initialize_copy (from) # :nodoc:
[
:privileges, :privilege_hierarchy, :roles, :role_hierarchy, :role_titles,
:role_descriptions, :rev_priv_hierarchy, :rev_role_hierarchy, :auth_rules
].each {|attr| instance_variable_set(:"@#{attr}", from.send(attr).clone) }
@rev_role_hierarchy
end

# Returns true if privilege is met by the current user. Raises
Expand Down Expand Up @@ -166,7 +167,7 @@ def permit! (privilege, options = {})

user, roles, privileges = user_roles_privleges_from_options(privilege, options)

return true if roles.is_a?(Array) and not (roles & @omnipotent_roles).empty?
return true if roles.is_a?(Array) and not (roles & omnipotent_roles).empty?

# find a authorization rule that matches for at least one of the roles and
# at least one of the given privileges
Expand Down Expand Up @@ -271,7 +272,7 @@ def roles_with_hierarchy_for(user)
# yet. If +dsl_file+ is given, it is passed on to Engine.new and
# a new instance is always created.
def self.instance (dsl_file = nil)
if dsl_file or ENV['RAILS_ENV'] == 'development'
if dsl_file
@@instance = new(dsl_file)
else
@@instance ||= new
Expand Down Expand Up @@ -316,7 +317,7 @@ def flatten_roles (roles, flattened_roles = Set.new)
# TODO caching?
roles.reject {|role| flattened_roles.include?(role)}.each do |role|
flattened_roles << role
flatten_roles(@role_hierarchy[role], flattened_roles) if @role_hierarchy[role]
flatten_roles(role_hierarchy[role], flattened_roles) if role_hierarchy[role]
end
flattened_roles.to_a
end
Expand All @@ -327,23 +328,25 @@ def flatten_privileges (privileges, context = nil, flattened_privileges = Set.ne
raise AuthorizationUsageError, "No context given or inferable from object" unless context
privileges.reject {|priv| flattened_privileges.include?(priv)}.each do |priv|
flattened_privileges << priv
flatten_privileges(@rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if @rev_priv_hierarchy[[priv, nil]]
flatten_privileges(@rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if @rev_priv_hierarchy[[priv, context]]
flatten_privileges(rev_priv_hierarchy[[priv, nil]], context, flattened_privileges) if rev_priv_hierarchy[[priv, nil]]
flatten_privileges(rev_priv_hierarchy[[priv, context]], context, flattened_privileges) if rev_priv_hierarchy[[priv, context]]
end
flattened_privileges.to_a
end

def matching_auth_rules (roles, privileges, context)
@auth_rules.matching(roles, privileges, context)
auth_rules.matching(roles, privileges, context)
end
end


class AuthorizationRuleSet
include Enumerable
extend Forwardable
def_delegators :@rules, :each, :length, :[]

def initialize rules
@rules = rules
def initialize (rules = [])
@rules = rules.clone
reset!
end
def initialize_copy source
Expand Down
22 changes: 15 additions & 7 deletions lib/declarative_authorization/reader.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,25 @@ def parse (dsl_data, file_name = nil)
raise DSLSyntaxError, "Illegal DSL syntax: #{e}"
end

# Loads and parses a DSL from the given file name.
# Load and parse a DSL from the given file name.
def load (dsl_file)
parse(File.read(dsl_file), dsl_file) if File.exist?(dsl_file)
end

# Load and parse a DSL from the given file name. Raises Authorization::Reader::DSLFileNotFoundError
# if the file cannot be found.
def load! (dsl_file)
raise ::Authorization::Reader::DSLFileNotFoundError, "Error reading authorization rules file with path '#{dsl_file}'! Please ensure it exists and that it is accessible." unless File.exist?(dsl_file)
load(dsl_file)
end

# Loads and parses DSL files and returns a new reader
def self.load (dsl_files)
# TODO cache reader in production mode?
reader = new
dsl_files = [dsl_files].flatten
dsl_files.each do |file|
begin
reader.parse(File.read(file), file)
rescue SystemCallError
raise ::Authorization::Reader::DSLFileNotFoundError, "Error reading authorization rules file with path '#{file}'! Please ensure it exists and that it is accessible."
end
reader.load(file)
end
reader
end
Expand Down Expand Up @@ -182,7 +190,7 @@ def initialize # :nodoc:
@role_hierarchy = {}
@role_titles = {}
@role_descriptions = {}
@auth_rules = []
@auth_rules = AuthorizationRuleSet.new
end

def append_role (role, options = {}) # :nodoc:
Expand Down
2 changes: 1 addition & 1 deletion test/dsl_reader_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ def test_factory_loads_file

def test_load_file_not_found
assert_raise(Authorization::Reader::DSLFileNotFoundError) do
Authorization::Reader::DSLReader.load("nonexistent_file.rb")
Authorization::Reader::DSLReader.new.load!("nonexistent_file.rb")
end
end
end
Expand Down

0 comments on commit fb37826

Please sign in to comment.