role-auth 0.1.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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