canned 0.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,130 @@
1
+ module Canned
2
+
3
+ ## Holds all rules associated to a single user profile.
4
+ #
5
+ # This class describes the avaliable DSL when defining a new profile.
6
+ # TODO: example
7
+ #
8
+ class ProfileDsl
9
+
10
+ def initialize(_profile, _loaded_profiles)
11
+ @profile = _profile
12
+ @loaded_profiles = _loaded_profiles
13
+ end
14
+
15
+ ## Sets the default context for this profile block.
16
+ def context(_proc=nil, &_block)
17
+ @profile.context = _proc || _block
18
+ end
19
+
20
+ ## Adds an "allowance" rule
21
+ #
22
+ # Examples:
23
+ # allow 'index'
24
+ # allow 'index', upon(:user) { that(:is_admin) }
25
+ # allow('index') { upon(:user).that(:is_admin) }
26
+ # allow
27
+ # allow upon(:user) { that(:is_admin) }
28
+ # allow { upon(:user).that(:is_admin) }
29
+ #
30
+ # @param [String|Proc] _action The action to authorize, if no action is given then rule apply to any action.
31
+ # @param [Proc] _proc The test procedure, if not given, then action is always allowed.
32
+ #
33
+ def allow(_action=nil, _proc=nil)
34
+
35
+ if _action.is_a? Proc
36
+ _proc = _action
37
+ _action = nil
38
+ end
39
+
40
+ @profile.rules << { type: :allow, action: _action, proc: _proc }
41
+ end
42
+
43
+ ## Adds a "forbidden" rule
44
+ #
45
+ # Works the same way as **allow** but if rule checks then user is forbidden to access
46
+ # the resource regardles of presenting another profile that passes.
47
+ #
48
+ def forbid(_action=nil, _proc=nil)
49
+
50
+ if _action.is_a? Proc
51
+ _proc = _action
52
+ _action = nil
53
+ end
54
+
55
+ @profile.rules << { type: :forbid, action: _action, proc: _proc }
56
+ end
57
+
58
+ ## Breaks from the current profile scope if condition is not match.
59
+ #
60
+ # When calling this function from within a scope, it will only break from scope.
61
+ #
62
+ # Example:
63
+ # # The following rules will be tested against every user
64
+ # allow 'index', upon(:user) { that(:is_registered) }
65
+ # allow 'index', upon(:user) { that(:is_alien) }
66
+ # allow 'index', upon(:user) { that(:is_chewbaka) }
67
+ #
68
+ # continue upon(:user) { that(:is_jedi) }
69
+ # # The following rules will only be tested against jedis
70
+ # allow 'index', upon(:user) { with(:force).greater_than(100) }
71
+ #
72
+ #
73
+ def continue(_proc)
74
+ @profile.rules << { type: :continue, proc: _proc }
75
+ end
76
+
77
+ ## Embedds a _profile inside another one.
78
+ #
79
+ def expand(_profile)
80
+ profile = @loaded_profiles[_profile]
81
+ raise SetupError.new "Profile not found '#{_profile}'" if profile.nil?
82
+ @profile.rules << { type: :expand, profile: profile }
83
+ end
84
+
85
+ ## Allows defining a set of rules with common options.
86
+ #
87
+ # Example:
88
+ # # The following group
89
+ # scope upon: :user
90
+ # # the following only breaks from current scope.
91
+ # continue upon { that(:is_jedi) }
92
+ # # the following will only forbid jedis that belong to death star (resource).
93
+ # forbid 'index', upon { belongs_to(:death_star) }
94
+ # allow 'index', upon(:user) { that(:has_pony_tail) }
95
+ # end
96
+ #
97
+ # # the following rule will be tested against every user.
98
+ # allow 'index', upon(:user) { that(:is_registered) }
99
+ #
100
+ #
101
+ def scope(&_block)
102
+ child = Profile.new
103
+ ProfileDsl.new(child, @loaded_profiles).instance_eval &_block
104
+ @profile.rules << { type: :scope, profile: child }
105
+ end
106
+
107
+ ## SHORT HAND METHODS
108
+
109
+ ## Same as calling upon { the() ... }
110
+ def upon(*_args, &_block)
111
+ if _args.count == 0
112
+ return _block
113
+ elsif _block
114
+ Proc.new { the(*_args).instance_eval &_block }
115
+ else
116
+ Proc.new { the(*_args) }
117
+ end
118
+ end
119
+
120
+ # TODO: Implement following
121
+
122
+ # def upon_one(_expr, &_block)
123
+ # Proc.new { upon_one(_expr, &_block) }
124
+ # end
125
+
126
+ # def upon_all(_expr, &_block)
127
+ # Proc.new { upon_all(_expr, &_block) }
128
+ # end
129
+ end
130
+ end
@@ -0,0 +1,63 @@
1
+ module Canned
2
+
3
+ ## Implements an inmmutable stack where every operation generates a new stack
4
+ #
5
+ # Used by the test contexts to hold the subject stack.
6
+ #
7
+ class InmmutableStack
8
+
9
+ class NotFound < Exception; end
10
+
11
+ ## Class used to hold stack entries
12
+ class Entry
13
+
14
+ attr_reader :tag # entry tag
15
+ attr_reader :name # entry name
16
+ attr_reader :obj # entry data
17
+
18
+ def initialize(_tag, _name, _obj)
19
+ @tag = _tag
20
+ @name = _name.to_s
21
+ @obj = _obj
22
+ end
23
+ end
24
+
25
+ def initialize(_entry=nil, _tail=nil)
26
+ @stack = if _tail then _tail.entries else [] end
27
+ @stack << _entry if _entry
28
+ end
29
+
30
+ ## Gets a copy of the internal stack state.
31
+ #
32
+ def entries; @stack.clone; end
33
+
34
+ ## Returns true if stack is empty
35
+ def empty?; @stack.empty?; end
36
+
37
+ ## Creates a new stack using the current stack as tail.
38
+ #
39
+ def push(_tag, _name, _value)
40
+ InmmutableStack.new Entry.new(_tag, _name, _value), self
41
+ end
42
+
43
+ ## Retrieves the top value of the stack
44
+ #
45
+ def top(_tag=nil)
46
+ return @stack.last.obj if _tag.nil?
47
+ @stack.reverse_each do |item|
48
+ return item.obj if item.tag == _tag
49
+ end
50
+ return nil
51
+ end
52
+
53
+ ## Resolves a stack value by it's name
54
+ #
55
+ def resolve(_name)
56
+ _name = _name.to_s
57
+ @stack.reverse_each do |item|
58
+ return item.obj if item.name == _name
59
+ end
60
+ raise NotFound
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,3 @@
1
+ module Canned
2
+ VERSION = "0.1.4"
3
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helper'
2
+
3
+ describe Canned do
4
+
5
+ describe "TestProfiles.validate" do
6
+
7
+ context 'when using profile with a context' do
8
+
9
+ let(:definition) do
10
+ class TestProfiles
11
+ include Canned::Definition
12
+
13
+ profile :profile do
14
+ context { the(:user) }
15
+ allow 'rute1', upon { asks_with_same_id(:app_id) }
16
+ allow 'rute2', upon { asks_for(:test) }
17
+ forbid 'rute3', upon { not is(:is_admin) }
18
+ end
19
+ end
20
+ TestProfiles
21
+ end
22
+
23
+ context 'and a matching context' do
24
+ let(:context) do
25
+ dummy(
26
+ action_name: 'test',
27
+ actors: { user: dummy(app_id: 10, is_admin: false) },
28
+ resources: {},
29
+ params: { app_id: "10" }
30
+ )
31
+ end
32
+
33
+ it "is allowed if asks for same id" do
34
+ definition.validate(context, :profile, 'rute1').should == :allowed
35
+ end
36
+
37
+ it "is allowed if asks for same action" do
38
+ definition.validate(context, :profile, 'rute2').should == :allowed
39
+ end
40
+
41
+ it "is forbidden if 'is' expression returns true" do
42
+ definition.validate(context, :profile, 'rute3').should == :forbidden
43
+ end
44
+ end
45
+ end
46
+
47
+ context 'when using simple profile' do
48
+
49
+ let(:definition) do
50
+ class TestProfiles
51
+ include Canned::Definition
52
+
53
+ profile :profile do
54
+ allow 'action1', upon(:user) { asks_with_same_id(:app_id) }
55
+ allow 'action2', upon(:user) { belongs_to(:app, as: :app) }
56
+ allow 'action3', upon(:user) { asks_with_same(:app_id) }
57
+ end
58
+ end
59
+ TestProfiles
60
+ end
61
+
62
+ context 'and an allowed context with actor and resource' do
63
+ let(:context) do
64
+ dummy(
65
+ action_name: 'test',
66
+ actors: { user: dummy(app_id: 10, is_admin: true) },
67
+ resources: { app: dummy(id: 10) },
68
+ params: { app_id: "10" }
69
+ )
70
+ end
71
+
72
+ it "is allowed if calls asks_for_same_id" do
73
+ definition.validate(context, :profile, 'action1').should == :allowed
74
+ end
75
+
76
+ it "is allowed if belongs_to resource" do
77
+ definition.validate(context, :profile, 'action2').should == :allowed
78
+ end
79
+
80
+ it "is not allowed if asks_for_same instead of asks_same_id" do
81
+ definition.validate(context, :profile, 'action3').should == :default
82
+ end
83
+ end
84
+
85
+ context 'and does not ask for same id' do
86
+ let(:context) do
87
+ dummy(
88
+ action_name: 'test',
89
+ actors: { user: dummy(app_id: 10, is_admin: true) },
90
+ resources: {},
91
+ params: { app_id: "11" }
92
+ )
93
+ end
94
+
95
+ it "is not allowed" do
96
+ definition.validate(context, :profile, 'action1').should == :default
97
+ end
98
+ end
99
+
100
+ context 'and does not belong to resource' do
101
+ let(:context) do
102
+ dummy(
103
+ action_name: 'test',
104
+ actors: { user: dummy(app_id: 10, is_admin: true) },
105
+ resources: { app: dummy(id: 11) },
106
+ params: {}
107
+ )
108
+ end
109
+
110
+ it "is not allowed" do
111
+ definition.validate(context, :profile, 'action2').should == :default
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,95 @@
1
+ require 'spec_helper'
2
+
3
+ describe Canned::ControllerExt do
4
+
5
+ let(:controller_class) do
6
+ controller_class = Class.new
7
+ controller_class.send(:include, Canned::ControllerExt)
8
+ controller_class
9
+ end
10
+
11
+ let(:controller) do
12
+ controller = controller_class.new
13
+ controller.stub(:params) { { app_id: 10 } }
14
+ controller.stub(:controller_name) { 'controller' }
15
+ controller.stub(:action_name) { 'action' }
16
+ controller.stub(:good_user) { dummy(app_id: 10, is_admin: true) }
17
+ controller.stub(:bad_user) { dummy(app_id: 11) }
18
+ controller
19
+ end
20
+
21
+ let(:definition) do
22
+ class TestProfiles
23
+ include Canned::Definition
24
+
25
+ profile :profile do
26
+ allow 'controller#action', upon(:user) { asks_with_id(:app_id).equal_to(own: :app_id) }
27
+ end
28
+
29
+ profile :profile2 do
30
+ allow 'controller#action', upon(:user) { belongs_to(:resource, as: :app) }
31
+ end
32
+ end
33
+ TestProfiles
34
+ end
35
+
36
+ describe ".is_restricted?" do
37
+ context 'when action is restricted' do
38
+ it { controller.is_restricted?.should be_true }
39
+ end
40
+ context 'when action is not restricted' do
41
+ before { controller_class.unrestricted :action }
42
+ it { controller.is_restricted?.should be_false }
43
+ end
44
+ end
45
+
46
+ describe ".perform_resource_loading" do
47
+
48
+ context 'when registering resource for another action' do
49
+ let(:proxy) do
50
+ controller_class.register_resource(:resource, only: [:other]) { HashObj.new(id: 10) }
51
+ controller.perform_resource_loading
52
+ end
53
+ it { proxy.resources.has_key?(:resource).should be_false }
54
+ end
55
+
56
+ context 'when registering resource except for this action' do
57
+ let(:proxy) do
58
+ controller_class.register_resource(:resource, except: [:action]) { HashObj.new(id: 10) }
59
+ controller.perform_resource_loading
60
+ end
61
+ it { proxy.resources.has_key?(:resource).should be_false }
62
+ end
63
+
64
+ context 'when registering actor in a super class' do
65
+ let(:proxy) do
66
+ controller_class.register_actor(:actor) { HashObj.new(id: 10) }
67
+ sub_class = Class.new controller_class
68
+ other_controller = sub_class.new
69
+ other_controller.stub(:action_name) { 'action' }
70
+ other_controller.perform_resource_loading
71
+ end
72
+ it { proxy.actors.has_key?(:actor).should be_true }
73
+ end
74
+ end
75
+
76
+ describe ".perform_access_authorization" do
77
+
78
+ context 'when registering and testing a valid actor' do
79
+ let(:result) do
80
+ controller_class.register_actor :good_user, as: :user
81
+ controller.perform_access_authorization(definition, [:profile])
82
+ end
83
+ it { result.should be_true }
84
+ end
85
+
86
+ context 'when registering and testing a valid actor and resource' do
87
+ let(:result) do
88
+ controller_class.register_actor :good_user, as: :user
89
+ controller_class.register_resource(:resource) { HashObj.new(id: 10) }
90
+ controller.perform_access_authorization(definition, [:profile2])
91
+ end
92
+ it { result.should be_true }
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,35 @@
1
+ # This file was generated by the `rspec --init` command. Conventionally, all
2
+ # specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
3
+ # Require this file using `require "spec_helper"` to ensure that it is only
4
+ # loaded once.
5
+ #
6
+ # See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
7
+ #
8
+
9
+ require 'active_support/all'
10
+ require 'canned'
11
+
12
+ class HashObj
13
+ def initialize(_hash)
14
+ _hash.each do |k,v| self.class.send(:define_method, k) { v } end
15
+ end
16
+ end
17
+
18
+ module Helpers
19
+ def dummy(_hash={})
20
+ HashObj.new _hash
21
+ end
22
+ end
23
+
24
+ RSpec.configure do |config|
25
+ config.treat_symbols_as_metadata_keys_with_true_values = true
26
+ config.run_all_when_everything_filtered = true
27
+ config.filter_run :focus
28
+
29
+ # Run specs in random order to surface order dependencies. If you find an
30
+ # order dependency and want to debug it, you can fix the order by providing
31
+ # the seed, which is printed after each run.
32
+ # --seed 1234
33
+ config.order = 'random'
34
+ config.include Helpers
35
+ end