shamu 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.codeclimate.yml +26 -0
- data/.gitignore +2 -1
- data/.rubocop.yml +89 -30
- data/.yardopts +4 -5
- data/Gemfile +24 -12
- data/Guardfile +5 -0
- data/LABELS.md +22 -0
- data/README.md +41 -0
- data/Rakefile +12 -0
- data/circle.yml +7 -3
- data/config.ru +7 -0
- data/lib/shamu/active_record.rb +7 -0
- data/lib/shamu/attributes/assignment.rb +114 -0
- data/lib/shamu/attributes/equality.rb +40 -0
- data/lib/shamu/attributes/fluid_assignment.rb +49 -0
- data/lib/shamu/attributes/validation.rb +74 -0
- data/lib/shamu/attributes.rb +255 -0
- data/lib/shamu/auditing/README.md +0 -0
- data/lib/shamu/auditing/audit_record.rb +32 -0
- data/lib/shamu/auditing/auditing_service.rb +32 -0
- data/lib/shamu/auditing/list_scope.rb +22 -0
- data/lib/shamu/auditing/logging_auditing_service.rb +16 -0
- data/lib/shamu/auditing/support.rb +75 -0
- data/lib/shamu/auditing/transaction.rb +58 -0
- data/lib/shamu/auditing.rb +12 -0
- data/lib/shamu/entities/README.md +1 -0
- data/lib/shamu/entities/active_record.rb +123 -0
- data/lib/shamu/entities/active_record_soft_destroy.rb +91 -0
- data/lib/shamu/entities/entity.rb +196 -0
- data/lib/shamu/entities/entity_path.rb +87 -0
- data/lib/shamu/entities/identity_cache.rb +64 -0
- data/lib/shamu/entities/list.rb +54 -0
- data/lib/shamu/entities/list_scope/dates.rb +57 -0
- data/lib/shamu/entities/list_scope/paging.rb +51 -0
- data/lib/shamu/entities/list_scope/scoped_paging.rb +65 -0
- data/lib/shamu/entities/list_scope/sorting.rb +76 -0
- data/lib/shamu/entities/list_scope.rb +105 -0
- data/lib/shamu/entities/null_entity.rb +88 -0
- data/lib/shamu/entities.rb +11 -0
- data/lib/shamu/error.rb +23 -5
- data/lib/shamu/events/README.md +0 -0
- data/lib/shamu/events/active_record/channel.rb +36 -0
- data/lib/shamu/events/active_record/message.rb +52 -0
- data/lib/shamu/events/active_record/migration.rb +49 -0
- data/lib/shamu/events/active_record/runner.rb +28 -0
- data/lib/shamu/events/active_record/service.rb +174 -0
- data/lib/shamu/events/active_record.rb +13 -0
- data/lib/shamu/events/channel_stats.rb +23 -0
- data/lib/shamu/events/error.rb +24 -0
- data/lib/shamu/events/events_service.rb +136 -0
- data/lib/shamu/events/in_memory/async_service.rb +48 -0
- data/lib/shamu/events/in_memory/service.rb +97 -0
- data/lib/shamu/events/in_memory.rb +10 -0
- data/lib/shamu/events/message.rb +38 -0
- data/lib/shamu/events/support.rb +60 -0
- data/lib/shamu/events.rb +12 -0
- data/lib/shamu/features/README.md +0 -0
- data/lib/shamu/features/conditions/condition.rb +39 -0
- data/lib/shamu/features/conditions/env.rb +37 -0
- data/lib/shamu/features/conditions/hosts.rb +25 -0
- data/lib/shamu/features/conditions/matching.rb +16 -0
- data/lib/shamu/features/conditions/not_matching.rb +16 -0
- data/lib/shamu/features/conditions/percentage.rb +44 -0
- data/lib/shamu/features/conditions/proc.rb +54 -0
- data/lib/shamu/features/conditions/roles.rb +23 -0
- data/lib/shamu/features/conditions/schedule_at.rb +27 -0
- data/lib/shamu/features/conditions.rb +18 -0
- data/lib/shamu/features/config_service.rb +10 -0
- data/lib/shamu/features/context.rb +80 -0
- data/lib/shamu/features/env_store.rb +88 -0
- data/lib/shamu/features/errors.rb +29 -0
- data/lib/shamu/features/features_service.rb +168 -0
- data/lib/shamu/features/list_scope.rb +30 -0
- data/lib/shamu/features/selector.rb +50 -0
- data/lib/shamu/features/support.rb +51 -0
- data/lib/shamu/features/toggle.rb +149 -0
- data/lib/shamu/features/toggle_codec.rb +69 -0
- data/lib/shamu/features.rb +16 -0
- data/lib/shamu/locale/en.yml +22 -2
- data/lib/shamu/logger.rb +13 -0
- data/lib/shamu/rack/README.md +0 -0
- data/lib/shamu/rack/cookies.rb +115 -0
- data/lib/shamu/rack/cookies_middleware.rb +26 -0
- data/lib/shamu/rack/query_params.rb +41 -0
- data/lib/shamu/rack/query_params_middleware.rb +24 -0
- data/lib/shamu/rack.rb +12 -0
- data/lib/shamu/rails/controller.rb +131 -0
- data/lib/shamu/rails/entity.rb +168 -0
- data/lib/shamu/rails/features.rb +13 -0
- data/lib/shamu/rails/railtie.rb +30 -0
- data/lib/shamu/rails.rb +10 -0
- data/lib/shamu/rspec/matchers.rb +44 -0
- data/lib/shamu/rspec.rb +1 -0
- data/lib/shamu/security/README.md +0 -0
- data/lib/shamu/security/active_record_policy.rb +106 -0
- data/lib/shamu/security/error.rb +65 -0
- data/lib/shamu/security/hashed_value.rb +71 -0
- data/lib/shamu/security/no_policy.rb +15 -0
- data/lib/shamu/security/policy.rb +289 -0
- data/lib/shamu/security/policy_refinement.rb +50 -0
- data/lib/shamu/security/policy_rule.rb +59 -0
- data/lib/shamu/security/principal.rb +72 -0
- data/lib/shamu/security/roles.rb +62 -0
- data/lib/shamu/security/roles_service.rb +30 -0
- data/lib/shamu/security/support.rb +83 -0
- data/lib/shamu/security.rb +43 -0
- data/lib/shamu/services/README.md +2 -0
- data/lib/shamu/services/active_record.rb +58 -0
- data/lib/shamu/services/active_record_crud.rb +378 -0
- data/lib/shamu/services/error.rb +24 -0
- data/lib/shamu/services/lazy_association.rb +31 -0
- data/lib/shamu/services/lazy_transform.rb +97 -0
- data/lib/shamu/services/request.rb +122 -0
- data/lib/shamu/services/request_support.rb +124 -0
- data/lib/shamu/services/result.rb +75 -0
- data/lib/shamu/services/service.rb +355 -0
- data/lib/shamu/services.rb +12 -0
- data/lib/shamu/sessions/README.md +2 -0
- data/lib/shamu/sessions/cookie_store.rb +79 -0
- data/lib/shamu/sessions/session_store.rb +42 -0
- data/lib/shamu/sessions.rb +8 -0
- data/lib/shamu/to_bool_extension.rb +57 -0
- data/lib/shamu/to_model_id_extension.rb +50 -0
- data/lib/shamu/version.rb +10 -4
- data/lib/shamu.rb +18 -6
- data/shamu.gemspec +21 -10
- data/spec/internal/README.md +4 -0
- data/spec/internal/config/database.yml +3 -0
- data/spec/internal/config/routes.rb +3 -0
- data/spec/internal/db/schema.rb +3 -0
- data/spec/internal/log/.gitignore +1 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/lib/shamu/active_record_support.rb +32 -0
- data/spec/lib/shamu/attributes/assignment_spec.rb +129 -0
- data/spec/lib/shamu/attributes/equality_spec.rb +63 -0
- data/spec/lib/shamu/attributes/fluid_assignment_spec.rb +31 -0
- data/spec/lib/shamu/attributes/validation_spec.rb +53 -0
- data/spec/lib/shamu/attributes_spec.rb +331 -0
- data/spec/lib/shamu/auditing/logging_auditing_service_spec.rb +18 -0
- data/spec/lib/shamu/auditing/support_spec.rb +41 -0
- data/spec/lib/shamu/entities/active_record_soft_destroy_spec.rb +82 -0
- data/spec/lib/shamu/entities/active_record_spec.rb +66 -0
- data/spec/lib/shamu/entities/entity_path_spec.rb +40 -0
- data/spec/lib/shamu/entities/entity_spec.rb +56 -0
- data/spec/lib/shamu/entities/identity_cache_spec.rb +69 -0
- data/spec/lib/shamu/entities/list_scope/dates_spec.rb +47 -0
- data/spec/lib/shamu/entities/list_scope/paging_spec.rb +41 -0
- data/spec/lib/shamu/entities/list_scope/scoped_paging_spec.rb +40 -0
- data/spec/lib/shamu/entities/list_scope/sorting_spec.rb +59 -0
- data/spec/lib/shamu/entities/list_scope_spec.rb +127 -0
- data/spec/lib/shamu/entities/list_spec.rb +60 -0
- data/spec/lib/shamu/entities/null_entity_spec.rb +94 -0
- data/spec/lib/shamu/events/active_record/migration_spec.rb +11 -0
- data/spec/lib/shamu/events/active_record/service_spec.rb +139 -0
- data/spec/lib/shamu/events/events_service_spec.rb +57 -0
- data/spec/lib/shamu/events/in_memory/async_service_spec.rb +37 -0
- data/spec/lib/shamu/events/in_memory/service_spec.rb +36 -0
- data/spec/lib/shamu/events/message_spec.rb +7 -0
- data/spec/lib/shamu/events/support_spec.rb +44 -0
- data/spec/lib/shamu/features/conditions/condition_spec.rb +8 -0
- data/spec/lib/shamu/features/conditions/env_spec.rb +29 -0
- data/spec/lib/shamu/features/conditions/hosts_spec.rb +21 -0
- data/spec/lib/shamu/features/conditions/matching_spec.rb +23 -0
- data/spec/lib/shamu/features/conditions/percentage_spec.rb +71 -0
- data/spec/lib/shamu/features/conditions/proc_spec.rb +28 -0
- data/spec/lib/shamu/features/env_store_spec.rb +48 -0
- data/spec/lib/shamu/features/features.yml +34 -0
- data/spec/lib/shamu/features/features_service_spec.rb +109 -0
- data/spec/lib/shamu/features/secondary.yml +5 -0
- data/spec/lib/shamu/features/selector_spec.rb +17 -0
- data/spec/lib/shamu/features/support_spec.rb +45 -0
- data/spec/lib/shamu/features/toggle_codec_spec.rb +28 -0
- data/spec/lib/shamu/features/toggle_spec.rb +42 -0
- data/spec/lib/shamu/rack/cookies_middleware_spec.rb +33 -0
- data/spec/lib/shamu/rack/cookies_spec.rb +43 -0
- data/spec/lib/shamu/rack/query_params_middleware_spec.rb +33 -0
- data/spec/lib/shamu/rack/query_params_spec.rb +23 -0
- data/spec/lib/shamu/rails/controller_spec.rb +74 -0
- data/spec/lib/shamu/rails/entity_spec.rb +150 -0
- data/spec/lib/shamu/rails/features.yml +13 -0
- data/spec/lib/shamu/rails/features_spec.rb +45 -0
- data/spec/lib/shamu/security/active_record_policy_spec.rb +38 -0
- data/spec/lib/shamu/security/hashed_value_spec.rb +41 -0
- data/spec/lib/shamu/security/policy_refinement_spec.rb +61 -0
- data/spec/lib/shamu/security/policy_rule_spec.rb +60 -0
- data/spec/lib/shamu/security/policy_spec.rb +158 -0
- data/spec/lib/shamu/security/roles_spec.rb +46 -0
- data/spec/lib/shamu/services/active_record_crud_spec.rb +460 -0
- data/spec/lib/shamu/services/active_record_spec.rb +92 -0
- data/spec/lib/shamu/services/lazy_association_spec.rb +31 -0
- data/spec/lib/shamu/services/lazy_transform_spec.rb +96 -0
- data/spec/lib/shamu/services/request_spec.rb +58 -0
- data/spec/lib/shamu/services/request_support_spec.rb +129 -0
- data/spec/lib/shamu/services/result_spec.rb +37 -0
- data/spec/lib/shamu/services/service_spec.rb +307 -0
- data/spec/lib/shamu/sessions/cookie_store_spec.rb +44 -0
- data/spec/lib/shamu/to_bool_extension_spec.rb +67 -0
- data/spec/lib/shamu/to_model_id_extension_spec.rb +54 -0
- data/spec/rails_helper.rb +13 -0
- data/spec/spec_helper.rb +17 -12
- data/spec/support/active_record.rb +17 -0
- data/spec/support/database.rb +14 -0
- data/spec/support/logger.rb +0 -0
- metadata +383 -9
- data/spec/lib/shamu_spec.rb +0 -5
- /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,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
|
data/lib/shamu/rails.rb
ADDED
@@ -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
|
data/lib/shamu/rspec.rb
ADDED
@@ -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
|