shamu 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (207) hide show
  1. checksums.yaml +4 -4
  2. data/.codeclimate.yml +26 -0
  3. data/.gitignore +2 -1
  4. data/.rubocop.yml +89 -30
  5. data/.yardopts +4 -5
  6. data/Gemfile +24 -12
  7. data/Guardfile +5 -0
  8. data/LABELS.md +22 -0
  9. data/README.md +41 -0
  10. data/Rakefile +12 -0
  11. data/circle.yml +7 -3
  12. data/config.ru +7 -0
  13. data/lib/shamu/active_record.rb +7 -0
  14. data/lib/shamu/attributes/assignment.rb +114 -0
  15. data/lib/shamu/attributes/equality.rb +40 -0
  16. data/lib/shamu/attributes/fluid_assignment.rb +49 -0
  17. data/lib/shamu/attributes/validation.rb +74 -0
  18. data/lib/shamu/attributes.rb +255 -0
  19. data/lib/shamu/auditing/README.md +0 -0
  20. data/lib/shamu/auditing/audit_record.rb +32 -0
  21. data/lib/shamu/auditing/auditing_service.rb +32 -0
  22. data/lib/shamu/auditing/list_scope.rb +22 -0
  23. data/lib/shamu/auditing/logging_auditing_service.rb +16 -0
  24. data/lib/shamu/auditing/support.rb +75 -0
  25. data/lib/shamu/auditing/transaction.rb +58 -0
  26. data/lib/shamu/auditing.rb +12 -0
  27. data/lib/shamu/entities/README.md +1 -0
  28. data/lib/shamu/entities/active_record.rb +123 -0
  29. data/lib/shamu/entities/active_record_soft_destroy.rb +91 -0
  30. data/lib/shamu/entities/entity.rb +196 -0
  31. data/lib/shamu/entities/entity_path.rb +87 -0
  32. data/lib/shamu/entities/identity_cache.rb +64 -0
  33. data/lib/shamu/entities/list.rb +54 -0
  34. data/lib/shamu/entities/list_scope/dates.rb +57 -0
  35. data/lib/shamu/entities/list_scope/paging.rb +51 -0
  36. data/lib/shamu/entities/list_scope/scoped_paging.rb +65 -0
  37. data/lib/shamu/entities/list_scope/sorting.rb +76 -0
  38. data/lib/shamu/entities/list_scope.rb +105 -0
  39. data/lib/shamu/entities/null_entity.rb +88 -0
  40. data/lib/shamu/entities.rb +11 -0
  41. data/lib/shamu/error.rb +23 -5
  42. data/lib/shamu/events/README.md +0 -0
  43. data/lib/shamu/events/active_record/channel.rb +36 -0
  44. data/lib/shamu/events/active_record/message.rb +52 -0
  45. data/lib/shamu/events/active_record/migration.rb +49 -0
  46. data/lib/shamu/events/active_record/runner.rb +28 -0
  47. data/lib/shamu/events/active_record/service.rb +174 -0
  48. data/lib/shamu/events/active_record.rb +13 -0
  49. data/lib/shamu/events/channel_stats.rb +23 -0
  50. data/lib/shamu/events/error.rb +24 -0
  51. data/lib/shamu/events/events_service.rb +136 -0
  52. data/lib/shamu/events/in_memory/async_service.rb +48 -0
  53. data/lib/shamu/events/in_memory/service.rb +97 -0
  54. data/lib/shamu/events/in_memory.rb +10 -0
  55. data/lib/shamu/events/message.rb +38 -0
  56. data/lib/shamu/events/support.rb +60 -0
  57. data/lib/shamu/events.rb +12 -0
  58. data/lib/shamu/features/README.md +0 -0
  59. data/lib/shamu/features/conditions/condition.rb +39 -0
  60. data/lib/shamu/features/conditions/env.rb +37 -0
  61. data/lib/shamu/features/conditions/hosts.rb +25 -0
  62. data/lib/shamu/features/conditions/matching.rb +16 -0
  63. data/lib/shamu/features/conditions/not_matching.rb +16 -0
  64. data/lib/shamu/features/conditions/percentage.rb +44 -0
  65. data/lib/shamu/features/conditions/proc.rb +54 -0
  66. data/lib/shamu/features/conditions/roles.rb +23 -0
  67. data/lib/shamu/features/conditions/schedule_at.rb +27 -0
  68. data/lib/shamu/features/conditions.rb +18 -0
  69. data/lib/shamu/features/config_service.rb +10 -0
  70. data/lib/shamu/features/context.rb +80 -0
  71. data/lib/shamu/features/env_store.rb +88 -0
  72. data/lib/shamu/features/errors.rb +29 -0
  73. data/lib/shamu/features/features_service.rb +168 -0
  74. data/lib/shamu/features/list_scope.rb +30 -0
  75. data/lib/shamu/features/selector.rb +50 -0
  76. data/lib/shamu/features/support.rb +51 -0
  77. data/lib/shamu/features/toggle.rb +149 -0
  78. data/lib/shamu/features/toggle_codec.rb +69 -0
  79. data/lib/shamu/features.rb +16 -0
  80. data/lib/shamu/locale/en.yml +22 -2
  81. data/lib/shamu/logger.rb +13 -0
  82. data/lib/shamu/rack/README.md +0 -0
  83. data/lib/shamu/rack/cookies.rb +115 -0
  84. data/lib/shamu/rack/cookies_middleware.rb +26 -0
  85. data/lib/shamu/rack/query_params.rb +41 -0
  86. data/lib/shamu/rack/query_params_middleware.rb +24 -0
  87. data/lib/shamu/rack.rb +12 -0
  88. data/lib/shamu/rails/controller.rb +131 -0
  89. data/lib/shamu/rails/entity.rb +168 -0
  90. data/lib/shamu/rails/features.rb +13 -0
  91. data/lib/shamu/rails/railtie.rb +30 -0
  92. data/lib/shamu/rails.rb +10 -0
  93. data/lib/shamu/rspec/matchers.rb +44 -0
  94. data/lib/shamu/rspec.rb +1 -0
  95. data/lib/shamu/security/README.md +0 -0
  96. data/lib/shamu/security/active_record_policy.rb +106 -0
  97. data/lib/shamu/security/error.rb +65 -0
  98. data/lib/shamu/security/hashed_value.rb +71 -0
  99. data/lib/shamu/security/no_policy.rb +15 -0
  100. data/lib/shamu/security/policy.rb +289 -0
  101. data/lib/shamu/security/policy_refinement.rb +50 -0
  102. data/lib/shamu/security/policy_rule.rb +59 -0
  103. data/lib/shamu/security/principal.rb +72 -0
  104. data/lib/shamu/security/roles.rb +62 -0
  105. data/lib/shamu/security/roles_service.rb +30 -0
  106. data/lib/shamu/security/support.rb +83 -0
  107. data/lib/shamu/security.rb +43 -0
  108. data/lib/shamu/services/README.md +2 -0
  109. data/lib/shamu/services/active_record.rb +58 -0
  110. data/lib/shamu/services/active_record_crud.rb +378 -0
  111. data/lib/shamu/services/error.rb +24 -0
  112. data/lib/shamu/services/lazy_association.rb +31 -0
  113. data/lib/shamu/services/lazy_transform.rb +97 -0
  114. data/lib/shamu/services/request.rb +122 -0
  115. data/lib/shamu/services/request_support.rb +124 -0
  116. data/lib/shamu/services/result.rb +75 -0
  117. data/lib/shamu/services/service.rb +355 -0
  118. data/lib/shamu/services.rb +12 -0
  119. data/lib/shamu/sessions/README.md +2 -0
  120. data/lib/shamu/sessions/cookie_store.rb +79 -0
  121. data/lib/shamu/sessions/session_store.rb +42 -0
  122. data/lib/shamu/sessions.rb +8 -0
  123. data/lib/shamu/to_bool_extension.rb +57 -0
  124. data/lib/shamu/to_model_id_extension.rb +50 -0
  125. data/lib/shamu/version.rb +10 -4
  126. data/lib/shamu.rb +18 -6
  127. data/shamu.gemspec +21 -10
  128. data/spec/internal/README.md +4 -0
  129. data/spec/internal/config/database.yml +3 -0
  130. data/spec/internal/config/routes.rb +3 -0
  131. data/spec/internal/db/schema.rb +3 -0
  132. data/spec/internal/log/.gitignore +1 -0
  133. data/spec/internal/public/favicon.ico +0 -0
  134. data/spec/lib/shamu/active_record_support.rb +32 -0
  135. data/spec/lib/shamu/attributes/assignment_spec.rb +129 -0
  136. data/spec/lib/shamu/attributes/equality_spec.rb +63 -0
  137. data/spec/lib/shamu/attributes/fluid_assignment_spec.rb +31 -0
  138. data/spec/lib/shamu/attributes/validation_spec.rb +53 -0
  139. data/spec/lib/shamu/attributes_spec.rb +331 -0
  140. data/spec/lib/shamu/auditing/logging_auditing_service_spec.rb +18 -0
  141. data/spec/lib/shamu/auditing/support_spec.rb +41 -0
  142. data/spec/lib/shamu/entities/active_record_soft_destroy_spec.rb +82 -0
  143. data/spec/lib/shamu/entities/active_record_spec.rb +66 -0
  144. data/spec/lib/shamu/entities/entity_path_spec.rb +40 -0
  145. data/spec/lib/shamu/entities/entity_spec.rb +56 -0
  146. data/spec/lib/shamu/entities/identity_cache_spec.rb +69 -0
  147. data/spec/lib/shamu/entities/list_scope/dates_spec.rb +47 -0
  148. data/spec/lib/shamu/entities/list_scope/paging_spec.rb +41 -0
  149. data/spec/lib/shamu/entities/list_scope/scoped_paging_spec.rb +40 -0
  150. data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +59 -0
  151. data/spec/lib/shamu/entities/list_scope_spec.rb +127 -0
  152. data/spec/lib/shamu/entities/list_spec.rb +60 -0
  153. data/spec/lib/shamu/entities/null_entity_spec.rb +94 -0
  154. data/spec/lib/shamu/events/active_record/migration_spec.rb +11 -0
  155. data/spec/lib/shamu/events/active_record/service_spec.rb +139 -0
  156. data/spec/lib/shamu/events/events_service_spec.rb +57 -0
  157. data/spec/lib/shamu/events/in_memory/async_service_spec.rb +37 -0
  158. data/spec/lib/shamu/events/in_memory/service_spec.rb +36 -0
  159. data/spec/lib/shamu/events/message_spec.rb +7 -0
  160. data/spec/lib/shamu/events/support_spec.rb +44 -0
  161. data/spec/lib/shamu/features/conditions/condition_spec.rb +8 -0
  162. data/spec/lib/shamu/features/conditions/env_spec.rb +29 -0
  163. data/spec/lib/shamu/features/conditions/hosts_spec.rb +21 -0
  164. data/spec/lib/shamu/features/conditions/matching_spec.rb +23 -0
  165. data/spec/lib/shamu/features/conditions/percentage_spec.rb +71 -0
  166. data/spec/lib/shamu/features/conditions/proc_spec.rb +28 -0
  167. data/spec/lib/shamu/features/env_store_spec.rb +48 -0
  168. data/spec/lib/shamu/features/features.yml +34 -0
  169. data/spec/lib/shamu/features/features_service_spec.rb +109 -0
  170. data/spec/lib/shamu/features/secondary.yml +5 -0
  171. data/spec/lib/shamu/features/selector_spec.rb +17 -0
  172. data/spec/lib/shamu/features/support_spec.rb +45 -0
  173. data/spec/lib/shamu/features/toggle_codec_spec.rb +28 -0
  174. data/spec/lib/shamu/features/toggle_spec.rb +42 -0
  175. data/spec/lib/shamu/rack/cookies_middleware_spec.rb +33 -0
  176. data/spec/lib/shamu/rack/cookies_spec.rb +43 -0
  177. data/spec/lib/shamu/rack/query_params_middleware_spec.rb +33 -0
  178. data/spec/lib/shamu/rack/query_params_spec.rb +23 -0
  179. data/spec/lib/shamu/rails/controller_spec.rb +74 -0
  180. data/spec/lib/shamu/rails/entity_spec.rb +150 -0
  181. data/spec/lib/shamu/rails/features.yml +13 -0
  182. data/spec/lib/shamu/rails/features_spec.rb +45 -0
  183. data/spec/lib/shamu/security/active_record_policy_spec.rb +38 -0
  184. data/spec/lib/shamu/security/hashed_value_spec.rb +41 -0
  185. data/spec/lib/shamu/security/policy_refinement_spec.rb +61 -0
  186. data/spec/lib/shamu/security/policy_rule_spec.rb +60 -0
  187. data/spec/lib/shamu/security/policy_spec.rb +158 -0
  188. data/spec/lib/shamu/security/roles_spec.rb +46 -0
  189. data/spec/lib/shamu/services/active_record_crud_spec.rb +460 -0
  190. data/spec/lib/shamu/services/active_record_spec.rb +92 -0
  191. data/spec/lib/shamu/services/lazy_association_spec.rb +31 -0
  192. data/spec/lib/shamu/services/lazy_transform_spec.rb +96 -0
  193. data/spec/lib/shamu/services/request_spec.rb +58 -0
  194. data/spec/lib/shamu/services/request_support_spec.rb +129 -0
  195. data/spec/lib/shamu/services/result_spec.rb +37 -0
  196. data/spec/lib/shamu/services/service_spec.rb +307 -0
  197. data/spec/lib/shamu/sessions/cookie_store_spec.rb +44 -0
  198. data/spec/lib/shamu/to_bool_extension_spec.rb +67 -0
  199. data/spec/lib/shamu/to_model_id_extension_spec.rb +54 -0
  200. data/spec/rails_helper.rb +13 -0
  201. data/spec/spec_helper.rb +17 -12
  202. data/spec/support/active_record.rb +17 -0
  203. data/spec/support/database.rb +14 -0
  204. data/spec/support/logger.rb +0 -0
  205. metadata +383 -9
  206. data/spec/lib/shamu_spec.rb +0 -5
  207. /data/{spec/internal/log/test.log → lib/shamu/attributes/README.md} +0 -0
