strongbolt 0.3.6

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