canned 0.1.4

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.
@@ -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