authoreyes 0.1.1
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.
- checksums.yaml +7 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +21 -0
- data/README.md +53 -0
- data/Rakefile +10 -0
- data/authoreyes.gemspec +29 -0
- data/authorization_rules.dist.rb +20 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/lib/authoreyes.rb +7 -0
- data/lib/authoreyes/authorization.rb +80 -0
- data/lib/authoreyes/authorization/anonymous_user.rb +11 -0
- data/lib/authoreyes/authorization/attribute.rb +164 -0
- data/lib/authoreyes/authorization/attribute_with_permission.rb +133 -0
- data/lib/authoreyes/authorization/authorization_rule.rb +89 -0
- data/lib/authoreyes/authorization/authorization_rule_set.rb +58 -0
- data/lib/authoreyes/authorization/engine.rb +296 -0
- data/lib/authoreyes/parser.rb +18 -0
- data/lib/authoreyes/parser/authorization_rules_parser.rb +399 -0
- data/lib/authoreyes/parser/dsl_parser.rb +91 -0
- data/lib/authoreyes/parser/priveleges_reader.rb +59 -0
- data/lib/authoreyes/version.rb +3 -0
- metadata +115 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c692f2b44a5992e6ed48d9edb77cda2accfa2bc1
|
4
|
+
data.tar.gz: 63e2a6282929830fa130217e901c2a55e1c9897e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7086240162b0a8734af9922554d20b9867706bbe9619917642d72f2531596c13229a97d16f59831fac56820bd94547bc367c6c3862f525ac248cbca8a58ec1f9
|
7
|
+
data.tar.gz: 77bb72a8bf572b7c06595993985f48e896c68327681b563cb6cd2fd1429da4af2fe2b4c77a710e94364d626a6d1d34f1430aade2b56eda9ef6bebd50caf45313
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2016 Xavier Bick
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# Authoreyes
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/authoreyes) [](https://travis-ci.org/tektite-software/authoreyes) [](https://codeclimate.com/github/tektite-software/authoreyes) [](https://codeclimate.com/github/tektite-software/authoreyes/coverage) [](http://inch-ci.org/github/tektite-software/authoreyes)
|
4
|
+
|
5
|
+
#### Warning! This gem is an alpha!
|
6
|
+
|
7
|
+
_Authoreyes_ (pronounced "authorize") is intended to be a modern, Rails 5 compatible replacement for [Declarative Authorization](https://github.com/stffn/declarative_authorization/).
|
8
|
+
|
9
|
+
## Installation
|
10
|
+
|
11
|
+
Add this line to your application's Gemfile:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
gem 'authoreyes'
|
15
|
+
```
|
16
|
+
|
17
|
+
And then execute:
|
18
|
+
|
19
|
+
$ bundle
|
20
|
+
|
21
|
+
Or install it yourself as:
|
22
|
+
|
23
|
+
$ gem install authoreyes
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
For Rails authorization in Rails versions 4 and below, please use [Declarative Authorization](https://github.com/stffn/declarative_authorization) or one of its forks.
|
28
|
+
|
29
|
+
__Warning! This gem is not finished!__ Although authorization functionality _does_ work, you will need to do a few things to actually use it in your application...
|
30
|
+
|
31
|
+
At this point, to use Authoreyes, you must do the following:
|
32
|
+
1. Add an `authorization_rules.rb` file.
|
33
|
+
2. Create an Authoreyes DSL Parser object.
|
34
|
+
3. Use the DSL Parser object to parse your authorization rules.
|
35
|
+
4. Create an Authoreyes Authorization Engine object passing in the Parser object.
|
36
|
+
5. Use the Engine's `permit!` and `permit?` methods in your application.
|
37
|
+
|
38
|
+
## Contributing
|
39
|
+
|
40
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/tektite-software/authoreyes.
|
41
|
+
|
42
|
+
__Please check out the wiki for guides on contributing to this project.__
|
43
|
+
|
44
|
+
## Acknowledgements
|
45
|
+
|
46
|
+
This gem was originally based on [stffn](https://github.com/stffn)'s gem [Declarative_Authorization](https://github.com/stffn/declarative_authorization). Many thanks to stffn and all who contributed to Declarative Authorization for a great gem!
|
47
|
+
|
48
|
+
|
49
|
+
## License
|
50
|
+
|
51
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
52
|
+
|
53
|
+
:copyright: 2016 Tektite Software
|
data/Rakefile
ADDED
data/authoreyes.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'authoreyes/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'authoreyes'
|
8
|
+
spec.version = Authoreyes::VERSION
|
9
|
+
spec.authors = ['Tektite Software', 'Xavier Bick']
|
10
|
+
spec.email = ['fxb9500@gmail.com']
|
11
|
+
|
12
|
+
spec.summary = 'A modern authorization plugin for Rails.'
|
13
|
+
spec.description = 'A powerful, modern authorization plugin for Ruby on
|
14
|
+
Rails featuring a declarative DSL for centralized
|
15
|
+
authorization roles.
|
16
|
+
Based on Declarative Authorization.'
|
17
|
+
spec.homepage = 'https://www.github.com/tektite-software/authoreyes'
|
18
|
+
spec.license = 'MIT'
|
19
|
+
|
20
|
+
|
21
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
spec.bindir = 'exe'
|
23
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
24
|
+
spec.require_paths = ['lib']
|
25
|
+
|
26
|
+
spec.add_development_dependency 'bundler', '~> 1.12'
|
27
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
28
|
+
spec.add_development_dependency 'minitest', '~> 5.0'
|
29
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
authorization do
|
2
|
+
role :guest do
|
3
|
+
# add permissions for guests here, e.g.
|
4
|
+
#has_permission_on :conferences, :to => :read
|
5
|
+
end
|
6
|
+
|
7
|
+
# permissions on other roles, such as
|
8
|
+
#role :admin do
|
9
|
+
# has_permission_on :conferences, :to => :manage
|
10
|
+
#end
|
11
|
+
end
|
12
|
+
|
13
|
+
privileges do
|
14
|
+
# default privilege hierarchies to facilitate RESTful Rails apps
|
15
|
+
privilege :manage, :includes => [:create, :read, :update, :delete]
|
16
|
+
privilege :read, :includes => [:index, :show]
|
17
|
+
privilege :create, :includes => :new
|
18
|
+
privilege :update, :includes => :edit
|
19
|
+
privilege :delete, :includes => :destroy
|
20
|
+
end
|
data/bin/console
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "bundler/setup"
|
4
|
+
require "authoreyes"
|
5
|
+
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
8
|
+
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
10
|
+
# require "pry"
|
11
|
+
# Pry.start
|
12
|
+
|
13
|
+
require "irb"
|
14
|
+
IRB.start
|
data/bin/setup
ADDED
data/lib/authoreyes.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
# Authorization
|
2
|
+
require 'rails'
|
3
|
+
require 'authoreyes/authorization/engine'
|
4
|
+
require 'authoreyes/authorization/authorization_rule_set'
|
5
|
+
require 'authoreyes/authorization/authorization_rule'
|
6
|
+
require 'authoreyes/authorization/attribute'
|
7
|
+
require 'authoreyes/authorization/attribute_with_permission'
|
8
|
+
require 'authoreyes/authorization/anonymous_user'
|
9
|
+
|
10
|
+
require "set"
|
11
|
+
require "forwardable"
|
12
|
+
|
13
|
+
module Authoreyes
|
14
|
+
module Authorization
|
15
|
+
# An exception raised if anything goes wrong in the Authorization realm
|
16
|
+
class AuthorizationError < StandardError ; end
|
17
|
+
# NotAuthorized is raised if the current user is not allowed to perform
|
18
|
+
# the given operation possibly on a specific object.
|
19
|
+
class NotAuthorized < AuthorizationError ; end
|
20
|
+
# AttributeAuthorizationError is more specific than NotAuthorized, signaling
|
21
|
+
# that the access was denied on the grounds of attribute conditions.
|
22
|
+
class AttributeAuthorizationError < NotAuthorized ; end
|
23
|
+
# AuthorizationUsageError is used whenever a situation is encountered
|
24
|
+
# in which the application misused the plugin. That is, if, e.g.,
|
25
|
+
# authorization rules may not be evaluated.
|
26
|
+
class AuthorizationUsageError < AuthorizationError ; end
|
27
|
+
# NilAttributeValueError is raised by Attribute#validate? when it hits a nil attribute value.
|
28
|
+
# The exception is raised to ensure that the entire rule is invalidated.
|
29
|
+
class NilAttributeValueError < AuthorizationError ; end
|
30
|
+
|
31
|
+
AUTH_DSL_FILES = [Pathname.new(Rails.root || '').join("config", "authorization_rules.rb").to_s] unless defined? AUTH_DSL_FILES
|
32
|
+
|
33
|
+
# Controller-independent method for retrieving the current user.
|
34
|
+
# Needed for model security where the current controller is not available.
|
35
|
+
def self.current_user
|
36
|
+
Thread.current["current_user"] || AnonymousUser.new
|
37
|
+
end
|
38
|
+
|
39
|
+
# Controller-independent method for setting the current user.
|
40
|
+
def self.current_user=(user)
|
41
|
+
Thread.current["current_user"] = user
|
42
|
+
end
|
43
|
+
|
44
|
+
# For use in test cases only
|
45
|
+
def self.ignore_access_control(state = nil) # :nodoc:
|
46
|
+
Thread.current["ignore_access_control"] = state unless state.nil?
|
47
|
+
Thread.current["ignore_access_control"] || false
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.activate_authorization_rules_browser? # :nodoc:
|
51
|
+
::Rails.env.development?
|
52
|
+
end
|
53
|
+
|
54
|
+
@@dot_path = "dot"
|
55
|
+
def self.dot_path
|
56
|
+
@@dot_path
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.dot_path= (path)
|
60
|
+
@@dot_path = path
|
61
|
+
end
|
62
|
+
|
63
|
+
@@default_role = :guest
|
64
|
+
def self.default_role
|
65
|
+
@@default_role
|
66
|
+
end
|
67
|
+
|
68
|
+
def self.default_role= (role)
|
69
|
+
@@default_role = role.to_sym
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.is_a_association_proxy? (object)
|
73
|
+
if Rails.version < "3.2"
|
74
|
+
object.respond_to?(:proxy_reflection)
|
75
|
+
else
|
76
|
+
object.respond_to?(:proxy_association)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,11 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
# Represents a pseudo-user to facilitate anonymous users in applications
|
4
|
+
class AnonymousUser
|
5
|
+
attr_reader :role_symbols
|
6
|
+
def initialize (roles = [Authorization.default_role])
|
7
|
+
@role_symbols = roles
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
module Authoreyes
|
2
|
+
module Authorization
|
3
|
+
class Attribute
|
4
|
+
# attr_conditions_hash of form
|
5
|
+
# { :object_attribute => [operator, value_block], ... }
|
6
|
+
# { :object_attribute => { :attr => ... } }
|
7
|
+
def initialize (conditions_hash)
|
8
|
+
@conditions_hash = conditions_hash
|
9
|
+
end
|
10
|
+
|
11
|
+
def initialize_copy (from)
|
12
|
+
@conditions_hash = deep_hash_clone(@conditions_hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
def validate? (attr_validator, object = nil, hash = nil)
|
16
|
+
object ||= attr_validator.object
|
17
|
+
return false unless object
|
18
|
+
|
19
|
+
if ( Authorization.is_a_association_proxy?(object) &&
|
20
|
+
object.respond_to?(:empty?) )
|
21
|
+
return false if object.empty?
|
22
|
+
object.each do |member|
|
23
|
+
return true if validate?(attr_validator, member, hash)
|
24
|
+
end
|
25
|
+
return false
|
26
|
+
end
|
27
|
+
|
28
|
+
(hash || @conditions_hash).all? do |attr, value|
|
29
|
+
attr_value = object_attribute_value(object, attr)
|
30
|
+
if value.is_a?(Hash)
|
31
|
+
if attr_value.is_a?(Enumerable)
|
32
|
+
attr_value.any? do |inner_value|
|
33
|
+
validate?(attr_validator, inner_value, value)
|
34
|
+
end
|
35
|
+
elsif attr_value == nil
|
36
|
+
raise NilAttributeValueError, "Attribute #{attr.inspect} is nil in #{object.inspect}."
|
37
|
+
else
|
38
|
+
validate?(attr_validator, attr_value, value)
|
39
|
+
end
|
40
|
+
elsif value.is_a?(Array) and value.length == 2 and value.first.is_a?(Symbol)
|
41
|
+
evaluated = if value[1].is_a?(Proc)
|
42
|
+
attr_validator.evaluate(value[1])
|
43
|
+
else
|
44
|
+
value[1]
|
45
|
+
end
|
46
|
+
case value[0]
|
47
|
+
when :is
|
48
|
+
attr_value == evaluated
|
49
|
+
when :is_not
|
50
|
+
attr_value != evaluated
|
51
|
+
when :contains
|
52
|
+
begin
|
53
|
+
attr_value.include?(evaluated)
|
54
|
+
rescue NoMethodError => e
|
55
|
+
raise AuthorizationUsageError, "Operator contains requires a " +
|
56
|
+
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
|
57
|
+
"contains #{evaluated.inspect}: #{e}"
|
58
|
+
end
|
59
|
+
when :does_not_contain
|
60
|
+
begin
|
61
|
+
!attr_value.include?(evaluated)
|
62
|
+
rescue NoMethodError => e
|
63
|
+
raise AuthorizationUsageError, "Operator does_not_contain requires a " +
|
64
|
+
"subclass of Enumerable as attribute value, got: #{attr_value.inspect} " +
|
65
|
+
"does_not_contain #{evaluated.inspect}: #{e}"
|
66
|
+
end
|
67
|
+
when :intersects_with
|
68
|
+
begin
|
69
|
+
!(evaluated.to_set & attr_value.to_set).empty?
|
70
|
+
rescue NoMethodError => e
|
71
|
+
raise AuthorizationUsageError, "Operator intersects_with requires " +
|
72
|
+
"subclasses of Enumerable, got: #{attr_value.inspect} " +
|
73
|
+
"intersects_with #{evaluated.inspect}: #{e}"
|
74
|
+
end
|
75
|
+
when :is_in
|
76
|
+
begin
|
77
|
+
evaluated.include?(attr_value)
|
78
|
+
rescue NoMethodError => e
|
79
|
+
raise AuthorizationUsageError, "Operator is_in requires a " +
|
80
|
+
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
|
81
|
+
"is_in #{evaluated.inspect}: #{e}"
|
82
|
+
end
|
83
|
+
when :is_not_in
|
84
|
+
begin
|
85
|
+
!evaluated.include?(attr_value)
|
86
|
+
rescue NoMethodError => e
|
87
|
+
raise AuthorizationUsageError, "Operator is_not_in requires a " +
|
88
|
+
"subclass of Enumerable as value, got: #{attr_value.inspect} " +
|
89
|
+
"is_not_in #{evaluated.inspect}: #{e}"
|
90
|
+
end
|
91
|
+
when :lt
|
92
|
+
attr_value && attr_value < evaluated
|
93
|
+
when :lte
|
94
|
+
attr_value && attr_value <= evaluated
|
95
|
+
when :gt
|
96
|
+
attr_value && attr_value > evaluated
|
97
|
+
when :gte
|
98
|
+
attr_value && attr_value >= evaluated
|
99
|
+
else
|
100
|
+
raise AuthorizationError, "Unknown operator #{value[0]}"
|
101
|
+
end
|
102
|
+
else
|
103
|
+
raise AuthorizationError, "Wrong conditions hash format"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# resolves all the values in condition_hash
|
109
|
+
def obligation (attr_validator, hash = nil)
|
110
|
+
hash = (hash || @conditions_hash).clone
|
111
|
+
hash.each do |attr, value|
|
112
|
+
if value.is_a?(Hash)
|
113
|
+
hash[attr] = obligation(attr_validator, value)
|
114
|
+
elsif value.is_a?(Array) and value.length == 2
|
115
|
+
hash[attr] = [value[0], attr_validator.evaluate(value[1])]
|
116
|
+
else
|
117
|
+
raise AuthorizationError, "Wrong conditions hash format"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
hash
|
121
|
+
end
|
122
|
+
|
123
|
+
def to_long_s (hash = nil)
|
124
|
+
if hash
|
125
|
+
hash.inject({}) do |memo, key_val|
|
126
|
+
key, val = key_val
|
127
|
+
memo[key] = case val
|
128
|
+
when Array then "#{val[0]} { #{val[1].respond_to?(:to_ruby) ? val[1].to_ruby.gsub(/^proc \{\n?(.*)\n?\}$/m, '\1') : "..."} }"
|
129
|
+
when Hash then to_long_s(val)
|
130
|
+
end
|
131
|
+
memo
|
132
|
+
end
|
133
|
+
else
|
134
|
+
"if_attribute #{to_long_s(@conditions_hash).inspect}"
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
protected
|
139
|
+
def object_attribute_value (object, attr)
|
140
|
+
begin
|
141
|
+
object.send(attr)
|
142
|
+
rescue ArgumentError, NoMethodError => e
|
143
|
+
raise AuthorizationUsageError, "Error occurred while validating attribute ##{attr} on #{object.inspect}: #{e}.\n" +
|
144
|
+
"Please check your authorization rules and ensure the attribute is correctly spelled and \n" +
|
145
|
+
"corresponds to a method on the model you are authorizing for."
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
def deep_hash_clone (hash)
|
150
|
+
hash.inject({}) do |memo, (key, val)|
|
151
|
+
memo[key] = case val
|
152
|
+
when Hash
|
153
|
+
deep_hash_clone(val)
|
154
|
+
when NilClass, Symbol
|
155
|
+
val
|
156
|
+
else
|
157
|
+
val.clone
|
158
|
+
end
|
159
|
+
memo
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|