stasi 0.0.1.alpha → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile.lock +2 -2
- data/README.md +28 -6
- data/lib/stasi.rb +0 -2
- data/lib/stasi/authorization/law.rb +20 -11
- data/lib/stasi/authorization/status.rb +29 -15
- data/lib/stasi/authorization/watch.rb +3 -1
- data/test/law_test.rb +41 -15
- data/test/status_test.rb +69 -15
- data/test/test_helper.rb +0 -1
- data/test/watch_test.rb +2 -14
- metadata +11 -11
data/Gemfile.lock
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
stasi (0.
|
5
|
-
activesupport (>= 4.0.0)
|
4
|
+
stasi (0.1.0)
|
6
5
|
dsl_eval (>= 0.0.2)
|
7
6
|
|
8
7
|
GEM
|
@@ -31,6 +30,7 @@ PLATFORMS
|
|
31
30
|
java
|
32
31
|
|
33
32
|
DEPENDENCIES
|
33
|
+
activesupport (>= 4.0.0)
|
34
34
|
bundler (~> 1.3)
|
35
35
|
minitest (~> 4.2)
|
36
36
|
rake
|
data/README.md
CHANGED
@@ -17,30 +17,52 @@ include Robotnik::Authorization::Watch
|
|
17
17
|
Then you can check permissions this way :
|
18
18
|
|
19
19
|
```ruby
|
20
|
-
user.can? :read,
|
20
|
+
user.can? :read, @post
|
21
21
|
```
|
22
22
|
|
23
23
|
You define permissions in an initializer :
|
24
24
|
|
25
25
|
```ruby
|
26
26
|
Robotnik::Authorization::Law.define do
|
27
|
+
|
28
|
+
default do
|
29
|
+
can :read, Post
|
30
|
+
end
|
27
31
|
|
28
32
|
status :admin do
|
29
|
-
can
|
30
|
-
can :destroy,
|
33
|
+
can :edit, Post, if: Proc.new{ |post| post.editable }
|
34
|
+
can :destroy, Post
|
31
35
|
end
|
32
36
|
|
33
37
|
status :guest do
|
34
|
-
can :
|
38
|
+
can :comment, :commentable
|
35
39
|
end
|
36
40
|
|
37
41
|
end
|
38
42
|
```
|
39
43
|
|
40
44
|
Undefined permissions default to `false`.
|
41
|
-
`:admin` and `:guest`, in this example, must be
|
45
|
+
`:admin` and `:guest`, in this example, must be method names on the `user` object. The only method name that is not allowed is `:default`, as `status :default` is equivalent to `default`.
|
46
|
+
|
47
|
+
The `can` method takes two arguments : an action name as a symbol, and a resource. The resource can be :
|
48
|
+
|
49
|
+
* a class, eg. `Post`
|
50
|
+
* a symbol, eg. `:commentable`. The authorization will be applied if `@post.commentable` returns `true`. This method can take one argument, in which case, the user object will be passed to it.
|
51
|
+
|
52
|
+
Optionnally, the `can` method can take a hash with conditions (hash keys can be `if` and `unless`, values can be Proc. The resource tested will be yielded).
|
53
|
+
Finally, the `can` method can take a block, in which case the `can?` method will return the return value of the block. This is useful when defining abilities on collections :
|
54
|
+
|
55
|
+
```ruby
|
56
|
+
can :index, Post do |posts|
|
57
|
+
posts.where(published: true)
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
The `cannot` method takes only two arguments : the action name, and the resource.
|
42
62
|
|
43
63
|
## Milestones
|
44
64
|
|
45
|
-
*
|
65
|
+
* yield user to blocks and procs in defining abilities
|
66
|
+
* pass symbol or proc to `:if` and `:unless` conditions
|
67
|
+
* alias actions :manage, :all, :read => [:index, :show], :create => [:new, :create], …
|
46
68
|
* load specific permissions from db
|
data/lib/stasi.rb
CHANGED
@@ -5,8 +5,6 @@ module Robotnik
|
|
5
5
|
#--- Class methods ---#
|
6
6
|
|
7
7
|
include Robotnik::DslEval
|
8
|
-
|
9
|
-
cattr_reader :law
|
10
8
|
|
11
9
|
def self.define &block
|
12
10
|
@@law = self.new.evaluate(&block)
|
@@ -16,23 +14,31 @@ module Robotnik
|
|
16
14
|
@@law = nil
|
17
15
|
end
|
18
16
|
|
17
|
+
def self.law
|
18
|
+
@@law
|
19
|
+
end
|
20
|
+
|
19
21
|
#--- Instance methods ---#
|
20
22
|
|
23
|
+
attr_reader :statuses
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@statuses = []
|
27
|
+
end
|
28
|
+
|
21
29
|
def status method, &block
|
22
30
|
init_role_for(method).evaluate &block
|
23
31
|
end
|
24
32
|
|
25
|
-
def
|
26
|
-
|
33
|
+
def default &block
|
34
|
+
status :default, &block
|
27
35
|
end
|
28
36
|
|
29
|
-
def can?
|
30
|
-
subject, args = args.shift, args
|
37
|
+
def can? action, resource, options
|
31
38
|
verdict = false
|
32
39
|
statuses.each do |status|
|
33
|
-
if
|
34
|
-
verdict = rules[status].can?
|
35
|
-
break
|
40
|
+
if status == :default || status.to_proc.call(options[:agent])
|
41
|
+
verdict = rules[status].can? action.to_sym, resource, options
|
36
42
|
end
|
37
43
|
end
|
38
44
|
verdict
|
@@ -41,11 +47,14 @@ module Robotnik
|
|
41
47
|
private
|
42
48
|
|
43
49
|
def rules
|
44
|
-
@rules ||= {}
|
50
|
+
@rules ||= {}
|
45
51
|
end
|
46
52
|
|
47
53
|
def init_role_for method
|
48
|
-
rules[method] ||=
|
54
|
+
rules[method] ||= begin
|
55
|
+
@statuses << method
|
56
|
+
Robotnik::Authorization::Status.new
|
57
|
+
end
|
49
58
|
end
|
50
59
|
|
51
60
|
end
|
@@ -14,31 +14,45 @@ module Robotnik
|
|
14
14
|
rules[resource][action] = false
|
15
15
|
end
|
16
16
|
|
17
|
-
def can? action, resource,
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
17
|
+
def can? action, resource, options={}
|
18
|
+
verdict = false
|
19
|
+
rules.each do |rule_condition, actions|
|
20
|
+
if Robotnik::Authorization::Status.matches? rule_condition, resource, options
|
21
|
+
action_condition = actions[action]
|
22
|
+
verdict = case action_condition
|
23
|
+
when true, false then action_condition
|
24
|
+
else
|
25
|
+
if action_condition.respond_to?(:call)
|
26
|
+
action_condition.call(resource)
|
27
|
+
else
|
28
|
+
deliberation = true
|
29
|
+
deliberation = deliberation && action_condition[:if].call(resource) if action_condition.has_key?(:if)
|
30
|
+
deliberation = deliberation && (! action_condition[:unless].call(resource)) if deliberation && action_condition.has_key?(:unless)
|
31
|
+
deliberation
|
32
|
+
end
|
33
|
+
end
|
28
34
|
end
|
29
|
-
|
30
|
-
|
35
|
+
end
|
36
|
+
verdict
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.matches? rule_condition, resource, options
|
40
|
+
rule_condition = rule_condition.to_proc if rule_condition.respond_to?(:to_proc)
|
41
|
+
begin
|
42
|
+
rule_condition === resource
|
43
|
+
rescue ArgumentError
|
44
|
+
rule_condition.call(resource, options[:agent])
|
31
45
|
end
|
32
46
|
end
|
33
47
|
|
34
48
|
private
|
35
49
|
|
36
50
|
def rules
|
37
|
-
@rules ||= {}
|
51
|
+
@rules ||= {}
|
38
52
|
end
|
39
53
|
|
40
54
|
def init_rule_for resource
|
41
|
-
rules[resource] ||= {}
|
55
|
+
rules[resource] ||= {}
|
42
56
|
end
|
43
57
|
|
44
58
|
end
|
data/test/law_test.rb
CHANGED
@@ -8,7 +8,7 @@ class LawTest < ActiveSupport::TestCase
|
|
8
8
|
|
9
9
|
test "it has rules" do
|
10
10
|
rules = @law.send :rules
|
11
|
-
assert_instance_of
|
11
|
+
assert_instance_of Hash, rules
|
12
12
|
end
|
13
13
|
|
14
14
|
test "it initiates rules for role" do
|
@@ -20,20 +20,20 @@ class LawTest < ActiveSupport::TestCase
|
|
20
20
|
|
21
21
|
test "it yields status for authorization definition" do
|
22
22
|
@law.status :admin do
|
23
|
-
can :read,
|
23
|
+
can :read, Object
|
24
24
|
end
|
25
|
-
assert @law.instance_variable_get('@rules')[:admin].can? :read,
|
25
|
+
assert @law.instance_variable_get('@rules')[:admin].can? :read, Object.new
|
26
26
|
end
|
27
27
|
|
28
28
|
test "it has statuses" do
|
29
29
|
@law.status :admin do
|
30
30
|
end
|
31
|
-
assert_equal [
|
31
|
+
assert_equal [:admin], @law.statuses
|
32
32
|
end
|
33
33
|
|
34
34
|
test "it tests status on given object" do
|
35
|
-
|
36
|
-
class <<
|
35
|
+
user = Object.new
|
36
|
+
class << user
|
37
37
|
def admin
|
38
38
|
true
|
39
39
|
end
|
@@ -42,13 +42,13 @@ class LawTest < ActiveSupport::TestCase
|
|
42
42
|
end
|
43
43
|
end
|
44
44
|
@law.status :admin do
|
45
|
-
can :read,
|
45
|
+
can :read, Object
|
46
46
|
end
|
47
47
|
@law.status :not_admin do
|
48
|
-
cannot :read,
|
48
|
+
cannot :read, Object
|
49
49
|
end
|
50
|
-
assert @law.can?
|
51
|
-
class <<
|
50
|
+
assert @law.can? :read, Object.new, {agent: user}
|
51
|
+
class << user
|
52
52
|
def admin
|
53
53
|
false
|
54
54
|
end
|
@@ -56,19 +56,19 @@ class LawTest < ActiveSupport::TestCase
|
|
56
56
|
true
|
57
57
|
end
|
58
58
|
end
|
59
|
-
refute @law.can?
|
59
|
+
refute @law.can? :read, Object.new, {agent: user}
|
60
60
|
end
|
61
61
|
|
62
62
|
test "it returns false if no status returns true" do
|
63
|
-
|
64
|
-
class <<
|
63
|
+
user = Object.new
|
64
|
+
class << user
|
65
65
|
def admin
|
66
66
|
false
|
67
67
|
end
|
68
68
|
end
|
69
69
|
@law.status :admin do
|
70
70
|
end
|
71
|
-
refute @law.can?
|
71
|
+
refute @law.can? :do, :something, {agent: user}
|
72
72
|
end
|
73
73
|
|
74
74
|
test "it defines a new law at class level" do
|
@@ -76,7 +76,33 @@ class LawTest < ActiveSupport::TestCase
|
|
76
76
|
status :admin do
|
77
77
|
end
|
78
78
|
end
|
79
|
-
assert_equal [
|
79
|
+
assert_equal [:admin], Robotnik::Authorization::Law.law.statuses
|
80
|
+
end
|
81
|
+
|
82
|
+
test "it parses all stauses" do
|
83
|
+
user = Object.new
|
84
|
+
class << user
|
85
|
+
def is_a_a
|
86
|
+
true
|
87
|
+
end
|
88
|
+
def is_a_b
|
89
|
+
true
|
90
|
+
end
|
91
|
+
end
|
92
|
+
@law.status :is_a_a do
|
93
|
+
can :read, Object
|
94
|
+
end
|
95
|
+
@law.status :is_a_b do
|
96
|
+
cannot :read, Object
|
97
|
+
end
|
98
|
+
refute @law.can? :read, Object.new, {agent: user}
|
99
|
+
end
|
100
|
+
|
101
|
+
test "it accepts defaults" do
|
102
|
+
@law.default do
|
103
|
+
can :read, Object
|
104
|
+
end
|
105
|
+
assert @law.can? :read, Object.new, {agent: Object.new}
|
80
106
|
end
|
81
107
|
|
82
108
|
end
|
data/test/status_test.rb
CHANGED
@@ -2,59 +2,113 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class StatusTest < ActiveSupport::TestCase
|
4
4
|
|
5
|
+
Book = Class.new
|
6
|
+
|
5
7
|
def setup
|
6
8
|
@status = Robotnik::Authorization::Status.new
|
7
9
|
end
|
8
10
|
|
9
11
|
test "it has rules" do
|
10
12
|
rules = @status.send :rules
|
11
|
-
assert_instance_of
|
13
|
+
assert_instance_of Hash, rules
|
12
14
|
end
|
13
15
|
|
14
16
|
test "it initiates rules for resource" do
|
15
17
|
@status.send :init_rule_for, :resource
|
16
18
|
rules = @status.instance_variable_get('@rules')
|
17
19
|
assert rules.has_key? :resource
|
18
|
-
assert_instance_of
|
20
|
+
assert_instance_of Hash, rules[:resource]
|
19
21
|
end
|
20
22
|
|
21
23
|
test "it defines prohibition" do
|
22
|
-
@status.cannot :read,
|
23
|
-
refute @status.can? :read,
|
24
|
+
@status.cannot :read, Book
|
25
|
+
refute @status.can? :read, Book.new
|
26
|
+
assert_equal false, @status.instance_variable_get('@rules')[Book][:read]
|
24
27
|
end
|
25
28
|
|
26
29
|
test "it defines strict authorization" do
|
27
|
-
@status.can :read,
|
28
|
-
assert @status.can? :read,
|
30
|
+
@status.can :read, Book
|
31
|
+
assert @status.can? :read, Book.new
|
32
|
+
assert_equal true, @status.instance_variable_get('@rules')[Book][:read]
|
29
33
|
end
|
30
34
|
|
31
35
|
test "it defines authorization with if and unless options" do
|
36
|
+
Post = Struct.new :name
|
32
37
|
assertions = [true, false, false, true, false, true, false, false]
|
33
38
|
[[true, nil], [nil, true], [false, nil], [nil, false], [true, true], [true, false], [false, true], [false, false]].each_with_index do |conditions, i|
|
34
39
|
conditions_hash = {}
|
35
40
|
[:if, :unless].each_with_index do |operator, j|
|
36
41
|
unless conditions[j].nil?
|
37
42
|
if conditions[j]
|
38
|
-
conditions_hash[operator] = Proc.new{ |
|
43
|
+
conditions_hash[operator] = Proc.new{ |post| post.name == 'test' }
|
39
44
|
else
|
40
|
-
conditions_hash[operator] = Proc.new{ |
|
45
|
+
conditions_hash[operator] = Proc.new{ |post| post.name != 'test' }
|
41
46
|
end
|
42
47
|
end
|
43
48
|
end
|
44
|
-
@status.can :read,
|
45
|
-
assert_equal assertions[i], @status.can?(:read,
|
49
|
+
@status.can :read, Post, conditions_hash
|
50
|
+
assert_equal assertions[i], @status.can?(:read, Post.new('test'))
|
46
51
|
end
|
47
52
|
end
|
48
53
|
|
49
54
|
test "it defines authorizations with a block" do
|
50
|
-
|
51
|
-
|
55
|
+
Book.class_eval do
|
56
|
+
attr_accessor :collection
|
57
|
+
def self.all
|
58
|
+
Book.new.tap do |book|
|
59
|
+
book.collection = [1, 2, 3, 4]
|
60
|
+
end
|
61
|
+
end
|
52
62
|
end
|
53
|
-
|
63
|
+
@status.can :read, Book do |books|
|
64
|
+
books.collection.first 2
|
65
|
+
end
|
66
|
+
assert_equal [1, 2], @status.can?(:read, Book.all)
|
54
67
|
end
|
55
68
|
|
56
69
|
test "it defaults to forbidden if permission is not defined" do
|
57
|
-
refute @status.can? :be, :free
|
58
|
-
end
|
70
|
+
refute @status.can? :be, :free
|
71
|
+
end
|
72
|
+
|
73
|
+
test "it accepts a class" do
|
74
|
+
@status.can :read, Object
|
75
|
+
assert @status.can? :read, Object.new
|
76
|
+
assert @status.can? :read, Object
|
77
|
+
end
|
78
|
+
|
79
|
+
test "it accepts a symbol as a method name" do
|
80
|
+
o = Object.new
|
81
|
+
class << o
|
82
|
+
def taggable
|
83
|
+
true
|
84
|
+
end
|
85
|
+
end
|
86
|
+
@status.can :read, :taggable
|
87
|
+
assert @status.can? :read, o
|
88
|
+
end
|
89
|
+
|
90
|
+
test "it accepts a symbol for a method with one argument and evaluates it with the agent" do
|
91
|
+
o = Object.new
|
92
|
+
class << o
|
93
|
+
def taggable user
|
94
|
+
user[:name] == 'test'
|
95
|
+
end
|
96
|
+
end
|
97
|
+
@status.can :read, :taggable
|
98
|
+
assert @status.can? :read, o, {agent: {name: 'test'}}
|
99
|
+
refute @status.can? :read, o, {agent: {name: 'tested'}}
|
100
|
+
end
|
101
|
+
|
102
|
+
test "the last matching condition is returned" do
|
103
|
+
o = Object.new
|
104
|
+
class << o
|
105
|
+
def taggable
|
106
|
+
true
|
107
|
+
end
|
108
|
+
end
|
109
|
+
@status.cannot :read, Object
|
110
|
+
@status.can :read, :taggable
|
111
|
+
assert @status.can? :read, o
|
112
|
+
end
|
59
113
|
|
60
114
|
end
|
data/test/test_helper.rb
CHANGED
data/test/watch_test.rb
CHANGED
@@ -2,20 +2,8 @@ require 'test_helper'
|
|
2
2
|
|
3
3
|
class WatchTest < ActiveSupport::TestCase
|
4
4
|
|
5
|
-
test "
|
6
|
-
|
7
|
-
object.extend Robotnik::Authorization::Watch
|
8
|
-
class << object
|
9
|
-
def admin
|
10
|
-
true
|
11
|
-
end
|
12
|
-
end
|
13
|
-
Robotnik::Authorization::Law.define do
|
14
|
-
status :admin do
|
15
|
-
can :do, :something
|
16
|
-
end
|
17
|
-
end
|
18
|
-
assert object.can?(:do, :something, :stupid)
|
5
|
+
test "passes the watched agent in the options hash" do
|
6
|
+
skip "could not figure out how to use assert_send"
|
19
7
|
end
|
20
8
|
|
21
9
|
end
|
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: stasi
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
prerelease:
|
5
|
-
version: 0.
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Paul Vonderscher
|
@@ -76,34 +76,34 @@ dependencies:
|
|
76
76
|
prerelease: false
|
77
77
|
type: :development
|
78
78
|
- !ruby/object:Gem::Dependency
|
79
|
-
name:
|
79
|
+
name: activesupport
|
80
80
|
version_requirements: !ruby/object:Gem::Requirement
|
81
81
|
requirements:
|
82
82
|
- - '>='
|
83
83
|
- !ruby/object:Gem::Version
|
84
|
-
version: 0.0
|
84
|
+
version: 4.0.0
|
85
85
|
none: false
|
86
86
|
requirement: !ruby/object:Gem::Requirement
|
87
87
|
requirements:
|
88
88
|
- - '>='
|
89
89
|
- !ruby/object:Gem::Version
|
90
|
-
version: 0.0
|
90
|
+
version: 4.0.0
|
91
91
|
none: false
|
92
92
|
prerelease: false
|
93
|
-
type: :
|
93
|
+
type: :development
|
94
94
|
- !ruby/object:Gem::Dependency
|
95
|
-
name:
|
95
|
+
name: dsl_eval
|
96
96
|
version_requirements: !ruby/object:Gem::Requirement
|
97
97
|
requirements:
|
98
98
|
- - '>='
|
99
99
|
- !ruby/object:Gem::Version
|
100
|
-
version:
|
100
|
+
version: 0.0.2
|
101
101
|
none: false
|
102
102
|
requirement: !ruby/object:Gem::Requirement
|
103
103
|
requirements:
|
104
104
|
- - '>='
|
105
105
|
- !ruby/object:Gem::Version
|
106
|
-
version:
|
106
|
+
version: 0.0.2
|
107
107
|
none: false
|
108
108
|
prerelease: false
|
109
109
|
type: :runtime
|
@@ -144,9 +144,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
144
144
|
none: false
|
145
145
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
146
146
|
requirements:
|
147
|
-
- - '
|
147
|
+
- - '>='
|
148
148
|
- !ruby/object:Gem::Version
|
149
|
-
version:
|
149
|
+
version: '0'
|
150
150
|
none: false
|
151
151
|
requirements: []
|
152
152
|
rubyforge_project:
|