authorizy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +5 -0
  3. data/LICENSE +21 -0
  4. data/README.md +190 -0
  5. data/lib/authorizy.rb +19 -0
  6. data/lib/authorizy/base_cop.rb +21 -0
  7. data/lib/authorizy/config.rb +15 -0
  8. data/lib/authorizy/core.rb +43 -0
  9. data/lib/authorizy/expander.rb +61 -0
  10. data/lib/authorizy/extension.rb +31 -0
  11. data/lib/authorizy/version.rb +5 -0
  12. data/lib/generators/authorizy/install_generator.rb +23 -0
  13. data/lib/generators/authorizy/templates/config/initializers/authorizy.rb +23 -0
  14. data/lib/generators/authorizy/templates/db/migrate/add_authorizy_on_users.rb +7 -0
  15. data/spec/authorizy/base_cop/access_question_spec.rb +9 -0
  16. data/spec/authorizy/config/aliases_spec.rb +13 -0
  17. data/spec/authorizy/config/cop_spec.rb +13 -0
  18. data/spec/authorizy/config/current_user_spec.rb +31 -0
  19. data/spec/authorizy/config/dependencies_spec.rb +13 -0
  20. data/spec/authorizy/config/initialize_spec.rb +7 -0
  21. data/spec/authorizy/config/redirect_url_spec.rb +31 -0
  22. data/spec/authorizy/cop/controller_spec.rb +42 -0
  23. data/spec/authorizy/cop/model_spec.rb +15 -0
  24. data/spec/authorizy/cop/namespaced_controller_spec.rb +42 -0
  25. data/spec/authorizy/core/access_spec.rb +137 -0
  26. data/spec/authorizy/expander/expand_spec.rb +144 -0
  27. data/spec/authorizy/extension/authorizy_question_spec.rb +46 -0
  28. data/spec/authorizy/extension/authorizy_spec.rb +56 -0
  29. data/spec/common_helper.rb +11 -0
  30. data/spec/spec_helper.rb +29 -0
  31. data/spec/support/application.rb +8 -0
  32. data/spec/support/common.rb +13 -0
  33. data/spec/support/controllers/admin/dummy_controller.rb +13 -0
  34. data/spec/support/controllers/dummy_controller.rb +11 -0
  35. data/spec/support/coverage.rb +14 -0
  36. data/spec/support/i18n.rb +3 -0
  37. data/spec/support/locales/en.yml +3 -0
  38. data/spec/support/models/authorizy_cop.rb +31 -0
  39. data/spec/support/models/empty_cop.rb +4 -0
  40. data/spec/support/models/user.rb +4 -0
  41. data/spec/support/routes.rb +6 -0
  42. data/spec/support/schema.rb +22 -0
  43. metadata +198 -0
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ Authorizy.configure do |config|
4
+ # Creates aliases to automatically allow permission for another action.
5
+ # https://github.com/wbotelhos/authorizy#aliases
6
+ # config.aliases = {}
7
+
8
+ # An interceptor to filter the request and decide if the request will be authorized
9
+ # https://github.com/wbotelhos/authorizy#cop
10
+ # config.cop = Authorizy::BaseCop
11
+
12
+ # The current user from we fetch the permissions
13
+ # https://github.com/wbotelhos/authorizy#current-user
14
+ # config.current_user = -> (context) { context.respond_to?(:current_user) ? context.current_user : nil }
15
+
16
+ # Inherited permissions from some other permission the user already has
17
+ # https://github.com/wbotelhos/authorizy#dependencies
18
+ # config.dependencies = {}
19
+
20
+ # URL to be redirect when user has no permission to access some resource
21
+ # https://github.com/wbotelhos/authorizy#dependencies
22
+ # config.redirect_url = -> (context) { context.respond_to?(:root_url) ? context.root_url : '/' }
23
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class AddAuthorizyOnUsers < ActiveRecord::Migration[6.0]
4
+ def change
5
+ add_column :users, :authorizy, :jsonb, default: {}, null: false
6
+ end
7
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::BaseCop, '#access?' do
4
+ subject(:cop) { described_class.new('current_user', 'params', 'session', 'controller', 'action') }
5
+
6
+ it 'returns false as default' do
7
+ expect(cop.access?).to be(false)
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#aliases' do
4
+ subject(:config) { described_class.new }
5
+
6
+ it 'has default value and can receive a new one' do
7
+ expect(subject.aliases).to eq({})
8
+
9
+ config.aliases = 'value'
10
+
11
+ expect(config.aliases).to eq('value')
12
+ end
13
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#cop' do
4
+ subject(:config) { described_class.new }
5
+
6
+ it 'has default value and can receive a new one' do
7
+ expect(subject.cop).to eq(Authorizy::BaseCop)
8
+
9
+ config.cop = 'value'
10
+
11
+ expect(config.cop).to eq('value')
12
+ end
13
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#current_user' do
4
+ subject(:config) { described_class.new }
5
+
6
+ context 'when uses default value' do
7
+ context 'when context responds to current_user' do
8
+ let!(:context) { OpenStruct.new(current_user: 'user') }
9
+
10
+ it 'is called' do
11
+ expect(subject.current_user.call(context)).to eq('user')
12
+ end
13
+ end
14
+
15
+ context 'when context does not respond to current_user' do
16
+ let!(:context) { 'context' }
17
+
18
+ it 'returns nil' do
19
+ expect(subject.current_user.call(context)).to be(nil)
20
+ end
21
+ end
22
+ end
23
+
24
+ context 'when uses custom value' do
25
+ it 'executes what you want' do
26
+ config.current_user = -> (context) { context[:value] }
27
+
28
+ expect(config.current_user.call({ value: 'value' })).to eq('value')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#dependencies' do
4
+ subject(:config) { described_class.new }
5
+
6
+ it 'has default value and can receive a new one' do
7
+ expect(subject.dependencies).to eq({})
8
+
9
+ config.dependencies = 'value'
10
+
11
+ expect(config.dependencies).to eq('value')
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config do
4
+ it 'starts with a default cop' do
5
+ expect(subject.cop).to eq(Authorizy::BaseCop)
6
+ end
7
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Config, '#redirect_url' do
4
+ subject(:config) { described_class.new }
5
+
6
+ context 'when uses default value' do
7
+ context 'when context responds to root_url' do
8
+ let!(:context) { OpenStruct.new(root_url: '/root') }
9
+
10
+ it 'is called' do
11
+ expect(subject.redirect_url.call(context)).to eq('/root')
12
+ end
13
+ end
14
+
15
+ context 'when context does not respond to root_url' do
16
+ let!(:context) { 'context' }
17
+
18
+ it 'returns just a slash' do
19
+ expect(subject.redirect_url.call(context)).to eq('/')
20
+ end
21
+ end
22
+ end
23
+
24
+ context 'when uses custom value' do
25
+ it 'executes what you want' do
26
+ config.redirect_url = -> (context) { context[:value] }
27
+
28
+ expect(config.redirect_url.call({ value: 'value' })).to eq('value')
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/models/authorizy_cop'
4
+ require 'support/models/empty_cop'
5
+ require 'support/controllers/dummy_controller'
6
+
7
+ RSpec.describe DummyController, '#authorizy', type: :controller do
8
+ let!(:parameters) { ActionController::Parameters.new(key: 'value', controller: 'dummy', action: 'action') }
9
+ let!(:user) { User.new }
10
+
11
+ context 'when cop responds to the controller name' do
12
+ context 'when method resturns false' do
13
+ it 'denies the access' do
14
+ config_mock(cop: AuthorizyCop, current_user: user) do
15
+ get :action, params: { access: false }
16
+ end
17
+
18
+ expect(response).to redirect_to('/')
19
+ end
20
+ end
21
+
22
+ context 'when method resturns true' do
23
+ it 'denies the access' do
24
+ config_mock(cop: AuthorizyCop, current_user: user) do
25
+ get :action, params: { access: true }
26
+ end
27
+
28
+ expect(response.body).to eq('{"message":"authorized"}')
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when cop responds to the controller name' do
34
+ it 'denies the access' do
35
+ config_mock(cop: EmptyCop, current_user: user) do
36
+ get :action
37
+ end
38
+
39
+ expect(response).to redirect_to('/')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/models/authorizy_cop'
4
+
5
+ RSpec.describe AuthorizyCop do
6
+ subject(:cop) { described_class.new('current_user', 'params', 'session', 'controller', 'action') }
7
+
8
+ it 'adds private attributes readers' do
9
+ expect(cop.get_action).to eq('action')
10
+ expect(cop.get_controller).to eq('controller')
11
+ expect(cop.get_current_user).to eq('current_user')
12
+ expect(cop.get_params).to eq('params')
13
+ expect(cop.get_session).to eq('session')
14
+ end
15
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'support/models/authorizy_cop'
4
+ require 'support/models/empty_cop'
5
+ require 'support/controllers/admin/dummy_controller'
6
+
7
+ RSpec.describe Admin::DummyController, '#authorizy', type: :controller do
8
+ let!(:parameters) { ActionController::Parameters.new(key: 'value', controller: 'admin/users', action: 'action') }
9
+ let!(:user) { User.new }
10
+
11
+ context 'when cop responds to the controller name' do
12
+ context 'when method resturns false' do
13
+ it 'denies the access' do
14
+ config_mock(cop: AuthorizyCop, current_user: user) do
15
+ get :action, params: { admin: false }
16
+ end
17
+
18
+ expect(response).to redirect_to('/')
19
+ end
20
+ end
21
+
22
+ context 'when method resturns true' do
23
+ it 'denies the access' do
24
+ config_mock(cop: AuthorizyCop, current_user: user) do
25
+ get :action, params: { admin: true }
26
+ end
27
+
28
+ expect(response.body).to eq('{"message":"authorized"}')
29
+ end
30
+ end
31
+ end
32
+
33
+ context 'when cop responds to the controller name' do
34
+ it 'denies the access' do
35
+ config_mock(cop: EmptyCop, current_user: user) do
36
+ get :action
37
+ end
38
+
39
+ expect(response).to redirect_to('/')
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,137 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Core, '#access?' do
4
+ context 'when permissions is in session as string' do
5
+ let!(:current_user) { User.new }
6
+ let!(:params) { { 'action' => 'create', 'controller' => 'controller' } }
7
+ let!(:session) { { 'permissions' => [{ 'action' => 'create', 'controller' => 'controller' }] } }
8
+
9
+ it 'uses the session value skipping the user fetch' do
10
+ expect(described_class.new(current_user, params, session).access?).to be(true)
11
+ end
12
+ end
13
+
14
+ context 'when permissions is not in session' do
15
+ subject(:authorizy) { described_class.new(current_user, params, session) }
16
+
17
+ let!(:current_user) { User.new(authorizy: { permissions: [{ action: 'create', controller: 'match' }] }) }
18
+ let!(:params) { { 'action' => 'create', 'controller' => 'match' } }
19
+ let!(:session) { {} }
20
+
21
+ it 'fetches the permission from user' do
22
+ expect(authorizy.access?).to be(true)
23
+ end
24
+ end
25
+
26
+ context 'when session has no permission nor the user' do
27
+ subject(:authorizy) { described_class.new(current_user, params, session) }
28
+
29
+ let!(:current_user) { User.new }
30
+ let!(:params) { { 'action' => 'create', 'controller' => 'match' } }
31
+ let!(:session) { {} }
32
+
33
+ it { expect(authorizy.access?).to be(false) }
34
+ end
35
+
36
+ context 'when cop does not respond to controller' do
37
+ subject(:authorizy) { described_class.new(current_user, params, session) }
38
+
39
+ let!(:cop) { instance_double('Authorizy.config.cop') }
40
+ let!(:current_user) { User.new }
41
+ let!(:params) { { 'action' => 'create', 'controller' => 'missing' } }
42
+ let!(:session) { {} }
43
+
44
+ before do
45
+ allow(Authorizy.config.cop).to receive(:new)
46
+ .with(current_user, params, session, 'missing', 'create')
47
+ .and_return(cop)
48
+
49
+ allow(cop).to receive(:respond_to?).with('missing').and_return(false)
50
+ end
51
+
52
+ it 'does not authorize via cop' do
53
+ expect(authorizy.access?).to be(false)
54
+ end
55
+ end
56
+
57
+ context 'when cop responds to controller' do
58
+ subject(:authorizy) { described_class.new(current_user, params, session) }
59
+
60
+ let!(:cop) { instance_double('Authorizy.config.cop') }
61
+ let!(:current_user) { User.new }
62
+ let!(:params) { { 'action' => 'create', 'controller' => 'match' } }
63
+ let!(:session) { {} }
64
+
65
+ before do
66
+ allow(Authorizy.config.cop).to receive(:new)
67
+ .with(current_user, params, session, 'match', 'create')
68
+ .and_return(cop)
69
+
70
+ allow(cop).to receive(:respond_to?).with('match').and_return(true)
71
+ end
72
+
73
+ context 'when cop does not release the access' do
74
+ it 'continues trying via session and so user permissions' do
75
+ allow(cop).to receive(:public_send).with('match').and_return(false)
76
+
77
+ expect(authorizy.access?).to be(false)
78
+ end
79
+ end
80
+
81
+ context 'when cop releases the access' do
82
+ it 'skips session and user permission return true to the access' do
83
+ allow(cop).to receive(:public_send).with('match').and_return(true)
84
+
85
+ expect(authorizy.access?).to be(true)
86
+ end
87
+ end
88
+ end
89
+
90
+ context 'when controller is given' do
91
+ subject(:authorizy) { described_class.new(current_user, params, session, controller: 'controller') }
92
+
93
+ let!(:current_user) { User.new }
94
+ let!(:params) { { 'action' => 'action', 'controller' => 'ignored' } }
95
+ let!(:session) { { 'permissions' => [{ 'action' => 'action', 'controller' => 'controller' }] } }
96
+
97
+ it 'uses the given controller over the one on params' do
98
+ expect(authorizy.access?).to be(true)
99
+ end
100
+ end
101
+
102
+ context 'when action is given' do
103
+ subject(:authorizy) { described_class.new(current_user, params, session, action: 'action') }
104
+
105
+ let!(:current_user) { User.new }
106
+ let!(:params) { { 'action' => 'ignored', 'controller' => 'controller' } }
107
+ let!(:session) { { 'permissions' => [{ 'action' => 'action', 'controller' => 'controller' }] } }
108
+
109
+ it 'uses the given action over the one on params' do
110
+ expect(authorizy.access?).to be(true)
111
+ end
112
+ end
113
+
114
+ context 'when user has the controller permission but not action' do
115
+ subject(:authorizy) { described_class.new(current_user, params, session) }
116
+
117
+ let!(:current_user) { User.new }
118
+ let!(:params) { { 'action' => 'action', 'controller' => 'controller' } }
119
+ let!(:session) { { 'permissions' => [{ 'action' => 'miss', 'controller' => 'controller' }] } }
120
+
121
+ it 'cannot access' do
122
+ expect(authorizy.access?).to be(false)
123
+ end
124
+ end
125
+
126
+ context 'when user has the action permission but not controller' do
127
+ subject(:authorizy) { described_class.new(current_user, params, session) }
128
+
129
+ let!(:current_user) { User.new }
130
+ let!(:params) { { 'action' => 'action', 'controller' => 'controller' } }
131
+ let!(:session) { { 'permissions' => [{ 'action' => 'create', 'controller' => 'miss' }] } }
132
+
133
+ it 'cannot access' do
134
+ expect(authorizy.access?).to be(false)
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe Authorizy::Expander, '#expand' do
4
+ subject(:expander) { described_class.new }
5
+
6
+ context 'when permissions is blank' do
7
+ let(:permissions) { [] }
8
+
9
+ it 'returns an empty permissions' do
10
+ expect(expander.expand(permissions)).to eq []
11
+ end
12
+ end
13
+
14
+ context 'when permissions is given' do
15
+ context 'when data is symbol' do
16
+ let(:permissions) do
17
+ [
18
+ { action: :create, controller: :controller },
19
+ { action: :edit, controller: :controller },
20
+ { action: :new, controller: :controller },
21
+ { action: :update, controller: :controller },
22
+ ]
23
+ end
24
+
25
+ it 'mappes the default actions aliases' do
26
+ expect(expander.expand(permissions)).to match_array [
27
+ { 'action' => 'create', 'controller' => 'controller' },
28
+ { 'action' => 'edit', 'controller' => 'controller' },
29
+ { 'action' => 'new', 'controller' => 'controller' },
30
+ { 'action' => 'update', 'controller' => 'controller' },
31
+ ]
32
+ end
33
+ end
34
+
35
+ context 'when data is string' do
36
+ let(:permissions) do
37
+ [
38
+ { 'action' => 'create', 'controller' => 'controller' },
39
+ { 'action' => 'edit', 'controller' => 'controller' },
40
+ { 'action' => 'new', 'controller' => 'controller' },
41
+ { 'action' => 'update', 'controller' => 'controller' },
42
+ ]
43
+ end
44
+
45
+ it 'mappes the default actions aliases' do
46
+ expect(expander.expand(permissions)).to match_array [
47
+ { 'action' => 'create', 'controller' => 'controller' },
48
+ { 'action' => 'edit', 'controller' => 'controller' },
49
+ { 'action' => 'new', 'controller' => 'controller' },
50
+ { 'action' => 'update', 'controller' => 'controller' },
51
+ ]
52
+ end
53
+ end
54
+ end
55
+
56
+ context 'when a dependencies is given' do
57
+ context 'when keys and values are strings' do
58
+ let(:dependencies) { { 'controller' => { 'action' => [{ 'action' => 'action2', 'controller' => 'controller2' }] } } }
59
+ let!(:permissions) { [{ 'action' => 'action', 'controller' => 'controller' }] }
60
+
61
+ it 'addes the dependencies permissions' do
62
+ config_mock(dependencies: dependencies) do
63
+ expect(expander.expand(permissions)).to match_array [
64
+ { 'action' => 'action', 'controller' => 'controller' },
65
+ { 'action' => 'action2', 'controller' => 'controller2' },
66
+ ]
67
+ end
68
+ end
69
+ end
70
+
71
+ context 'when keys and values are symbol' do
72
+ let(:dependencies) { { controller: { action: [{ action: :action2, controller: :controller2 }] } } }
73
+ let!(:permissions) { [{ 'action' => 'action', 'controller' => 'controller' }] }
74
+
75
+ it 'addes the dependencies permissions' do
76
+ config_mock(dependencies: dependencies) do
77
+ expect(expander.expand(permissions)).to match_array [
78
+ { 'action' => 'action', 'controller' => 'controller' },
79
+ { 'action' => 'action2', 'controller' => 'controller2' },
80
+ ]
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+
87
+ context 'when aliases is given' do
88
+ let!(:permissions) { [{ 'action' => 'action', 'controller' => 'controller' }] }
89
+
90
+ context 'when key and values are strings' do
91
+ let(:aliases) { { 'action' => 'action2' } }
92
+
93
+ it 'mappes the action with the current controller' do
94
+ config_mock(aliases: aliases) do
95
+ expect(expander.expand(permissions)).to match_array [
96
+ { 'action' => 'action', 'controller' => 'controller' },
97
+ { 'action' => 'action2', 'controller' => 'controller' },
98
+ ]
99
+ end
100
+ end
101
+ end
102
+
103
+ context 'when key and values are symbols' do
104
+ let(:aliases) { { action: :action2 } }
105
+
106
+ it 'mappes the action with the current controller' do
107
+ config_mock(aliases: aliases) do
108
+ expect(expander.expand(permissions)).to match_array [
109
+ { 'action' => 'action', 'controller' => 'controller' },
110
+ { 'action' => 'action2', 'controller' => 'controller' },
111
+ ]
112
+ end
113
+ end
114
+ end
115
+
116
+ context 'when key and values are array of strings' do
117
+ let(:aliases) { { action: %w[action2 action3] } }
118
+
119
+ it 'mappes the actions with the current controller' do
120
+ config_mock(aliases: aliases) do
121
+ expect(expander.expand(permissions)).to match_array [
122
+ { 'action' => 'action', 'controller' => 'controller' },
123
+ { 'action' => 'action2', 'controller' => 'controller' },
124
+ { 'action' => 'action3', 'controller' => 'controller' },
125
+ ]
126
+ end
127
+ end
128
+ end
129
+
130
+ context 'when key and values are array of symbols' do
131
+ let(:aliases) { { action: %i[action2 action3] } }
132
+
133
+ it 'mappes the actions with the current controller' do
134
+ config_mock(aliases: aliases) do
135
+ expect(expander.expand(permissions)).to match_array [
136
+ { 'action' => 'action', 'controller' => 'controller' },
137
+ { 'action' => 'action2', 'controller' => 'controller' },
138
+ { 'action' => 'action3', 'controller' => 'controller' },
139
+ ]
140
+ end
141
+ end
142
+ end
143
+ end
144
+ end