stasi 0.0.1.alpha → 0.1.0
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/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:
|