phi_attrs 0.4.2 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +1 -1
- data/lib/phi_attrs/phi_record.rb +80 -48
- data/lib/phi_attrs/rspec.rb +2 -2
- data/lib/phi_attrs/version.rb +1 -1
- data/lib/phi_attrs.rb +13 -12
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 40fc413631692c1e040f09c38e551985342b0555f0268f61d7ffcddf79522f2c
|
|
4
|
+
data.tar.gz: e0354aaf9093ae6125ae8a8d227459c59ce19bfd29b2255c1caaece063bf42ea
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: acea49a6c616147fcee57e5ea929acd4ee1902d21551c21f76b1c485149005cea09acd4cfe9ad5639e661150604e4b4947d56c3897329f181c74d74c33f60236
|
|
7
|
+
data.tar.gz: e60d3558f57e9cb626f13b1d3d14669fba2ca9e15503c51be061f1ae797fe270485129134a54d38ddd7c0a5920d2884c6aaa3a79cb2291255341a205ec59f30a
|
data/README.md
CHANGED
|
@@ -443,7 +443,7 @@ It is recommended to use the provided `docker-compose` environment for developme
|
|
|
443
443
|
|
|
444
444
|
### Tests
|
|
445
445
|
|
|
446
|
-
Tests are written using [RSpec](https://rspec.info/) and are setup to use [Appraisal](https://github.com/thoughtbot/appraisal) to run tests over multiple Rails versions. Supported runtimes are Ruby 3.2+ and Rails 7.0 through 8.
|
|
446
|
+
Tests are written using [RSpec](https://rspec.info/) and are setup to use [Appraisal](https://github.com/thoughtbot/appraisal) to run tests over multiple Rails versions. Supported runtimes are Ruby 3.2+ and Rails 7.0 through 8.1.
|
|
447
447
|
|
|
448
448
|
$ bin/run_tests
|
|
449
449
|
or for individual tests:
|
data/lib/phi_attrs/phi_record.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
# Namespace for classes and modules that handle PHI Attribute Access Logging
|
|
4
4
|
module PhiAttrs
|
|
5
|
-
PhiStackEntry = Struct.new(:phi_access_allowed, :user_id, :reason, :logged, :uuid
|
|
5
|
+
PhiStackEntry = Struct.new(:phi_access_allowed, :user_id, :reason, :logged, :uuid)
|
|
6
6
|
|
|
7
7
|
# Module for extending ActiveRecord models to handle PHI access logging
|
|
8
8
|
# and restrict access to attributes.
|
|
@@ -12,6 +12,14 @@ module PhiAttrs
|
|
|
12
12
|
module PhiRecord
|
|
13
13
|
extend ActiveSupport::Concern
|
|
14
14
|
|
|
15
|
+
# Serializes method wrapping. Wrapping runs from `after_initialize`, so the
|
|
16
|
+
# first instances of a model built concurrently on different threads would
|
|
17
|
+
# otherwise race through the check-then-alias steps below. A thread that
|
|
18
|
+
# loses that race aliases the unwrapped name to the wrapper, and every later
|
|
19
|
+
# call recurses until SystemStackError. A Monitor (not a Mutex) is used so
|
|
20
|
+
# the same thread can take the lock again if building one record builds another.
|
|
21
|
+
PHI_WRAP_LOCK = Monitor.new
|
|
22
|
+
|
|
15
23
|
included do
|
|
16
24
|
class_attribute :__phi_exclude_methods
|
|
17
25
|
class_attribute :__phi_include_methods
|
|
@@ -73,21 +81,21 @@ module PhiAttrs
|
|
|
73
81
|
# Foo.allow_phi!('user@example.com', 'viewing patient record')
|
|
74
82
|
#
|
|
75
83
|
def allow_phi!(user_id = nil, reason = nil)
|
|
76
|
-
raise ArgumentError,
|
|
84
|
+
raise ArgumentError, "block not allowed. use allow_phi with block" if block_given?
|
|
77
85
|
|
|
78
86
|
user_id ||= current_user
|
|
79
87
|
reason ||= i18n_reason
|
|
80
|
-
raise ArgumentError,
|
|
88
|
+
raise ArgumentError, "user_id and reason cannot be blank" if user_id.blank? || reason.blank?
|
|
81
89
|
|
|
82
90
|
user_id = user_id.to_s.gsub(/[\r\n]/, " ")
|
|
83
91
|
reason = reason.to_s.gsub(/[\r\n]/, " ")
|
|
84
92
|
uuid = SecureRandom.uuid
|
|
85
93
|
__phi_stack.push(PhiStackEntry.new(
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
94
|
+
phi_access_allowed: true,
|
|
95
|
+
user_id: user_id,
|
|
96
|
+
reason: reason,
|
|
97
|
+
uuid: uuid
|
|
98
|
+
))
|
|
91
99
|
|
|
92
100
|
PhiAttrs::Logger.tagged(PHI_ACCESS_LOG_TAG, name, uuid) do
|
|
93
101
|
PhiAttrs::Logger.info("PHI Access Enabled for '#{user_id}': #{reason}")
|
|
@@ -137,12 +145,12 @@ module PhiAttrs
|
|
|
137
145
|
# end
|
|
138
146
|
#
|
|
139
147
|
def get_phi(user_id = nil, reason = nil, allow_only: nil)
|
|
140
|
-
raise ArgumentError,
|
|
148
|
+
raise ArgumentError, "block required" unless block_given?
|
|
141
149
|
|
|
142
150
|
if allow_only.present?
|
|
143
|
-
raise ArgumentError,
|
|
151
|
+
raise ArgumentError, "allow_only must be iterable with each" unless allow_only.respond_to?(:each)
|
|
144
152
|
raise ArgumentError, "allow_only must all be `#{name}` objects" unless allow_only.all? { |t| t.is_a?(self) }
|
|
145
|
-
raise ArgumentError,
|
|
153
|
+
raise ArgumentError, "allow_only must all have `allow_phi!` methods" unless allow_only.all? { |t| t.respond_to?(:allow_phi!) }
|
|
146
154
|
end
|
|
147
155
|
|
|
148
156
|
# Save this so we don't revoke access previously extended outside the block
|
|
@@ -197,7 +205,7 @@ module PhiAttrs
|
|
|
197
205
|
# end
|
|
198
206
|
# # PHI Access Allowed Again
|
|
199
207
|
def disallow_phi
|
|
200
|
-
raise ArgumentError,
|
|
208
|
+
raise ArgumentError, "block required. use disallow_phi! without block" unless block_given?
|
|
201
209
|
|
|
202
210
|
__phi_stack.push(PhiStackEntry.new(phi_access_allowed: false))
|
|
203
211
|
begin
|
|
@@ -213,10 +221,10 @@ module PhiAttrs
|
|
|
213
221
|
# Foo.disallow_phi!
|
|
214
222
|
#
|
|
215
223
|
def disallow_phi!
|
|
216
|
-
raise ArgumentError,
|
|
224
|
+
raise ArgumentError, "block not allowed. use disallow_phi with block" if block_given?
|
|
217
225
|
|
|
218
226
|
uuid = __uuid_string(__phi_stack)
|
|
219
|
-
message = __phi_stack.present? ? "PHI access disabled for #{__user_id_string(__phi_stack)}" :
|
|
227
|
+
message = __phi_stack.present? ? "PHI access disabled for #{__user_id_string(__phi_stack)}" : "PHI access disabled. No class level access was granted."
|
|
220
228
|
|
|
221
229
|
__reset_phi_stack
|
|
222
230
|
|
|
@@ -231,11 +239,11 @@ module PhiAttrs
|
|
|
231
239
|
# Foo.disallow_last_phi!
|
|
232
240
|
#
|
|
233
241
|
def disallow_last_phi!
|
|
234
|
-
raise ArgumentError,
|
|
242
|
+
raise ArgumentError, "block not allowed" if block_given?
|
|
235
243
|
|
|
236
244
|
removed_access = __phi_stack.pop
|
|
237
245
|
uuid = __uuid_string(removed_access)
|
|
238
|
-
message = removed_access.present? ? "PHI access disabled for #{removed_access.user_id}" :
|
|
246
|
+
message = removed_access.present? ? "PHI access disabled for #{removed_access.user_id}" : "PHI access disabled. No class level access was granted."
|
|
239
247
|
|
|
240
248
|
PhiAttrs::Logger.tagged(PHI_ACCESS_LOG_TAG, name, uuid) do
|
|
241
249
|
PhiAttrs::Logger.info(message)
|
|
@@ -269,12 +277,12 @@ module PhiAttrs
|
|
|
269
277
|
|
|
270
278
|
def __user_id_string(access_list)
|
|
271
279
|
access_list ||= []
|
|
272
|
-
access_list.map { |c| "'#{c.user_id}'" }.join(
|
|
280
|
+
access_list.map { |c| "'#{c.user_id}'" }.join(",")
|
|
273
281
|
end
|
|
274
282
|
|
|
275
283
|
def __uuid_string(access_list)
|
|
276
284
|
access_list ||= []
|
|
277
|
-
Array.wrap(access_list).map(&:uuid).join(
|
|
285
|
+
Array.wrap(access_list).map(&:uuid).join(",").presence || "none"
|
|
278
286
|
end
|
|
279
287
|
|
|
280
288
|
def current_user
|
|
@@ -289,7 +297,7 @@ module PhiAttrs
|
|
|
289
297
|
|
|
290
298
|
i18n_path = [PhiAttrs.translation_prefix] + __path_to_controller_and_action(controller, action)
|
|
291
299
|
i18n_path.push(*__path_to_class)
|
|
292
|
-
i18n_key = i18n_path.join(
|
|
300
|
+
i18n_key = i18n_path.join(".")
|
|
293
301
|
|
|
294
302
|
return I18n.t(i18n_key) if I18n.exists?(i18n_key)
|
|
295
303
|
|
|
@@ -299,16 +307,16 @@ module PhiAttrs
|
|
|
299
307
|
end
|
|
300
308
|
|
|
301
309
|
def __path_to_controller_and_action(controller, action)
|
|
302
|
-
module_paths = controller.underscore.split(
|
|
303
|
-
class_name_parts = module_paths.pop.split(
|
|
304
|
-
class_name_parts.pop if class_name_parts[-1] ==
|
|
305
|
-
module_paths.push(class_name_parts.join(
|
|
310
|
+
module_paths = controller.underscore.split("/")
|
|
311
|
+
class_name_parts = module_paths.pop.split("_")
|
|
312
|
+
class_name_parts.pop if class_name_parts[-1] == "controller"
|
|
313
|
+
module_paths.push(class_name_parts.join("_"), action)
|
|
306
314
|
end
|
|
307
315
|
|
|
308
316
|
def __path_to_class
|
|
309
|
-
module_paths = name.underscore.split(
|
|
310
|
-
class_name_parts = module_paths.pop.split(
|
|
311
|
-
module_paths.push(class_name_parts.join(
|
|
317
|
+
module_paths = name.underscore.split("/")
|
|
318
|
+
class_name_parts = module_paths.pop.split("_")
|
|
319
|
+
module_paths.push(class_name_parts.join("_"))
|
|
312
320
|
end
|
|
313
321
|
end
|
|
314
322
|
|
|
@@ -341,21 +349,21 @@ module PhiAttrs
|
|
|
341
349
|
# foo.allow_phi!('user@example.com', 'viewing patient record')
|
|
342
350
|
#
|
|
343
351
|
def allow_phi!(user_id = nil, reason = nil)
|
|
344
|
-
raise ArgumentError,
|
|
352
|
+
raise ArgumentError, "block not allowed. use allow_phi with block" if block_given?
|
|
345
353
|
|
|
346
354
|
user_id ||= self.class.current_user
|
|
347
355
|
reason ||= self.class.i18n_reason
|
|
348
|
-
raise ArgumentError,
|
|
356
|
+
raise ArgumentError, "user_id and reason cannot be blank" if user_id.blank? || reason.blank?
|
|
349
357
|
|
|
350
358
|
user_id = user_id.to_s.gsub(/[\r\n]/, " ")
|
|
351
359
|
reason = reason.to_s.gsub(/[\r\n]/, " ")
|
|
352
360
|
uuid = SecureRandom.uuid
|
|
353
361
|
@__phi_access_stack.push(PhiStackEntry.new(
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
362
|
+
phi_access_allowed: true,
|
|
363
|
+
user_id: user_id,
|
|
364
|
+
reason: reason,
|
|
365
|
+
uuid: uuid
|
|
366
|
+
))
|
|
359
367
|
|
|
360
368
|
PhiAttrs::Logger.tagged(*phi_log_keys, uuid) do
|
|
361
369
|
PhiAttrs::Logger.info("PHI Access Enabled for '#{user_id}': #{reason}")
|
|
@@ -398,7 +406,7 @@ module PhiAttrs
|
|
|
398
406
|
# end
|
|
399
407
|
#
|
|
400
408
|
def get_phi(user_id = nil, reason = nil)
|
|
401
|
-
raise ArgumentError,
|
|
409
|
+
raise ArgumentError, "block required" unless block_given?
|
|
402
410
|
|
|
403
411
|
extended_instances = @__phi_relations_extended.clone
|
|
404
412
|
begin
|
|
@@ -419,7 +427,7 @@ module PhiAttrs
|
|
|
419
427
|
# foo.disallow_phi!
|
|
420
428
|
#
|
|
421
429
|
def disallow_phi!
|
|
422
|
-
raise ArgumentError,
|
|
430
|
+
raise ArgumentError, "block not allowed. use disallow_phi with block" if block_given?
|
|
423
431
|
|
|
424
432
|
removed_access_for_uuid = self.class.__uuid_string(@__phi_access_stack)
|
|
425
433
|
|
|
@@ -429,7 +437,7 @@ module PhiAttrs
|
|
|
429
437
|
revoke_extended_phi!
|
|
430
438
|
@__phi_access_stack = []
|
|
431
439
|
|
|
432
|
-
message = removed_access_for.present? ? "PHI access disabled for #{removed_access_for}" :
|
|
440
|
+
message = removed_access_for.present? ? "PHI access disabled for #{removed_access_for}" : "PHI access disabled. No instance level access was granted."
|
|
433
441
|
PhiAttrs::Logger.info(message)
|
|
434
442
|
end
|
|
435
443
|
end
|
|
@@ -449,7 +457,7 @@ module PhiAttrs
|
|
|
449
457
|
# # PHI Access Disallowed Here
|
|
450
458
|
#
|
|
451
459
|
def disallow_phi
|
|
452
|
-
raise ArgumentError,
|
|
460
|
+
raise ArgumentError, "block required. use disallow_phi! without block" unless block_given?
|
|
453
461
|
|
|
454
462
|
add_disallow_flag!
|
|
455
463
|
add_disallow_flag_to_extended_phi!
|
|
@@ -468,14 +476,14 @@ module PhiAttrs
|
|
|
468
476
|
# foo.disallow_last_phi!
|
|
469
477
|
#
|
|
470
478
|
def disallow_last_phi!(preserve_extensions: false)
|
|
471
|
-
raise ArgumentError,
|
|
479
|
+
raise ArgumentError, "block not allowed" if block_given?
|
|
472
480
|
|
|
473
481
|
removed_access = @__phi_access_stack.pop
|
|
474
482
|
uuid = removed_access&.uuid
|
|
475
483
|
|
|
476
484
|
PhiAttrs::Logger.tagged(*phi_log_keys, uuid) do
|
|
477
485
|
revoke_extended_phi! unless preserve_extensions
|
|
478
|
-
message = removed_access.present? ? "PHI access disabled for #{removed_access.user_id}" :
|
|
486
|
+
message = removed_access.present? ? "PHI access disabled for #{removed_access.user_id}" : "PHI access disabled. No instance level access was granted."
|
|
479
487
|
PhiAttrs::Logger.info(message)
|
|
480
488
|
end
|
|
481
489
|
end
|
|
@@ -486,7 +494,7 @@ module PhiAttrs
|
|
|
486
494
|
# @return [String] the user_id passed in to allow_phi!
|
|
487
495
|
#
|
|
488
496
|
def phi_allowed_by
|
|
489
|
-
return
|
|
497
|
+
return "__new_record__" if new_record?
|
|
490
498
|
|
|
491
499
|
phi_context&.user_id
|
|
492
500
|
end
|
|
@@ -497,7 +505,7 @@ module PhiAttrs
|
|
|
497
505
|
# @return [String] the reason passed in to allow_phi!
|
|
498
506
|
#
|
|
499
507
|
def phi_access_reason
|
|
500
|
-
return
|
|
508
|
+
return "new record" if new_record?
|
|
501
509
|
|
|
502
510
|
phi_context&.reason
|
|
503
511
|
end
|
|
@@ -523,10 +531,10 @@ module PhiAttrs
|
|
|
523
531
|
# end
|
|
524
532
|
#
|
|
525
533
|
def require_phi!
|
|
526
|
-
raise PhiAttrs::Exceptions::PhiAccessException,
|
|
534
|
+
raise PhiAttrs::Exceptions::PhiAccessException, "PHI Access required, please call allow_phi or allow_phi! first" unless phi_allowed?
|
|
527
535
|
end
|
|
528
536
|
|
|
529
|
-
def reload
|
|
537
|
+
def reload(options = nil)
|
|
530
538
|
@__phi_relations_extended.clear
|
|
531
539
|
super
|
|
532
540
|
end
|
|
@@ -605,7 +613,7 @@ module PhiAttrs
|
|
|
605
613
|
def all_phi_allowed_by
|
|
606
614
|
ids = (@__phi_access_stack || []).filter_map { |c| "'#{c.user_id}'" }
|
|
607
615
|
ids.concat(self.class.__phi_stack.filter_map { |c| "'#{c.user_id}'" })
|
|
608
|
-
ids.join(
|
|
616
|
+
ids.join(",")
|
|
609
617
|
end
|
|
610
618
|
|
|
611
619
|
def all_phi_context_logged?
|
|
@@ -657,6 +665,18 @@ module PhiAttrs
|
|
|
657
665
|
end
|
|
658
666
|
return if self.class.__phi_methods_wrapped.include? method_name
|
|
659
667
|
|
|
668
|
+
PHI_WRAP_LOCK.synchronize { phi_wrap_method!(method_name) }
|
|
669
|
+
end
|
|
670
|
+
|
|
671
|
+
# Does the wrapping for `phi_wrap_method`. Must be called with
|
|
672
|
+
# PHI_WRAP_LOCK held.
|
|
673
|
+
#
|
|
674
|
+
# @private
|
|
675
|
+
#
|
|
676
|
+
def phi_wrap_method!(method_name)
|
|
677
|
+
# Another thread may have wrapped this method while we waited for the lock.
|
|
678
|
+
return if self.class.__phi_methods_wrapped.include? method_name
|
|
679
|
+
|
|
660
680
|
wrapped_method = :"__#{method_name}_phi_wrapped"
|
|
661
681
|
unwrapped_method = :"__#{method_name}_phi_unwrapped"
|
|
662
682
|
|
|
@@ -696,6 +716,18 @@ module PhiAttrs
|
|
|
696
716
|
raise NameError, "Undefined relationship in `extend_phi_access`: #{method_name}" unless respond_to?(method_name)
|
|
697
717
|
return if self.class.__phi_methods_to_extend.include? method_name
|
|
698
718
|
|
|
719
|
+
PHI_WRAP_LOCK.synchronize { phi_wrap_extension!(method_name) }
|
|
720
|
+
end
|
|
721
|
+
|
|
722
|
+
# Does the wrapping for `phi_wrap_extension`. Must be called with
|
|
723
|
+
# PHI_WRAP_LOCK held.
|
|
724
|
+
#
|
|
725
|
+
# @private
|
|
726
|
+
#
|
|
727
|
+
def phi_wrap_extension!(method_name)
|
|
728
|
+
# Another thread may have wrapped this method while we waited for the lock.
|
|
729
|
+
return if self.class.__phi_methods_to_extend.include? method_name
|
|
730
|
+
|
|
699
731
|
wrapped_method = wrapped_extended_name(method_name)
|
|
700
732
|
unwrapped_method = unwrapped_extended_name(method_name)
|
|
701
733
|
|
|
@@ -704,7 +736,7 @@ module PhiAttrs
|
|
|
704
736
|
relation = send(unwrapped_method, *args, **kwargs, &block)
|
|
705
737
|
|
|
706
738
|
access_context = phi_context
|
|
707
|
-
if access_context&.phi_access_allowed && relation.present? && relation_klass(relation).
|
|
739
|
+
if access_context&.phi_access_allowed && relation.present? && relation_klass(relation).include?(PhiRecord)
|
|
708
740
|
relations = relation.is_a?(Enumerable) ? relation : [relation]
|
|
709
741
|
relations.each do |r|
|
|
710
742
|
next if @__phi_relations_extended.include?(r) && r.phi_allowed?
|
|
@@ -730,7 +762,7 @@ module PhiAttrs
|
|
|
730
762
|
def revoke_extended_phi!(relations = nil)
|
|
731
763
|
relations ||= @__phi_relations_extended
|
|
732
764
|
relations.each do |relation|
|
|
733
|
-
relation.disallow_last_phi! if relation.present? && relation_klass(relation).
|
|
765
|
+
relation.disallow_last_phi! if relation.present? && relation_klass(relation).include?(PhiRecord)
|
|
734
766
|
end
|
|
735
767
|
@__phi_relations_extended.subtract(relations)
|
|
736
768
|
end
|
|
@@ -739,7 +771,7 @@ module PhiAttrs
|
|
|
739
771
|
def add_disallow_flag_to_extended_phi!(relations = nil)
|
|
740
772
|
relations ||= @__phi_relations_extended
|
|
741
773
|
relations.each do |relation|
|
|
742
|
-
relation.add_disallow_flag! if relation.present? && relation_klass(relation).
|
|
774
|
+
relation.add_disallow_flag! if relation.present? && relation_klass(relation).include?(PhiRecord)
|
|
743
775
|
end
|
|
744
776
|
end
|
|
745
777
|
|
|
@@ -747,7 +779,7 @@ module PhiAttrs
|
|
|
747
779
|
def remove_disallow_flag_from_extended_phi!(relations = nil)
|
|
748
780
|
relations ||= @__phi_relations_extended
|
|
749
781
|
relations.each do |relation|
|
|
750
|
-
relation.remove_disallow_flag! if relation.present? && relation_klass(relation).
|
|
782
|
+
relation.remove_disallow_flag! if relation.present? && relation_klass(relation).include?(PhiRecord)
|
|
751
783
|
end
|
|
752
784
|
end
|
|
753
785
|
|
data/lib/phi_attrs/rspec.rb
CHANGED
|
@@ -27,7 +27,7 @@ RSpec::Matchers.define :allow_phi_access do
|
|
|
27
27
|
@reason = reason
|
|
28
28
|
end
|
|
29
29
|
|
|
30
|
-
# :
|
|
30
|
+
# simplecov:disable
|
|
31
31
|
failure_message do |result|
|
|
32
32
|
msgs = []
|
|
33
33
|
|
|
@@ -41,5 +41,5 @@ RSpec::Matchers.define :allow_phi_access do
|
|
|
41
41
|
failure_message_when_negated do |result|
|
|
42
42
|
"PHI access was allowed by '#{result.phi_allowed_by}', because '#{result.phi_access_reason}'"
|
|
43
43
|
end
|
|
44
|
-
# :
|
|
44
|
+
# simplecov:enable
|
|
45
45
|
end
|
data/lib/phi_attrs/version.rb
CHANGED
data/lib/phi_attrs.rb
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
require
|
|
10
|
-
require
|
|
11
|
-
require
|
|
12
|
-
require
|
|
13
|
-
require
|
|
14
|
-
require
|
|
3
|
+
require "rails"
|
|
4
|
+
require "active_support"
|
|
5
|
+
require "request_store"
|
|
6
|
+
require "securerandom"
|
|
7
|
+
require "monitor"
|
|
8
|
+
|
|
9
|
+
require "phi_attrs/version"
|
|
10
|
+
require "phi_attrs/configure"
|
|
11
|
+
require "phi_attrs/railtie" if defined?(Rails)
|
|
12
|
+
require "phi_attrs/formatter"
|
|
13
|
+
require "phi_attrs/logger"
|
|
14
|
+
require "phi_attrs/exceptions"
|
|
15
|
+
require "phi_attrs/phi_record"
|
|
15
16
|
|
|
16
17
|
module PhiAttrs
|
|
17
18
|
def self.log_phi_access(user, message)
|