strongbolt 0.3.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (145) hide show
  1. checksums.yaml +7 -0
  2. data/.editorconfig +33 -0
  3. data/.gitignore +18 -0
  4. data/.rspec +1 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +130 -0
  9. data/LICENSE.txt +22 -0
  10. data/README.md +182 -0
  11. data/Rakefile +1 -0
  12. data/app/assets/javascripts/strongbolt.js +1 -0
  13. data/app/assets/javascripts/strongbolt/role-capabilities.js +80 -0
  14. data/app/controllers/strongbolt/capabilities_controller.rb +77 -0
  15. data/app/controllers/strongbolt/roles_controller.rb +92 -0
  16. data/app/controllers/strongbolt/security_controller.rb +8 -0
  17. data/app/controllers/strongbolt/user_groups_controller.rb +76 -0
  18. data/app/controllers/strongbolt/user_groups_users_controller.rb +35 -0
  19. data/app/controllers/strongbolt_controller.rb +2 -0
  20. data/app/views/strongbolt/_menu.html.erb +13 -0
  21. data/app/views/strongbolt/capabilities/index.html.erb +53 -0
  22. data/app/views/strongbolt/capabilities/show.html.erb +53 -0
  23. data/app/views/strongbolt/roles/_capabilities.html.erb +47 -0
  24. data/app/views/strongbolt/roles/_capability.html.erb +21 -0
  25. data/app/views/strongbolt/roles/_form.html.erb +12 -0
  26. data/app/views/strongbolt/roles/edit.html.erb +14 -0
  27. data/app/views/strongbolt/roles/index.html.erb +54 -0
  28. data/app/views/strongbolt/roles/new.html.erb +11 -0
  29. data/app/views/strongbolt/roles/show.html.erb +52 -0
  30. data/app/views/strongbolt/user_groups/_form.html.erb +12 -0
  31. data/app/views/strongbolt/user_groups/edit.html.erb +14 -0
  32. data/app/views/strongbolt/user_groups/index.html.erb +46 -0
  33. data/app/views/strongbolt/user_groups/new.html.erb +13 -0
  34. data/app/views/strongbolt/user_groups/show.html.erb +88 -0
  35. data/lib/generators/strongbolt/fix_generator.rb +23 -0
  36. data/lib/generators/strongbolt/indexes_generator.rb +19 -0
  37. data/lib/generators/strongbolt/install_generator.rb +29 -0
  38. data/lib/generators/strongbolt/templates/fix.rb +5 -0
  39. data/lib/generators/strongbolt/templates/indexes.rb +21 -0
  40. data/lib/generators/strongbolt/templates/migration.rb +73 -0
  41. data/lib/generators/strongbolt/templates/strongbolt.rb +45 -0
  42. data/lib/generators/strongbolt/views_generator.rb +26 -0
  43. data/lib/strongbolt.rb +219 -0
  44. data/lib/strongbolt/base.rb +7 -0
  45. data/lib/strongbolt/bolted.rb +125 -0
  46. data/lib/strongbolt/bolted_controller.rb +297 -0
  47. data/lib/strongbolt/capabilities_role.rb +15 -0
  48. data/lib/strongbolt/capability.rb +165 -0
  49. data/lib/strongbolt/configuration.rb +111 -0
  50. data/lib/strongbolt/controllers/url_helpers.rb +37 -0
  51. data/lib/strongbolt/engine.rb +44 -0
  52. data/lib/strongbolt/errors.rb +38 -0
  53. data/lib/strongbolt/generators/migration.rb +35 -0
  54. data/lib/strongbolt/helpers.rb +18 -0
  55. data/lib/strongbolt/rails/routes.rb +20 -0
  56. data/lib/strongbolt/role.rb +46 -0
  57. data/lib/strongbolt/roles_user_group.rb +15 -0
  58. data/lib/strongbolt/rspec.rb +29 -0
  59. data/lib/strongbolt/rspec/user.rb +90 -0
  60. data/lib/strongbolt/tenantable.rb +304 -0
  61. data/lib/strongbolt/user_abilities.rb +292 -0
  62. data/lib/strongbolt/user_group.rb +24 -0
  63. data/lib/strongbolt/user_groups_user.rb +16 -0
  64. data/lib/strongbolt/users_tenant.rb +12 -0
  65. data/lib/strongbolt/version.rb +3 -0
  66. data/lib/tasks/strongbolt_tasks.rake +29 -0
  67. data/spec/controllers/strongbolt/capabilities_controller_spec.rb +254 -0
  68. data/spec/controllers/strongbolt/roles_controller_spec.rb +228 -0
  69. data/spec/controllers/strongbolt/user_groups_controller_spec.rb +216 -0
  70. data/spec/controllers/strongbolt/user_groups_users_controller_spec.rb +69 -0
  71. data/spec/controllers/without_authorization_controller_spec.rb +20 -0
  72. data/spec/dummy/.rspec +2 -0
  73. data/spec/dummy/README.rdoc +28 -0
  74. data/spec/dummy/Rakefile +6 -0
  75. data/spec/dummy/app/assets/images/.keep +0 -0
  76. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  77. data/spec/dummy/app/assets/stylesheets/application.css +15 -0
  78. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  79. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  80. data/spec/dummy/app/controllers/posts_controller.rb +18 -0
  81. data/spec/dummy/app/controllers/test_controller.rb +3 -0
  82. data/spec/dummy/app/controllers/without_authorization_controller.rb +5 -0
  83. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  84. data/spec/dummy/app/mailers/.keep +0 -0
  85. data/spec/dummy/app/models/.keep +0 -0
  86. data/spec/dummy/app/models/concerns/.keep +0 -0
  87. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  88. data/spec/dummy/bin/bundle +3 -0
  89. data/spec/dummy/bin/rails +4 -0
  90. data/spec/dummy/bin/rake +4 -0
  91. data/spec/dummy/config.ru +4 -0
  92. data/spec/dummy/config/application.rb +29 -0
  93. data/spec/dummy/config/boot.rb +5 -0
  94. data/spec/dummy/config/database.yml +25 -0
  95. data/spec/dummy/config/environment.rb +5 -0
  96. data/spec/dummy/config/environments/development.rb +37 -0
  97. data/spec/dummy/config/environments/production.rb +78 -0
  98. data/spec/dummy/config/environments/test.rb +39 -0
  99. data/spec/dummy/config/initializers/assets.rb +8 -0
  100. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  101. data/spec/dummy/config/initializers/cookies_serializer.rb +3 -0
  102. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  103. data/spec/dummy/config/initializers/inflections.rb +16 -0
  104. data/spec/dummy/config/initializers/mime_types.rb +4 -0
  105. data/spec/dummy/config/initializers/session_store.rb +3 -0
  106. data/spec/dummy/config/initializers/strongbolt.rb +32 -0
  107. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  108. data/spec/dummy/config/locales/en.yml +23 -0
  109. data/spec/dummy/config/routes.rb +12 -0
  110. data/spec/dummy/config/secrets.yml +22 -0
  111. data/spec/dummy/db/development.sqlite3 +0 -0
  112. data/spec/dummy/db/migrate/20150630212236_create_strongbolt_tables.rb +54 -0
  113. data/spec/dummy/db/migrate/20150630212251_create_strongbolt_tables_indexes.rb +21 -0
  114. data/spec/dummy/db/schema.rb +84 -0
  115. data/spec/dummy/db/test.sqlite3 +0 -0
  116. data/spec/dummy/lib/assets/.keep +0 -0
  117. data/spec/dummy/public/404.html +67 -0
  118. data/spec/dummy/public/422.html +67 -0
  119. data/spec/dummy/public/500.html +66 -0
  120. data/spec/dummy/public/favicon.ico +0 -0
  121. data/spec/fabricators/capability_fabricator.rb +4 -0
  122. data/spec/fabricators/role_fabricator.rb +9 -0
  123. data/spec/fabricators/user_fabricator.rb +3 -0
  124. data/spec/fabricators/user_group_fabricator.rb +9 -0
  125. data/spec/fixtures/application.rb +28 -0
  126. data/spec/fixtures/controllers.rb +5 -0
  127. data/spec/spec_helper.rb +89 -0
  128. data/spec/strongbolt/bolted_controller_spec.rb +706 -0
  129. data/spec/strongbolt/bolted_spec.rb +136 -0
  130. data/spec/strongbolt/capability_spec.rb +251 -0
  131. data/spec/strongbolt/configuration_spec.rb +119 -0
  132. data/spec/strongbolt/controllers/url_helpers_spec.rb +34 -0
  133. data/spec/strongbolt/helpers_spec.rb +43 -0
  134. data/spec/strongbolt/role_spec.rb +90 -0
  135. data/spec/strongbolt/tenantable_spec.rb +281 -0
  136. data/spec/strongbolt/user_abilities_spec.rb +509 -0
  137. data/spec/strongbolt/user_group_spec.rb +37 -0
  138. data/spec/strongbolt/users_tenant_spec.rb +36 -0
  139. data/spec/strongbolt_spec.rb +274 -0
  140. data/spec/support/controller_macros.rb +11 -0
  141. data/spec/support/db_setup.rb +134 -0
  142. data/spec/support/helpers.rb +62 -0
  143. data/spec/support/transactional_specs.rb +17 -0
  144. data/strongbolt.gemspec +32 -0
  145. metadata +407 -0
