Module: Legion::Phi::AccessLog

Defined in:
lib/legion/phi/access_log.rb

Constant Summary collapse

AUDIT_EVENT_TYPE =
'phi_access'

Class Method Summary collapse

Class Method Details

.build_entry(actor:, resource:, action:, phi_fields:, reason:) ⇒ Object



38
39
40
41
42
43
44
45
46
47
# File 'lib/legion/phi/access_log.rb', line 38

def build_entry(actor:, resource:, action:, phi_fields:, reason:)
  {
    actor:      actor.to_s,
    resource:   resource.to_s,
    action:     action.to_s,
    phi_fields: Array(phi_fields).map(&:to_s),
    reason:     reason&.to_s,
    timestamp:  Time.now.utc.iso8601
  }
end

.emit_warning(message) ⇒ Object



99
100
101
102
103
# File 'lib/legion/phi/access_log.rb', line 99

def emit_warning(message)
  Legion::Logging.warn(message) if defined?(Legion::Logging)
rescue NoMethodError
  Kernel.warn(message)
end

.format_detail(entry) ⇒ Object



105
106
107
# File 'lib/legion/phi/access_log.rb', line 105

def format_detail(entry)
  "fields=#{entry[:phi_fields].join(',')};reason=#{entry[:reason]}"
end

.log_access(actor:, resource:, action:, phi_fields:, reason: nil) ⇒ Object

Logs PHI access to the audit trail. Returns true on success, false on failure.



11
12
13
14
15
16
17
18
19
# File 'lib/legion/phi/access_log.rb', line 11

def log_access(actor:, resource:, action:, phi_fields:, reason: nil)
  entry = build_entry(actor: actor, resource: resource, action: action,
                      phi_fields: phi_fields, reason: reason)
  persist(entry)
  true
rescue StandardError => e
  emit_warning("PHI access log failed: #{e.message}")
  false
end

.log_access!(actor:, resource:, action:, phi_fields:, reason: nil) ⇒ Object

Same as log_access but raises on failure.



22
23
24
25
26
27
# File 'lib/legion/phi/access_log.rb', line 22

def log_access!(actor:, resource:, action:, phi_fields:, reason: nil)
  entry = build_entry(actor: actor, resource: resource, action: action,
                      phi_fields: phi_fields, reason: reason)
  persist!(entry)
  true
end

.log_to_logger(entry) ⇒ Object



89
90
91
92
93
94
95
96
97
# File 'lib/legion/phi/access_log.rb', line 89

def log_to_logger(entry)
  return unless defined?(Legion::Logging)

  Legion::Logging.info(
    "[PHI ACCESS] actor=#{entry[:actor]} resource=#{entry[:resource]} " \
    "action=#{entry[:action]} fields=#{entry[:phi_fields].join(',')} " \
    "reason=#{entry[:reason]} at=#{entry[:timestamp]}"
  )
end

.persist(entry) ⇒ Object



49
50
51
52
53
54
55
# File 'lib/legion/phi/access_log.rb', line 49

def persist(entry)
  if defined?(Legion::Audit)
    record_via_audit(entry)
  else
    log_to_logger(entry)
  end
end

.persist!(entry) ⇒ Object



57
58
59
60
61
62
63
# File 'lib/legion/phi/access_log.rb', line 57

def persist!(entry)
  if defined?(Legion::Audit)
    record_via_audit!(entry)
  else
    log_to_logger(entry)
  end
end

.query_in_memoryObject



118
119
120
# File 'lib/legion/phi/access_log.rb', line 118

def query_in_memory(**)
  []
end

.query_via_audit(resource:, limit:) ⇒ Object



109
110
111
112
113
114
115
116
# File 'lib/legion/phi/access_log.rb', line 109

def query_via_audit(resource:, limit:)
  return [] unless defined?(Legion::Data::Model::AuditLog)

  Legion::Audit.recent(limit: limit, resource: resource, event_type: AUDIT_EVENT_TYPE)
rescue StandardError => e
  Legion::Logging.warn "Phi::AccessLog#query_via_audit failed for resource=#{resource}: #{e.message}" if defined?(Legion::Logging)
  []
end

.recent_access(resource:, limit: 100) ⇒ Object

Query recent PHI access records for a given resource.



30
31
32
33
34
35
36
# File 'lib/legion/phi/access_log.rb', line 30

def recent_access(resource:, limit: 100)
  if defined?(Legion::Audit)
    query_via_audit(resource: resource, limit: limit)
  else
    query_in_memory(resource: resource, limit: limit)
  end
end

.record_via_audit(entry) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/legion/phi/access_log.rb', line 65

def record_via_audit(entry)
  Legion::Audit.record(
    event_type:   AUDIT_EVENT_TYPE,
    principal_id: entry[:actor],
    action:       entry[:action],
    resource:     entry[:resource],
    source:       'phi',
    detail:       format_detail(entry)
  )
rescue StandardError => e
  emit_warning("PHI audit record failed: #{e.message}")
end

.record_via_audit!(entry) ⇒ Object



78
79
80
81
82
83
84
85
86
87
# File 'lib/legion/phi/access_log.rb', line 78

def record_via_audit!(entry)
  Legion::Audit.record(
    event_type:   AUDIT_EVENT_TYPE,
    principal_id: entry[:actor],
    action:       entry[:action],
    resource:     entry[:resource],
    source:       'phi',
    detail:       format_detail(entry)
  )
end