@@ -0,0 +1,168 @@
1
+ module Shamu
2
+ module Rails
3
+
4
+ # Manages loading an entity as part of a controller action. See {.entity}
5
+ # for details.
6
+ module Entity
7
+ extend ActiveSupport::Concern
8
+
9
+ private
10
+
11
+ def fetch_entity( service, param )
12
+ service.find( params[ param ] )
13
+ end
14
+
15
+ def fetch_entities( service, param )
16
+ service.list( param ? params[ param ] : params )
17
+ end
18
+
19
+ def fetch_entity_request( service, entity, param_key )
20
+ action = params[ :action ].to_sym
21
+ return unless service.respond_to?( :request_for )
22
+ return unless request = service.request_for( action, entity )
23
+
24
+ param_key ||= entity.model_name.param_key
25
+
26
+ strong_param = :"#{ param_key }_params"
27
+ if respond_to?( strong_param, true )
28
+ request.assign_attributes( send( strong_param ) )
29
+ else
30
+ request.assign_attributes( params[ param_key ] )
31
+ end
32
+
33
+ service.authorize!( action, entity, request ) if service.respond_to?( :authorize! )
34
+ request
35
+ end
36
+
37
+ def load_entity( method:, list_method:, action: nil, only: nil, except: nil )
38
+ action ||= params[ :action ].to_sym
39
+ return unless matching_entity_action?( action, only: only, except: except )
40
+
41
+ send list_action?( action ) ? list_method : method
42
+ end
43
+
44
+ def matching_entity_action?( action, only:, except: )
45
+ return if only.present? && !only.include?( action )
46
+ return if except.present? && except.include?( action )
47
+ true
48
+ end
49
+
50
+ def list_action?( action )
51
+ action == :index
52
+ end
53
+
54
+ class_methods do
55
+
56
+ # Declare an entity dependency to be resolved before the requested
57
+ # controller action. Shamu will attempt to load an entity through the
58
+ # service and make it available to the controller as an attribute and
59
+ # a helper method.
60
+ #
61
+ # Adds a method named after the entity excluding the namespace and
62
+ # "Entity" suffix (Users::UserEntity => #user). It also makes an
63
+ # entity_request method available for mutating actions such as new,
64
+ # create, update, edit, etc.
65
+ #
66
+ # ```
67
+ # class UsersController < ApplicationController
68
+ # service :users_service, Users::UsersService
69
+ # entity Users::UserEntity
70
+ #
71
+ # def show
72
+ # render json: { name: user.name, id: user.id }
73
+ # end
74
+ #
75
+ # def update
76
+ # result = users_service.update( user_request )
77
+ # respond_with result
78
+ # end
79
+ # end
80
+ # ```
81
+ #
82
+ # @param [Class] entity_class an {Entities::Entity} class to be loaded.
83
+ # @param [Symbol] through the name of the service to fetch the entity
84
+ # from. If not set, guesses the name of the service from the entity
85
+ # class.
86
+ # @param [Symbol] as the name of the method to expose the entity
87
+ # through.
88
+ # @param [Symbol] list the name of the method to expose the list of
89
+ # entities for index actions.
90
+ # @param [Array<Symbol>] only load the entity only for the given
91
+ # actions.
92
+ # @param [Array<Symbol>] except load the entity except for the given
93
+ # actions.
94
+ # @param [Symbol] param the request param that holds the id of the
95
+ # entity.
96
+ # @param [Symbol] list_param request param that hols list scope params.
97
+ # @param [Symbol] param_key request param that holds the attributes used
98
+ # to populate the service change request.
99
+ # @param [Symbol] action override the default action detection. For
100
+ # example always use :show for a secondary or root entity that is
101
+ # not being modified in an :update request.
102
+ def entity( entity_class, through: nil, as: nil, list: nil, only: nil, except: nil, param: :id, list_param: nil, action: nil, param_key: nil ) # rubocop:disable Metrics/LineLength
103
+ as ||= entity_as_name( entity_class )
104
+ through ||= :"#{ as }_service"
105
+ list ||= as.to_s.pluralize.to_sym
106
+
107
+ define_entity_method( as, through, param )
108
+ define_entities_method( list, through, list_param )
109
+ define_entity_request_method( as, through, param_key )
110
+
111
+ before_action do
112
+ load_entity( method: as,
113
+ list_method: list,
114
+ action: action,
115
+ only: only && Array( only ),
116
+ except: except && Array( except ) )
117
+ end
118
+ end
119
+
120
+ private
121
+
122
+ def entity_as_name( entity_class )
123
+ entity_class.model_name.element
124
+ end
125
+
126
+ def define_entity_method( as, through, param )
127
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
128
+ private
129
+
130
+ def #{ as } # def entity
131
+ return @#{ as } if defined? @#{ as } # return @entity if defined? @entity
132
+ @#{ as } = fetch_entity( #{ through }, :#{ param } ) # @entity = fetch_entity( entity_service, :id )
133
+ end # end
134
+
135
+ helper_method :#{ as }
136
+ RUBY
137
+ end
138
+
139
+ def define_entities_method( as, through, param )
140
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
141
+ private
142
+
143
+ def #{ as } # def entities
144
+ return @#{ as } if defined? @#{ as } # return @entities if defined? @entities
145
+ @#{ as } = fetch_entities( #{ through }, #{ param ? ":#{ param }" : 'nil' } ) # @entities = fetch_entities( entity_service, nil )
146
+ end # end
147
+
148
+ helper_method :#{ as }
149
+ RUBY
150
+ end
151
+
152
+ def define_entity_request_method( as, through, param )
153
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
154
+ private
155
+
156
+ def #{ as }_request # def entity_request
157
+ return @#{ as }_request if defined? @#{ as }_request # return @entity_request if defined? @entity_request
158
+ @#{ as }_request = fetch_entity_request( #{ through }, #{ as }, :#{ as } ) # @entity_request = fetch_entity_request( entity_service, entity, :entity )
159
+ end # end
160
+
161
+ helper_method :#{ as }_request
162
+ RUBY
163
+ end
164
+
165
+ end
166
+ end
167
+ end
168
+ end
@@ -0,0 +1,13 @@
1
+ module Shamu
2
+ module Rails
3
+
4
+ # Add support for testing for feature toggles to controllers and views.
5
+ module Features
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ include Shamu::Features::Support
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,30 @@
1
+ require "shamu/rack"
2
+
3
+ module Shamu
4
+ module Rails
5
+
6
+ # Integrate Shamu with rails.
7
+ class Railtie < ::Rails::Railtie
8
+
9
+ rake_tasks do
10
+ rake_path = File.expand_path( "../../tasks/*.rake" )
11
+ Dir[ rake_path ].each { |f| load f }
12
+ end
13
+
14
+ initializer "shamu.configure" do
15
+ if defined? ::ActionController
16
+ ::ActionController::Base.send :include, Shamu::Rails::Controller
17
+ ::ActionController::Base.send :include, Shamu::Rails::Entity
18
+ ::ActionController::Base.send :include, Shamu::Rails::Features
19
+ end
20
+ end
21
+
22
+ initializer "shamu.insert_middleware" do |app|
23
+ app.config.middleware.use "Scorpion::Rack::Middleware"
24
+ app.config.middleware.use "Shamu::Rack::CookiesMiddleware"
25
+ app.config.middleware.use "Shamu::Rack::QueryParamsMiddleware"
26
+ end
27
+
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,10 @@
1
+ module Shamu
2
+
3
+ # Rails integration.
4
+ module Rails
5
+ require "shamu/rails/entity"
6
+ require "shamu/rails/controller"
7
+ require "shamu/rails/features"
8
+ require "shamu/rails/railtie"
9
+ end
10
+ end
@@ -0,0 +1,44 @@
1
+
2
+ RSpec::Matchers.define :be_permitted_to do |*args|
3
+
4
+ def supports_block_expectations?
5
+ true
6
+ end
7
+
8
+ failure_message do |actual|
9
+ "expected that #{ actual.class.name } would be permitted to #{ expected.first } #{ expected.second }"
10
+ end
11
+ match do |policy|
12
+ policy.permit?( *args )
13
+ end
14
+ end
15
+
16
+ RSpec::Matchers.define :maybe_be_permitted_to do |*args|
17
+
18
+ def supports_block_expectations?
19
+ true
20
+ end
21
+
22
+ failure_message do |actual|
23
+ "expected that #{ actual.class.name } might be permitted to #{ expected.first } #{ expected.second }"
24
+ end
25
+
26
+ match do |policy|
27
+ policy.permit?( *args ) == :maybe
28
+ end
29
+ end
30
+
31
+ RSpec::Matchers.define :absolutely_be_permitted_to do |*args|
32
+
33
+ def supports_block_expectations?
34
+ true
35
+ end
36
+
37
+ failure_message do |actual|
38
+ "expected that #{ actual.class.name } would absolutely be permitted to #{ expected.first } #{ expected.second }"
39
+ end
40
+
41
+ match do |policy|
42
+ policy.permit?( *args ) == :yes
43
+ end
44
+ end
@@ -0,0 +1 @@
1
+ require "shamu/rspec/matchers"
File without changes
@@ -0,0 +1,106 @@
1
+ module Shamu
2
+ module Security
3
+
4
+ # Extends the standard {Policy} class to add {ActiveRecord::Relation}
5
+ # refinements based on granted policies.
6
+ #
7
+ # @example
8
+ # class UserPolicy < Shamu::Security::ActiveRecordPolicy
9
+ # private
10
+ #
11
+ # def permissions
12
+ # permit :read, UserEntity do |user|
13
+ # user.public?
14
+ # end
15
+ #
16
+ # refine :read, Models::User do |users, additional_context|
17
+ # users.where( public: true )
18
+ # end
19
+ # end
20
+ #
21
+ # end
22
+ #
23
+ # class UsersService < Shamu::Services::Service
24
+ # include Shamu::Security::Support
25
+ #
26
+ # def list
27
+ # entity_list policy.refine_relation( :list, Model::User.all ) do |record|
28
+ # scorpion.fetch UserEntity, { record: record }, {}
29
+ # end
30
+ # end
31
+ #
32
+ # private
33
+ #
34
+ # def policy_class
35
+ # UserPolicy
36
+ # end
37
+ # end
38
+ class ActiveRecordPolicy < Policy
39
+
40
+ # Refine an {ActiveRecord::Relation} to select only those records
41
+ # permitted for the given `action`.
42
+ #
43
+ # @param [Symbol] action to perform on the {Entities::Entity} that will be
44
+ # projected from the records.
45
+ # @param [ActiveRecord::Relation] relation to refine.
46
+ # @param [Object] additional_context that the {#refine} block may consider
47
+ # when applying the refinement.
48
+ # @return [ActiveRecord::Relation] the refined relation.
49
+ def refine_relation( action, relation, additional_context = nil )
50
+ refined = false
51
+
52
+ refinements.each do |refinement|
53
+ if refinement.match?( action, relation )
54
+ refined = true
55
+ relation = refinement.apply( relation, additional_context ) || relation
56
+ end
57
+ end
58
+
59
+ refined ? relation : relation.none
60
+ end
61
+
62
+ private
63
+
64
+ # ============================================================================
65
+ # @!group Dependencies
66
+ #
67
+
68
+ # @!visibility public
69
+ #
70
+ # Declare a refinement that should be applied to an
71
+ # {ActiveRecord::Relation} for the given actions. {#refine_relation}
72
+ # will yield the relation to any matching refinement to reduce the scope
73
+ # of available records available for projection.
74
+ #
75
+ # @example
76
+ # def permissions
77
+ # permit :read, UserEntity do |user|
78
+ # user.public?
79
+ # end
80
+ # refine :read, Models::User do |users, additional_context|
81
+ # users.where( public: true )
82
+ # end
83
+ # end
84
+ #
85
+ # @param [Array<Symbol>] actions that should be refined.
86
+ # @param [Class] model_class the {ActiveRecord::Base} class to refine.
87
+ # @yield (relation, additional_context)
88
+ # @yieldparam [ActiveRecord::Relation] relation to refine.
89
+ # @yieldparam [Object] additional_context offered to {#refine_relation}.
90
+ # @yieldreturn [ActiveRecord::Relation,nil] the refined relation, or nil
91
+ # if no refinement should be applied.
92
+ # @return [void]
93
+ def refine( *actions, model_class, &block )
94
+ refinements << PolicyRefinement.new( expand_aliases( actions ), model_class, block )
95
+ end
96
+
97
+ #
98
+ # @!endgroup Dependencies
99
+
100
+ def refinements
101
+ @refinements ||= []
102
+ end
103
+
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,65 @@
1
+ require "i18n"
2
+
3
+ module Shamu
4
+ module Security
5
+
6
+ # A generic error class for problems with shamu services.
7
+ class Error < Shamu::Error
8
+ private
9
+
10
+ def translation_scope
11
+ super.dup.insert( 1, :security )
12
+ end
13
+
14
+ end
15
+
16
+ # The requested action was not permitted on the resource.
17
+ class AccessDeniedError < Error
18
+
19
+ # ============================================================================
20
+ # @!group Attributes
21
+ #
22
+
23
+ # @return [Symbol] the requested action that was denied.
24
+ attr_reader :action
25
+
26
+ # @return [Object] the resource the {#action} was to be performed on.
27
+ attr_reader :resource
28
+
29
+ # @return [Principal] the security {Principal} in use at the time of the
30
+ # policy violation.
31
+ attr_reader :principal
32
+
33
+ # @return [Object] additional principal provided to the policy authorization
34
+ # method.
35
+ attr_reader :additional_context
36
+
37
+ #
38
+ # @!endgroup Attributes
39
+
40
+ def initialize( message = :access_denied, action: nil, resource: nil, principal: nil, additional_context: nil )
41
+ @action = action
42
+ @resource = resource
43
+ @principal = principal
44
+ @additional_context = additional_context
45
+
46
+ super translate( :access_denied, action: action, resource: resource )
47
+ end
48
+ end
49
+
50
+ # Security has been included but has not been completely set up.
51
+ class IncompleteSetupError < Error
52
+ def initialize( message = :incomplete_setup )
53
+ super
54
+ end
55
+ end
56
+
57
+ # A policy check was performed on an ActiveRecord resource
58
+ class NoActiveRecordPolicyChecksError < Error
59
+ def initialize( message = :no_actiev_record_policy_checks )
60
+ super
61
+ end
62
+ end
63
+
64
+ end
65
+ end
@@ -0,0 +1,71 @@
1
+ require "openssl"
2
+
3
+ module Shamu
4
+ module Security
5
+
6
+ # Adds support for hashing and verifying a string value.
7
+ #
8
+ # ```ruby
9
+ # class Codec
10
+ # include Shamu::Security::HashedValue
11
+ #
12
+ # def initialize( private_key = Shamu::Security.private_key )
13
+ # @private_key = private_key
14
+ # end
15
+ #
16
+ # def store( value )
17
+ # hash_value( value )
18
+ # end
19
+ #
20
+ # def restore( hashed )
21
+ # verify_hash( hashed )
22
+ # end
23
+ # end
24
+ #
25
+ # codec = Codec.new
26
+ # signed = codec.store "example" # => "0123456789abcdef0123456789abcdef012345678;example"
27
+ # codec.restore signed # => "example"
28
+ # codec.restore "example" # => nil
29
+ # ```
30
+ module HashedValue
31
+
32
+ private
33
+
34
+ # @!visiblity public
35
+ # @return [String] the private key used to sign the hashes.
36
+ attr_reader :private_key
37
+
38
+ # @!visibility public
39
+ #
40
+ # @param [String] string to hash.
41
+ # @return [String] packed string with hash and original value.
42
+ def hash_value( string )
43
+ return nil unless string
44
+ "#{ hash_digest( string ) }$#{ string }"
45
+ end
46
+
47
+ def hash_digest( string )
48
+ alg = OpenSSL::Digest::SHA1.new
49
+ OpenSSL::HMAC.hexdigest( alg, private_key, string )
50
+ end
51
+
52
+ # @!visiblity public
53
+ #
54
+ # Verify that the hashed value has not been modified.
55
+ #
56
+ # @param [String] hashed value returned from {#hash_value}.
57
+ # @return [String] the original value.
58
+ def verify_hash( hashed )
59
+ return unless hashed
60
+ return if hashed.length < 41
61
+
62
+ mac = hashed[ 0...40 ]
63
+ toggles = hashed[ 41..-1 ]
64
+
65
+ return unless hash_digest( toggles ) == mac
66
+
67
+ toggles
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,15 @@
1
+ module Shamu
2
+ module Security
3
+
4
+ # Used in specs and service to service delegated requests to effectively
5
+ # offer no policy and permit all actions.
6
+ class NoPolicy
7
+
8
+ # (see Policy#permit?)
9
+ def permit?( * )
10
+ :yes
11
+ end
12
+
13
+ end
14
+ end
15
+ end