@@ -0,0 +1,297 @@
1
+ module Strongbolt
2
+ module BoltedController
3
+
4
+ #
5
+ # Maps controller actions to CRUD operations
6
+ #
7
+ ACTIONS_MAPPING = {
8
+ :index => :find,
9
+ :show => :find,
10
+ :edit => :update,
11
+ :update => :update,
12
+ :new => :create,
13
+ :create => :create,
14
+ :destroy => :destroy
15
+ }
16
+
17
+ module ClassMethods
18
+ #
19
+ # Allows defining a specific model for this controller,
20
+ # if it cannot be infer from the controller name
21
+ #
22
+ def model_for_authorization= model
23
+ @model_for_authorization = case model
24
+ when Class then model
25
+ when String then constantize_model(model)
26
+ when nil then nil
27
+ else
28
+ raise ArgumentError, "Model for authorization must be a Class or the name of the Class"
29
+ end
30
+ end
31
+
32
+ #
33
+ # Returns the model used for authorization,
34
+ # using controller name if not defined
35
+ #
36
+ def model_for_authorization
37
+ if @model_for_authorization.present?
38
+ @model_for_authorization
39
+ else
40
+ # We cannot just do controller_name.classify as it doesn't keep the modules
41
+ # We'll also check demoduling one module after the other for case when
42
+ # the controller and/or the model have different modules
43
+ full_name = name.sub("Controller", "").classify
44
+ # Split by ::
45
+ splits = full_name.split('::')
46
+ # While we still have modules to test
47
+ while splits.size >= 1
48
+ begin
49
+ return constantize_model splits.join('::')
50
+ rescue Strongbolt::ModelNotFound => e
51
+ ensure
52
+ # Removes first element
53
+ splits.shift
54
+ end
55
+ end
56
+ raise Strongbolt::ModelNotFound, "Model for controller #{controller_name} wasn't found"
57
+ end
58
+ end
59
+
60
+ #
61
+ # Skips controller authorization check for this controller
62
+ # No argument given will skip for all actions,
63
+ # and can be passed only: [] or except: []
64
+ #
65
+ def skip_controller_authorization opts = {}
66
+ skip_before_action :check_authorization, opts
67
+ end
68
+
69
+ #
70
+ # Skip all authorization checking for the controller,
71
+ # or a subset of actions
72
+ #
73
+ def skip_all_authorization opts = {}
74
+ skip_controller_authorization opts
75
+ around_action :disable_authorization, opts
76
+ end
77
+
78
+ #
79
+ # Sets what CRUD operation match a specific sets of non RESTful actions
80
+ #
81
+ [:find, :update, :create, :destroy].each do |operation|
82
+ define_method "authorize_as_#{operation}" do |*args|
83
+ args.each do |action|
84
+ actions_mapping[action] = operation
85
+ end
86
+ end
87
+ end
88
+
89
+ #
90
+ # Render without authorization, for better performance
91
+ #
92
+ def render_without_authorization *actions
93
+ self.actions_without_authorization = [*actions]
94
+
95
+ class_eval do
96
+ #
97
+ # It needs to be created afterward,
98
+ # No idea why
99
+ #
100
+ def render *args
101
+ if render_without_authorization?
102
+ Strongbolt.without_authorization { _render *args }
103
+ else
104
+ _render *args
105
+ end
106
+ end
107
+ end
108
+ end
109
+
110
+ #
111
+ # Render with authorization
112
+ #
113
+ def render_with_authorization
114
+ self.actions_without_authorization = nil
115
+ end
116
+
117
+ #
118
+ # Returns the actions mapping of this controller
119
+ #
120
+ def actions_mapping
121
+ # Defaults to a duplicate of the standard mapping
122
+ @actions_mapping ||= ACTIONS_MAPPING.dup
123
+ end
124
+
125
+ private
126
+
127
+ #
128
+ # Try to constantize a class
129
+ #
130
+ def constantize_model name
131
+ begin
132
+ name.constantize
133
+ rescue NameError
134
+ raise Strongbolt::ModelNotFound, "Model for controller #{controller_name} wasn't found"
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ module InstanceMethods
141
+
142
+ def can? *args
143
+ Strongbolt.current_user.can? *args
144
+ end
145
+
146
+ def cannot? *args
147
+ Strongbolt.current_user.cannot? *args
148
+ end
149
+
150
+ #
151
+ # Checks if the current action needs verification
152
+ #
153
+ def render_without_authorization?
154
+ self.class.actions_without_authorization.present? &&
155
+ self.class.actions_without_authorization.include?(params[:action].to_sym)
156
+ end
157
+
158
+ #
159
+ # We're aliasing render so we can trigger the without auth
160
+ #
161
+ # DOESN'T WORK WHEN DEFINED HERE?
162
+ #
163
+ def render *args
164
+ if render_without_authorization?
165
+ Strongbolt.without_authorization { _render *args }
166
+ else
167
+ _render *args
168
+ end
169
+ end
170
+
171
+ private
172
+
173
+ #
174
+ # Sets the current user using the :current_user method.
175
+ # Without Grant, as with it it would check if the user
176
+ # can find itself before having be assigned anything...
177
+ #
178
+ # Better than having to set an anymous method for granting
179
+ # find to anyone!
180
+ #
181
+ def set_current_user
182
+ # To be accessible in the model when not granted
183
+ $request = request
184
+ Grant::Status.without_grant do
185
+ Strongbolt.current_user = send(:current_user) if respond_to?(:current_user)
186
+ end
187
+ end
188
+
189
+ #
190
+ # Unset the current user, by security (needed in some servers with only 1 thread)
191
+ #
192
+ def unset_current_user
193
+ Strongbolt.current_user = nil
194
+ end
195
+
196
+ #
197
+ # Checks authorization on the object, without fetching it
198
+ # so it can say yes to :index but won't authorize loading everything
199
+ # after, in the model by model authorization
200
+ #
201
+ def check_authorization
202
+ # If no user or disabled, no need
203
+ if Strongbolt.current_user.present? && Strongbolt.enabled?
204
+ begin
205
+ # Current model
206
+ # begin
207
+ obj = self.class.model_for_authorization
208
+ # rescue Strongbolt::ModelNotFound
209
+ # Strongbolt.logger.warn "No class found or defined for controller #{controller_name}"
210
+ # return
211
+ # end
212
+
213
+ # Unless it is authorized for this action
214
+ unless Strongbolt.current_user.can? crud_operation_of(action_name), obj
215
+ Strongbolt.access_denied current_user, obj, crud_operation_of(action_name), request.try(:fullpath)
216
+ raise Strongbolt::Unauthorized.new Strongbolt.current_user, action_name, obj
217
+ end
218
+ rescue Strongbolt::Unauthorized => e
219
+ raise e
220
+ rescue => e
221
+ raise e
222
+ end
223
+ else
224
+ Strongbolt.logger.warn "No authorization checking because no current user"
225
+ end
226
+ end
227
+
228
+ #
229
+ # Catch Grant::Error and send Strongbolt::Unauthorized instead
230
+ #
231
+ def catch_grant_error
232
+ begin
233
+ yield
234
+ rescue Grant::Error => e
235
+ raise Strongbolt::Unauthorized, e.to_s
236
+ end
237
+ end
238
+
239
+ #
240
+ # Returns the CRUD operations based on the action name
241
+ #
242
+ def crud_operation_of action
243
+ operation = self.class.actions_mapping[action.to_sym]
244
+ # If nothing find, we raise an error
245
+ if operation.nil?
246
+ raise Strongbolt::ActionNotConfigured, "Action #{action} on controller #{self.class.controller_name} not mapped to a CRUD operation"
247
+ end
248
+ # Else ok
249
+ operation
250
+ end
251
+
252
+ #
253
+ # CAREFUL: this skips authorization !
254
+ #
255
+ def disable_authorization
256
+ Strongbolt.without_authorization { yield }
257
+ Strongbolt.logger.warn "Authorization were disabled!"
258
+ end
259
+
260
+ end
261
+
262
+ def self.included(receiver)
263
+ receiver.class_eval do
264
+ # Compulsory filters
265
+ before_action :set_current_user
266
+ after_action :unset_current_user
267
+
268
+ # Catch Grant error
269
+ around_action :catch_grant_error
270
+
271
+ # Quick check of high level authorization
272
+ before_action :check_authorization
273
+
274
+ # A list storing actions that render without authorization
275
+ self.class.send :attr_accessor, :actions_without_authorization
276
+
277
+ # To allow render without authorization
278
+ alias_method :_render, :render
279
+
280
+ # Catch errors
281
+ rescue_from Strongbolt::Unauthorized, Grant::Error do |e|
282
+ if respond_to? :unauthorized
283
+ unauthorized e
284
+ else
285
+ raise Strongbolt::Unauthorized.new e.to_s
286
+ end
287
+ end
288
+
289
+ end # End receiver class eval
290
+
291
+ receiver.extend ClassMethods
292
+ receiver.send :include, InstanceMethods
293
+
294
+ end # End self.included
295
+
296
+ end
297
+ end
@@ -0,0 +1,15 @@
1
+ module Strongbolt
2
+ class CapabilitiesRole < Base
3
+ authorize_as "Strongbolt::Role"
4
+
5
+ belongs_to :role,
6
+ :class_name => "Strongbolt::Role",
7
+ :inverse_of => :capabilities_roles
8
+
9
+ belongs_to :capability,
10
+ :class_name => "Strongbolt::Capability",
11
+ :inverse_of => :capabilities_roles
12
+
13
+ validates_presence_of :role, :capability
14
+ end
15
+ end
@@ -0,0 +1,165 @@
1
+ module Strongbolt
2
+ class Capability < Base
3
+
4
+ Actions = %w{find create update destroy}
5
+
6
+ DEFAULT_MODELS = ["Strongbolt::UserGroup",
7
+ "Strongbolt::Role",
8
+ "Strongbolt::Capability",
9
+ "Strongbolt::UsersTenant"]
10
+
11
+ has_many :capabilities_roles,
12
+ :class_name => "Strongbolt::CapabilitiesRole",
13
+ :dependent => :restrict_with_exception,
14
+ :inverse_of => :capability
15
+
16
+ has_many :roles, :through => :capabilities_roles
17
+
18
+ has_many :users, through: :roles
19
+
20
+ validates :model, :action, presence: true
21
+ validates :action, inclusion: Actions,
22
+ uniqueness: {scope: [:model, :require_ownership, :require_tenant_access]}
23
+ validate :model_exists?
24
+
25
+ before_validation :set_default
26
+ after_initialize :set_default
27
+
28
+ #
29
+ # List all the models to be used in capabilities
30
+ #
31
+ def self.models() @models ||= DEFAULT_MODELS; end
32
+ def self.models=(models) @models = models; end
33
+
34
+ def self.add_models models
35
+ @models ||= DEFAULT_MODELS
36
+ @models |= [*models]
37
+ @models.sort!
38
+ end
39
+
40
+ scope :ordered, -> {
41
+ select("#{self.table_name}.*")
42
+ .select("CASE WHEN action = 'find' THEN 0 " +
43
+ "WHEN action = 'create' THEN 1 " +
44
+ "WHEN action = 'update' THEN 2 " +
45
+ "WHEN action = 'destroy' THEN 3 END AS action_id")
46
+ .order(:model, :require_ownership, :require_tenant_access, 'action_id')
47
+ }
48
+
49
+ #
50
+ # Group by model, ownership and tenant access
51
+ # and tells whether each action is set or not
52
+ #
53
+ def self.to_table
54
+ table = []
55
+ all.ordered.each do |capability|
56
+ if table.last.nil? ||
57
+ ! (table.last[:model] == capability.model &&
58
+ table.last[:require_ownership] == capability.require_ownership &&
59
+ table.last[:require_tenant_access] == capability.require_tenant_access)
60
+
61
+ table << {
62
+ model: capability.model,
63
+ require_ownership: capability.require_ownership,
64
+ require_tenant_access: capability.require_tenant_access,
65
+ find: false,
66
+ create: false,
67
+ update: false,
68
+ destroy: false
69
+ }
70
+ end
71
+
72
+ table.last[capability.action.to_sym] = true
73
+ end
74
+ table
75
+ end
76
+
77
+ #
78
+ # Group by model, ownership and tenant access
79
+ # and tells whether each action is set or not
80
+ # in a hash
81
+ #
82
+ def self.to_hash
83
+ hash = {}
84
+ all.ordered.each do |capability|
85
+ key = {
86
+ model: capability.model,
87
+ require_ownership: capability.require_ownership,
88
+ require_tenant_access: capability.require_tenant_access
89
+ }
90
+
91
+ hash[key] ||= {
92
+ find: false,
93
+ create: false,
94
+ update: false,
95
+ destroy: false
96
+ }
97
+
98
+ hash[key][capability.action.to_sym] = true
99
+ end
100
+ hash
101
+ end
102
+
103
+
104
+
105
+ #
106
+ # Create a set capabilities from a hash
107
+ # which has:
108
+ # {
109
+ # model: "ModelName",
110
+ # require_ownership: true,
111
+ # require_tenant_access: false,
112
+ # actions: [:find, :update]}
113
+ #
114
+ # Actions can be either one operation, an array of operations,
115
+ # or :all meaning all operations
116
+ #
117
+ def self.from_hash hash
118
+ hash.symbolize_keys!
119
+ actions_from_list(hash[:actions]).map do |action|
120
+ new :model => hash[:model],
121
+ :require_ownership => hash[:require_ownership],
122
+ :require_tenant_access => hash[:require_tenant_access],
123
+ :action => action
124
+ end
125
+ end
126
+
127
+ #
128
+ # Virtual setter of actions
129
+ #
130
+ def self.actions_from_list actions
131
+ # Transform actions array
132
+ if actions.respond_to?(:to_sym) && actions.to_sym == :all
133
+ Actions # All actions
134
+ else
135
+ [*actions] # Transform into an array
136
+ end
137
+ end
138
+
139
+ private
140
+
141
+ #
142
+ # Checks that the model given as a string exists
143
+ #
144
+ def model_exists?
145
+ if model.present?
146
+ begin
147
+ model.constantize
148
+ rescue NameError => e
149
+ errors.add :model, "#{model} is not a valid model"
150
+ end
151
+ end
152
+ end
153
+
154
+ #
155
+ # Default parameters
156
+ #
157
+ def set_default
158
+ self.require_ownership = true if require_ownership.nil?
159
+ self.require_tenant_access = true if require_tenant_access.nil?
160
+ true # Ensures it passes
161
+ end
162
+ end
163
+ end
164
+
165
+ Capability = Strongbolt::Capability unless defined? Capability