fortress 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.
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'fortress/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'fortress'
8
+ spec.version = Fortress::VERSION
9
+ spec.authors = ['Guillaume Hain']
10
+ spec.email = ['zedtux@zedroot.org']
11
+ spec.summary = 'Secure your Rails application from preventing access ' \
12
+ 'to everything to opening allowed actions.'
13
+ spec.description = 'The rigths management libraries available today are ' \
14
+ 'all based on the principle: everything is open and ' \
15
+ 'you close it explicitely. Fortress is immediately ' \
16
+ 'closing access to every actions of every controllers' \
17
+ ' when you install it. It\'s then up to you to open ' \
18
+ 'the allowed actions.'
19
+ spec.homepage = 'https://github.com/YourCursus/fortress'
20
+ spec.license = 'MIT'
21
+
22
+ spec.files = `git ls-files -z`.split("\x0")
23
+ spec.executables = spec.files.grep(/^bin/) { |f| File.basename(f) }
24
+ spec.test_files = spec.files.grep(/^(test|spec|features)/)
25
+ spec.require_paths = ['lib']
26
+
27
+ spec.add_dependency 'actionpack', '> 3.1'
28
+ spec.add_dependency 'activesupport', '> 3.1'
29
+
30
+ spec.add_development_dependency 'bundler'
31
+ spec.add_development_dependency 'rake'
32
+ spec.add_development_dependency 'rspec-rails'
33
+ spec.add_development_dependency 'rspec'
34
+ spec.add_development_dependency 'rubocop'
35
+ end
@@ -0,0 +1,6 @@
1
+ require 'fortress/controller'
2
+ require 'fortress/controller_interface'
3
+ require 'fortress/mechanism'
4
+ require 'fortress/version'
5
+
6
+ ActionController::Base.send(:include, Fortress::Controller)
@@ -0,0 +1,48 @@
1
+ require 'active_support/concern'
2
+
3
+ module Fortress
4
+ #
5
+ # The Controller module embbed all the code to "hook" Fortress to your Rails
6
+ # application.
7
+ #
8
+ # @author zedtux
9
+ #
10
+ module Controller
11
+ extend ActiveSupport::Concern
12
+
13
+ included do
14
+ Mechanism.initialize_authorisations
15
+
16
+ # Add a new before_filter for all controllers
17
+ append_before_filter :prevent_access!
18
+ end
19
+
20
+ def prevent_access!
21
+ controller = Fortress::ControllerInterface.new(self)
22
+ Mechanism.authorised?(controller, action_name) ? true : access_deny
23
+ end
24
+
25
+ #
26
+ # Default access_deny method used when not re-defined in the Rails
27
+ # application.
28
+ #
29
+ # You can re-define it within the ApplicationController of you rails
30
+ # application.
31
+ def access_deny
32
+ flash[:error] = 'You are not authorised to access this page.'
33
+ redirect_to Rails.application.routes.url_helpers.root_url
34
+ end
35
+
36
+ #
37
+ # Class methods added to all controllers in a Rails application.
38
+ #
39
+ # @author zedtux
40
+ #
41
+ module ClassMethods
42
+ def fortress_allow(actions, options = {})
43
+ Mechanism.authorise!(name, actions)
44
+ Mechanism.parse_options(self, actions, options) if options.present?
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,58 @@
1
+ module Fortress
2
+ #
3
+ # Object to easily use the Mechanism stored rules. It's a kind of helper
4
+ # class
5
+ #
6
+ # @author zedtux
7
+ #
8
+ class ControllerInterface
9
+ attr_accessor :instance, :params
10
+
11
+ def initialize(controller_instance)
12
+ self.instance = controller_instance
13
+ end
14
+
15
+ def params
16
+ @params ||= Mechanism.authorisations[instance.class.name]
17
+ end
18
+
19
+ def blocked?
20
+ params.nil?
21
+ end
22
+
23
+ def allow_all?
24
+ params[:all] == true
25
+ end
26
+
27
+ def allow_all_without_except?
28
+ allow_all? && params.key?(:except) == false
29
+ end
30
+
31
+ def allow_action?(name)
32
+ return false if Array(params[:except]).include?(name.to_sym)
33
+
34
+ if params.key?(:if) && params[:if].key?(:actions)
35
+ if params[:if][:actions].include?(name.to_sym)
36
+ return params[:if][:method] == true
37
+ end
38
+ end
39
+
40
+ return true if Array(params[:only]).include?(name.to_sym)
41
+
42
+ allow_all?
43
+ end
44
+
45
+ def allow_method?
46
+ params.key?(:if) && params[:if].key?(:method)
47
+ end
48
+
49
+ def needs_to_check_action?(name)
50
+ params.key?(:if) && params[:if].key?(:actions) &&
51
+ Array(params[:if][:actions]).include?(name)
52
+ end
53
+
54
+ def call_allow_method
55
+ instance.send(params[:if][:method])
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,121 @@
1
+ require 'active_support/core_ext/module/attribute_accessors'
2
+
3
+ module Fortress
4
+ #
5
+ # Mechanism embbed all the logic of the Fortress library.
6
+ #
7
+ # @author zedtux
8
+ #
9
+ module Mechanism
10
+ # Authorisations stores the authorisations per controllers.
11
+ #
12
+ # This is a big picture of the structured data:
13
+ # {
14
+ # "UsersController": {
15
+ # "all": true
16
+ # },
17
+ # "LogsController": {
18
+ # "all": true,
19
+ # "except": [
20
+ # "destroy"
21
+ # ]
22
+ # },
23
+ # "NotificationsController": {
24
+ # "only": [
25
+ # "new",
26
+ # "create",
27
+ # "edit",
28
+ # "update"
29
+ # ]
30
+ # }
31
+ # "PostsController": {
32
+ # "only": [
33
+ # "edit",
34
+ # "update"
35
+ # ]
36
+ # "if": {
37
+ # "method": :can_create_post?,
38
+ # "actions": [
39
+ # "new",
40
+ # "create"
41
+ # ]
42
+ # }
43
+ # },
44
+ # "CommentsController": {
45
+ # "if": {
46
+ # "method": :is_admin?,
47
+ # "actions": :destroy
48
+ # }
49
+ # }
50
+ # }
51
+ mattr_accessor :authorisations
52
+
53
+ def self.initialize_authorisations
54
+ self.authorisations = {}
55
+ end
56
+
57
+ def self.parse_options(controller, actions, options)
58
+ options.each do |key, value|
59
+ case key
60
+ when :if
61
+ Mechanism.authorise_if_truthy(controller.name, value, actions)
62
+ when :except
63
+ Mechanism.authorise_excepted(controller.name, value)
64
+ end
65
+ end
66
+ end
67
+
68
+ def self.append_or_update(controller_name, key, value)
69
+ authorisations[controller_name] ||= {}
70
+ if authorisations[controller_name].key?(key)
71
+ if authorisations[controller_name][key].is_a?(Hash)
72
+ authorisations[controller_name][key].merge!(value)
73
+ else
74
+ authorisations[controller_name][key] = value
75
+ end
76
+ else
77
+ authorisations[controller_name].merge!(key => value)
78
+ end
79
+ end
80
+
81
+ def self.authorise!(class_name, actions)
82
+ if actions == :all
83
+ append_or_update(class_name, :all, true)
84
+ return
85
+ end
86
+ append_or_update(class_name, :only, Array(actions))
87
+ end
88
+
89
+ private
90
+
91
+ def self.authorise_if_truthy(class_name, method_sym, actions)
92
+ append_or_update(class_name, :if, method: method_sym,
93
+ actions: Array(actions))
94
+ end
95
+
96
+ def self.authorise_excepted(class_name, action)
97
+ append_or_update(class_name, :except, Array(action))
98
+ end
99
+
100
+ def self.authorised?(controller, action_name)
101
+ return false if controller.blocked?
102
+
103
+ # When the complete controller is authorised
104
+ return true if controller.allow_all_without_except?
105
+
106
+ # When the controller allows some actions and the current action is
107
+ # allowed
108
+ return true if controller.allow_action?(action_name)
109
+
110
+ # When the controller implement the authorisation method
111
+ if controller.allow_method?
112
+ if controller.needs_to_check_action?(action_name)
113
+ allowed = controller.call_allow_method
114
+ return true if allowed
115
+ end
116
+ end
117
+
118
+ false
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,8 @@
1
+ #
2
+ # Version manager module
3
+ #
4
+ # @author zedtux
5
+ #
6
+ module Fortress
7
+ VERSION = '0.1.0'
8
+ end
@@ -0,0 +1,33 @@
1
+ require 'active_support/all'
2
+ require 'action_controller'
3
+ require 'action_dispatch'
4
+
5
+ #
6
+ # Re-define the Rails module
7
+ #
8
+ module Rails
9
+ #
10
+ # Fake Rails application
11
+ #
12
+ class App
13
+ def env_config
14
+ {}
15
+ end
16
+
17
+ def routes
18
+ return @routes if defined?(@routes)
19
+ @routes = ActionDispatch::Routing::RouteSet.new
20
+ @routes.draw do
21
+ root 'home#index'
22
+ resources :guitars
23
+ end
24
+ @routes
25
+ end
26
+ end
27
+
28
+ def self.application
29
+ @app ||= App.new
30
+ end
31
+ end
32
+
33
+ Rails.application.routes.default_url_options[:host] = 'http://test.host'
@@ -0,0 +1,29 @@
1
+ require 'fortress'
2
+
3
+ #
4
+ # Mother class for all controller used to test Fortress
5
+ #
6
+ class TestController < ActionController::Base
7
+ include Rails.application.routes.url_helpers
8
+ def render(*_)
9
+ end
10
+ end
11
+
12
+ #
13
+ # This controller is the one used in all the tests
14
+ #
15
+ class GuitarsController < TestController
16
+ def index; end
17
+
18
+ def show; end
19
+
20
+ def new; end
21
+
22
+ def create; end
23
+
24
+ def edit; end
25
+
26
+ def update; end
27
+
28
+ def destroy; end
29
+ end
@@ -0,0 +1,250 @@
1
+ require 'spec_helper'
2
+ require 'fortress/controller_interface'
3
+
4
+ describe Fortress::ControllerInterface do
5
+ before do
6
+ Fortress::Mechanism.initialize_authorisations
7
+ @controller = GuitarsController.new
8
+ end
9
+
10
+ describe '.blocked?' do
11
+ context 'when controller is unknown from the Fortress::Mechanism module' do
12
+ subject { Fortress::ControllerInterface.new(@controller) }
13
+ it 'should return true' do
14
+ expect(subject.blocked?).to be_truthy
15
+ end
16
+ end
17
+ context 'when controller is known from the Fortress::Mechanism module' do
18
+ before do
19
+ Fortress::Mechanism.authorisations['GuitarsController'] = { all: true }
20
+ end
21
+ subject { Fortress::ControllerInterface.new(@controller) }
22
+ it 'should return false' do
23
+ expect(subject.blocked?).to be_falsy
24
+ end
25
+ end
26
+ end
27
+
28
+ describe '.allow_all?' do
29
+ context 'when controller has the `:all` key to true in the ' \
30
+ 'Fortress::Mechanism module' do
31
+ before do
32
+ Fortress::Mechanism.authorisations['GuitarsController'] = { all: true }
33
+ end
34
+ subject { Fortress::ControllerInterface.new(@controller) }
35
+ it 'should return true' do
36
+ expect(subject.allow_all?).to be_truthy
37
+ end
38
+ end
39
+ context 'when controller doesn\'t have the `:all` key to true in the ' \
40
+ 'Fortress::Mechanism module' do
41
+ before do
42
+ Fortress::Mechanism.authorisations['GuitarsController'] = {}
43
+ end
44
+ before { Fortress::Mechanism.authorise!('GuitarsController', :index) }
45
+ subject { Fortress::ControllerInterface.new(@controller) }
46
+ it 'should return false' do
47
+ expect(subject.allow_all?).to be_falsy
48
+ end
49
+ end
50
+ end
51
+
52
+ describe '.allow_all_without_except?' do
53
+ context 'when controller has the `:all` key to true in the ' \
54
+ 'Fortress::Mechanism module and doesn\'t have the `:except` key' do
55
+ before do
56
+ Fortress::Mechanism.authorisations['GuitarsController'] = { all: true }
57
+ end
58
+ subject { Fortress::ControllerInterface.new(@controller) }
59
+ it 'should return true' do
60
+ expect(subject.allow_all_without_except?).to be_truthy
61
+ end
62
+ end
63
+ context 'when controller has the `:all` key to true in the ' \
64
+ 'Fortress::Mechanism module and do have the `:except` key' do
65
+ before do
66
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
67
+ all: true, except: [:index]
68
+ }
69
+ end
70
+ subject { Fortress::ControllerInterface.new(@controller) }
71
+ it 'should return false' do
72
+ expect(subject.allow_all_without_except?).to be_falsy
73
+ end
74
+ end
75
+ end
76
+
77
+ describe '.allow_action?' do
78
+ describe 'passing `:index` as parameter' do
79
+ context 'when the controller has the `:except` key containing `:index` ' \
80
+ 'in the Fortress::Mechanism module' do
81
+ before do
82
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
83
+ except: :index
84
+ }
85
+ end
86
+ subject { Fortress::ControllerInterface.new(@controller) }
87
+ it 'should return false' do
88
+ expect(subject.allow_action?(:index)).to be_falsy
89
+ end
90
+ end
91
+ context 'when the controller has the `:except` key which doesn\'t ' \
92
+ 'contain the `:index` action in the Fortress::Mechanism module' do
93
+ context 'and the controller has the `:if` key with `:action` ' \
94
+ 'including `:index`' do
95
+ context 'the `:method` return false' do
96
+ before do
97
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
98
+ if: { actions: [:index], method: false }
99
+ }
100
+ end
101
+ subject { Fortress::ControllerInterface.new(@controller) }
102
+ it 'should return false' do
103
+ expect(subject.allow_action?(:index)).to be_falsy
104
+ end
105
+ end
106
+ context 'the `:method` return true' do
107
+ before do
108
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
109
+ if: { actions: [:index], method: true }
110
+ }
111
+ end
112
+ subject { Fortress::ControllerInterface.new(@controller) }
113
+ it 'should return true' do
114
+ expect(subject.allow_action?(:index)).to be_truthy
115
+ end
116
+ end
117
+ end
118
+ context 'and the controller has the `:if` key with `:action` ' \
119
+ 'which doesn\'t includ the `:index` action' do
120
+ context 'and the controller has the `:only` key' do
121
+ context 'which do include `:index`' do
122
+ before do
123
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
124
+ only: [:index]
125
+ }
126
+ end
127
+ subject { Fortress::ControllerInterface.new(@controller) }
128
+ it 'should return true' do
129
+ expect(subject.allow_action?(:index)).to be_truthy
130
+ end
131
+ end
132
+ context 'which do not include `:index`' do
133
+ before do
134
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
135
+ only: [:update]
136
+ }
137
+ end
138
+ subject { Fortress::ControllerInterface.new(@controller) }
139
+ it 'should return false' do
140
+ expect(subject.allow_action?(:index)).to be_falsy
141
+ end
142
+ end
143
+ end
144
+ context 'and the controller hasn\'t the `:only` key' do
145
+ context 'and the controller has the `:all` key' do
146
+ before do
147
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
148
+ only: [], except: [], all: true
149
+ }
150
+ end
151
+ subject { Fortress::ControllerInterface.new(@controller) }
152
+ it 'should return true' do
153
+ expect(subject.allow_action?(:index)).to be_truthy
154
+ end
155
+ end
156
+ context 'and the controller do not have the `:all` key' do
157
+ before do
158
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
159
+ only: [], except: []
160
+ }
161
+ end
162
+ subject { Fortress::ControllerInterface.new(@controller) }
163
+ it 'should return false' do
164
+ expect(subject.allow_action?(:index)).to be_falsy
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
171
+ end
172
+
173
+ describe '.allow_method?' do
174
+ context 'when controller do have a `:if` key but no `:method` key in ' \
175
+ 'the Fortress::Mechanism' do
176
+ before do
177
+ Fortress::Mechanism.authorisations['GuitarsController'] = { if: {} }
178
+ end
179
+ subject { Fortress::ControllerInterface.new(@controller) }
180
+ it 'should return false' do
181
+ expect(subject.allow_method?).to be_falsy
182
+ end
183
+ end
184
+ context 'when controller do have a `:if` key and a `:method` key in ' \
185
+ 'the Fortress::Mechanism' do
186
+ before do
187
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
188
+ if: { method: :test }
189
+ }
190
+ end
191
+ subject { Fortress::ControllerInterface.new(@controller) }
192
+ it 'should return true' do
193
+ expect(subject.allow_method?).to be_truthy
194
+ end
195
+ end
196
+ end
197
+
198
+ describe '.needs_to_check_action?' do
199
+ describe 'passing `:index` as parameter' do
200
+ context 'when controller do not have the `:if` key in ' \
201
+ 'the Fortress::Mechanism' do
202
+ before do
203
+ Fortress::Mechanism.authorisations['GuitarsController'] = {}
204
+ end
205
+ subject { Fortress::ControllerInterface.new(@controller) }
206
+ it 'should return false' do
207
+ expect(subject.needs_to_check_action?(:index)).to be_falsy
208
+ end
209
+ end
210
+ context 'when controller do have the `:if` key in ' \
211
+ 'the Fortress::Mechanism without the `actions` key' do
212
+ before do
213
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
214
+ if: { method: :is_admin? }
215
+ }
216
+ end
217
+ subject { Fortress::ControllerInterface.new(@controller) }
218
+ it 'should return false' do
219
+ expect(subject.needs_to_check_action?(:index)).to be_falsy
220
+ end
221
+ end
222
+ context 'when controller do have the `:if` key in ' \
223
+ 'the Fortress::Mechanism and the `actions` key ' \
224
+ 'containing :update' do
225
+ before do
226
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
227
+ if: { actions: :update }
228
+ }
229
+ end
230
+ subject { Fortress::ControllerInterface.new(@controller) }
231
+ it 'should return false' do
232
+ expect(subject.needs_to_check_action?(:index)).to be_falsy
233
+ end
234
+ end
235
+ context 'when controller do have the `:if` key in ' \
236
+ 'the Fortress::Mechanism and the `actions` key ' \
237
+ 'containing :index' do
238
+ before do
239
+ Fortress::Mechanism.authorisations['GuitarsController'] = {
240
+ if: { actions: :index }
241
+ }
242
+ end
243
+ subject { Fortress::ControllerInterface.new(@controller) }
244
+ it 'should return true' do
245
+ expect(subject.needs_to_check_action?(:index)).to be_truthy
246
+ end
247
+ end
248
+ end
249
+ end
250
+ end