role-auth 0.1.9

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ #specific files
2
+
3
+ # dirs
4
+ .bundle
5
+ bin
6
+ pkg
7
+
8
+ # tempfiles
9
+ *[~#]
10
+ .#*
11
+ *_flymake*
12
+ /.emacs-project
13
+ /.irbrc
14
+ *_out
15
+ *.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sequel-localize.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ role-auth (0.1.9)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ addressable (2.3.5)
10
+ data_objects (0.10.13)
11
+ addressable (~> 2.1)
12
+ diff-lcs (1.2.4)
13
+ dm-core (1.2.1)
14
+ addressable (~> 2.3)
15
+ dm-do-adapter (1.2.0)
16
+ data_objects (~> 0.10.6)
17
+ dm-core (~> 1.2.0)
18
+ dm-migrations (1.2.0)
19
+ dm-core (~> 1.2.0)
20
+ dm-sqlite-adapter (1.2.0)
21
+ dm-do-adapter (~> 1.2.0)
22
+ do_sqlite3 (~> 0.10.6)
23
+ do_sqlite3 (0.10.13)
24
+ data_objects (= 0.10.13)
25
+ rake (10.1.0)
26
+ rspec (2.14.1)
27
+ rspec-core (~> 2.14.0)
28
+ rspec-expectations (~> 2.14.0)
29
+ rspec-mocks (~> 2.14.0)
30
+ rspec-core (2.14.4)
31
+ rspec-expectations (2.14.0)
32
+ diff-lcs (>= 1.1.3, < 2.0)
33
+ rspec-mocks (2.14.1)
34
+ sqlite3 (1.3.7)
35
+
36
+ PLATFORMS
37
+ ruby
38
+
39
+ DEPENDENCIES
40
+ bundler
41
+ dm-core
42
+ dm-migrations
43
+ dm-sqlite-adapter
44
+ do_sqlite3
45
+ rake
46
+ role-auth!
47
+ rspec
48
+ sqlite3
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2009-2013 Jonas von Andrian
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,58 @@
1
+ # role-auth
2
+
3
+ there is no inheritance between permissions created within a role block
4
+
5
+ there is inheritance between task definitions
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'sequel-localize'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install sequel-localize
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Assumptions
26
+
27
+ * changes to the object are finished before the update hook
28
+ ** merb before: Filters are executed in order of definition
29
+ * standard REST Controller
30
+ * on scopes defined on the Model
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
39
+
40
+
41
+ ## TODO
42
+ * give possibility to inspect rules
43
+ ** Hints why it failed
44
+ ** nice Printout of ruby code
45
+ * docs
46
+ * publish
47
+
48
+ ## Ideas
49
+
50
+ * Adapter specific way of finding roles
51
+ * custom aliases
52
+
53
+ ## Notes
54
+ * Inheritance between parts of roles is bad. It can have unintended side effects.
55
+
56
+ ## Copyright
57
+
58
+ Copyright (c) 2010 Jonas von Andrian. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
data/lib/role-auth.rb ADDED
@@ -0,0 +1,86 @@
1
+ __DIR__ = File.dirname(__FILE__)
2
+
3
+ $LOAD_PATH.unshift __DIR__ unless
4
+ $LOAD_PATH.include?(__DIR__) ||
5
+ $LOAD_PATH.include?(File.expand_path(__DIR__))
6
+
7
+ %w{checker builder parser}.each do |file|
8
+ require "role-auth/#{file}"
9
+ end
10
+
11
+ module RoleAuth
12
+
13
+ class AuthorizationError < StandardError; end
14
+
15
+ DEFAULT_CONFIGURATION = {
16
+ :user_class => 'User',
17
+ :exception_class => AuthorizationError
18
+ }
19
+
20
+ attr_accessor :checker
21
+ module_function :checker, :checker=
22
+
23
+ module_function
24
+
25
+ def config=(options = {})
26
+ @config = DEFAULT_CONFIGURATION.merge(options)
27
+ end
28
+ def config
29
+ @config ||= DEFAULT_CONFIGURATION
30
+ end
31
+
32
+ def instance_name(model)
33
+ model.to_s[/([^:]*)$/,1].downcase
34
+ end
35
+
36
+ module InstanceMethods
37
+
38
+ def user!
39
+ User.current || raise(ArgumentError, "User.current is not set")
40
+ end
41
+
42
+ def can?(task, object, options = {})
43
+ user!.can?(task, object, options)
44
+ end
45
+
46
+ def can!(task, object, options = {})
47
+ user!.can!(task, object, options)
48
+ end
49
+
50
+ def is?(role, options = {})
51
+ user!.is?(role, options)
52
+ end
53
+
54
+ end
55
+
56
+ module UserClassMethods
57
+ def self.included(klass)
58
+ klass.extend ClassMethods
59
+ end
60
+
61
+ def can?(task, object, options = {})
62
+ RoleAuth.checker.can?(task, object, options, self)
63
+ end
64
+
65
+ def can!(task, object, options = {})
66
+ can?(task, object, options) || raise(RoleAuth.config[:exception_class])
67
+ end
68
+
69
+ def is?(role_name, options = {})
70
+ RoleAuth.checker.is?(role_name, options, self)
71
+ end
72
+
73
+ module ClassMethods
74
+ def current=(user)
75
+ Thread.current[:user] = user
76
+ end
77
+ def current
78
+ Thread.current[:user]
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ %w{ data_mapper merb}.each do |file|
85
+ require "role-auth/adapters/#{file}" if Object.const_defined? file.split('_').map{|e| e.capitalize}.join
86
+ end
@@ -0,0 +1,60 @@
1
+ class RoleAuth::DataMapperDSLBuilder < RoleAuth::DSLBuilder
2
+ use_for DataMapper::Model
3
+ class << self
4
+ def change(entries)
5
+ symbol_entries = entries.uniq.collect {|e| ":#{e}" }.join(', ')
6
+ "(_object.dirty_attributes.keys.map{ |attr| attr.name} - [#{symbol_entries}]).empty?"
7
+ end
8
+ end
9
+ end # RoleAuth::DataMapperDSLBuilder
10
+
11
+ module RoleAuth::Adapters
12
+ module DataMapper
13
+ module Hook
14
+ def check_permission_before(*args)
15
+ options = args.last.is_a?(Hash) ? args.pop : {}
16
+ include RoleAuth::InstanceMethods
17
+ extend RoleAuth::Adapters::DataMapper::ClassMethods
18
+ args.each do |filter|
19
+ self.send "_create_permission_filter_before_#{filter}", options
20
+ end
21
+ end
22
+ end # Hook
23
+ module ClassMethods
24
+ protected
25
+ def _create_permission_filter_before(action, options)
26
+ options_literal = options[:on] ? ", :on => #{RoleAuth.instance_name(options[:on])}" : ''
27
+ self.class_eval <<-RUBY
28
+ before :#{action} do
29
+ can!(:#{options[:as] || action}, self #{options_literal}) if ::#{RoleAuth.config[:user_class]}.current
30
+ end
31
+ RUBY
32
+ end
33
+
34
+ def _create_permission_filter_before_initialize(options)
35
+ options_literal = if options[:on]
36
+ instance_name = RoleAuth.instance_name(options[:on])
37
+ ", :on => attributes[:#{instance_name}] || ::#{options[:on]}.get(attributes[:#{instance_name}_id])"
38
+ end || ''
39
+ self.class_eval <<-RUBY
40
+ def initialize(attributes = {})
41
+ can!(:create, self #{options_literal}) if ::#{RoleAuth.config[:user_class]}.current
42
+ super
43
+ end
44
+ RUBY
45
+ end
46
+
47
+
48
+ def _create_permission_filter_before_create(options)
49
+ _create_permission_filter_before(:create, options)
50
+ end
51
+ def _create_permission_filter_before_update(options)
52
+ _create_permission_filter_before(:update, options)
53
+ end
54
+ def _create_permission_filter_before_destroy(options)
55
+ _create_permission_filter_before(:destroy, options.merge(:as => :delete))
56
+ end
57
+ end # ClassMethods
58
+ end # DataMapper
59
+ end # RoleAuth::Adapters
60
+ DataMapper::Model.append_extensions(RoleAuth::Adapters::DataMapper::Hook)
@@ -0,0 +1,39 @@
1
+ module RoleAuth::Adapters
2
+ module Merb
3
+ def lockdown(*args)
4
+ options = args.last.is_a?(Hash) ? args.pop : {}
5
+ options[:object] ||= _guess_model_class_from_controller_name
6
+
7
+ actions = args.empty? ? public_methods(false) : args
8
+
9
+ _define_lockdown(options)
10
+ before :"_lockdown_#{options[:object]}", :only => actions
11
+ end
12
+
13
+ def _guess_model_class_from_controller_name
14
+ self.to_s[/([^:]*)$/,1].singularize
15
+ end
16
+
17
+ def _define_lockdown(options)
18
+ options_hash = options[:on] ? ", :on => #{options[:on]}" : ''
19
+ self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
+ def _lockdown_#{options[:object]}
21
+ can! params[:action].to_sym, #{options[:object]} #{options_hash}
22
+ end
23
+ RUBY
24
+ end
25
+ end # Merb
26
+ end # RoleAuth::Adapters
27
+
28
+ Merb::AbstractController.send(:include, RoleAuth::InstanceMethods)
29
+ Merb::AbstractController.send(:extend, RoleAuth::Adapters::Merb)
30
+
31
+ class Merb::BootLoader::RoleAuth < Merb::BootLoader
32
+ after AfterAppLoads
33
+ class << self
34
+ def run
35
+ RoleAuth.config = Merb::Plugins.config[:role-auth].blank? ? {:exception_class => Merb::ControllerExceptions::Forbidden} : Merb::Plugins.config[:role-auth]
36
+ RoleAuth::Builder.new(File.new(Merb.root/'config/authorization_rules.rb')).build
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,141 @@
1
+ module RoleAuth
2
+ class Builder
3
+
4
+ def initialize(authorization_file)
5
+ @parser = Parser.new(authorization_file)
6
+ end
7
+
8
+ def build
9
+ task_methods = @parser.tasks.keys.collect do |task|
10
+ next if task == :do
11
+ build_task(task)
12
+ end.join
13
+ RoleAuth.checker = Checker.new(@parser, task_methods)
14
+ end
15
+
16
+ protected
17
+
18
+ def build_task(task)
19
+ permission_case_clauses = []
20
+
21
+ pcc = build_case_clause(@parser.permissions, task)
22
+ rcc = build_case_clause(@parser.restrictions, task)
23
+
24
+ permission_case_clauses << "(#{pcc})" if !pcc.empty?
25
+ permission_case_clauses << "!(#{rcc})" if !rcc.empty?
26
+ %!
27
+ ### #{task}
28
+ def #{task}(user, object, options = {})
29
+ _user, _object, _options = user, object, options
30
+ (#{permission_case_clauses.join(" && \n ")}) #{build_alternative_permissions_clause(task)}
31
+ end
32
+ !
33
+ end
34
+
35
+ def build_alternative_permissions_clause(parent_task)
36
+ return if @parser.tasks[parent_task].options[:alternatives].nil?
37
+ "|| " + @parser.tasks[parent_task].options[:alternatives].map do |task_name|
38
+ "#{task_name}(_user, _object, _options)"
39
+ end.join(" || ")
40
+ end
41
+
42
+ def build_any_clause(permissions, task)
43
+ clause = build_permission_clauses(permissions, task, :any)
44
+ clause.empty? ? "" : clause
45
+ end
46
+
47
+ def build_case_clause(permissions, task)
48
+ when_clauses = (permissions[task].keys + permissions[:do].keys).uniq.map do |model|
49
+ next if model == :any
50
+ build_when_clause(permissions, task, model)
51
+ end.join
52
+ if when_clauses.empty?
53
+ build_any_clause(permissions, task)
54
+ else
55
+ any_clause = build_any_clause(permissions, task)
56
+ case_clause = %!
57
+ case _object.is_a?(Class) ? _object.to_s : _object.class.to_s
58
+ #{when_clauses}
59
+ end!
60
+ any_clause.empty? ? case_clause : "#{any_clause} || #{case_clause}"
61
+ end
62
+ end
63
+
64
+ def build_when_clause(permissions, task, model)
65
+ permission_clauses = build_permission_clauses(permissions, task, model)
66
+ return if permission_clauses.empty?
67
+ %!
68
+ when '#{model}' then
69
+ #{RoleAuth.instance_name(model)} = _object
70
+ #{permission_clauses}!
71
+ end
72
+
73
+ def build_permission_clauses(permissions, task, model)
74
+ mixed_permissions = permissions[task][model].values + permissions[:do][model].values
75
+ mixed_permissions.map { |perm| build_permission(perm) }.join(" ||\n")
76
+ end
77
+
78
+ def build_permission(permission)
79
+ permission.load_task_options(@parser)
80
+ build_role_clause(permission) + build_permission_parts(permission)
81
+ end
82
+
83
+ def build_role_clause(permission)
84
+ roles = (@parser.roles[permission.role.name][:descendants].dup << permission.role.name)
85
+
86
+ instance_assignment = ''
87
+ block = "%w{ #{roles.join(" ")} }.include?( user_role.name )"
88
+
89
+ if !permission.options[:on].empty?
90
+ model = permission.options[:on].first
91
+ instance_name = RoleAuth.instance_name(model)
92
+ instance_value = permission.model.instance_methods.include?(instance_name.to_sym) ? "_options[:on] || _object.#{instance_name}" : "_options[:on]"
93
+
94
+ instance_assignment = "((#{instance_name} = #{instance_value} || (_object.is_a?(#{model}) ? _object : nil)) || true) &&"
95
+ block = "#{block} && ( user_role.type != '#{model}' || ( !#{instance_name}.nil? && (user_role.object_id.nil? || user_role.object_id == #{instance_name}.id )))"
96
+ end
97
+ "#{instance_assignment} _user.roles.any? {|user_role| #{block} }"
98
+ end
99
+
100
+ def build_permission_parts(permission)
101
+ return "" if permission.options[:if].empty?
102
+ dsl_parser = DSLBuilder.pick(permission.model)
103
+ " && " + permission.options[:if].collect do |type, entries|
104
+ dsl_parser.send type, entries
105
+ end.join(" && ")
106
+ end
107
+
108
+ end
109
+
110
+ class DSLBuilder
111
+ class << self
112
+ def pick(model)
113
+ pair = builders.find do | builder, base_class |
114
+ model.is_a? base_class
115
+ end
116
+ pair ? pair[0] : self
117
+ end
118
+
119
+ def string(entries)
120
+ "(#{entries.join(") && (")})"
121
+ end
122
+
123
+ def change(entries)
124
+ symbol_entries = entries.uniq.collect {|e| ":#{e}" }.join(', ')
125
+ "(_object.updated_attributes - [#{symbol_entries}]).empty?"
126
+ end
127
+
128
+ protected
129
+
130
+ def builders
131
+ @@builders ||= {}
132
+ end
133
+
134
+ def use_for(base_class)
135
+ builders[self] = base_class
136
+ end
137
+
138
+ end
139
+ end
140
+
141
+ end