Class: Lyra::Privacy::PolicyIntegration

Inherits:
Object
  • Object
show all
Defined in:
lib/lyra/privacy/policy_integration.rb

Overview

Integration layer between Lyra and PAM DSL

When PAM DSL is available, combines policy-defined fields with pattern-based PIIDetector for comprehensive PII detection.

Examples:

With policy and detector fallback (default)

integration = PolicyIntegration.new(:my_policy)
integration.detect_pii(attrs)  # Uses policy + detector

Policy-only mode

integration = PolicyIntegration.new(:my_policy, use_detector: false)
integration.detect_pii(attrs)  # Only fields defined in policy

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(policy_name, use_detector: true) ⇒ PolicyIntegration

Returns a new instance of PolicyIntegration.



19
20
21
22
23
24
25
26
27
28
# File 'lib/lyra/privacy/policy_integration.rb', line 19

def initialize(policy_name, use_detector: true)
  @use_detector = use_detector
  @policy = PamDsl.policy(policy_name)
rescue PamDsl::PolicyNotFoundError
  @policy = nil
rescue NameError
  # PAM DSL not available
  @policy = nil
  @use_detector = false
end

Instance Attribute Details

#policy ⇒ Object (readonly)

Returns the value of attribute policy.



17
18
19
# File 'lib/lyra/privacy/policy_integration.rb', line 17

def policy
  @policy
end

#use_detector ⇒ Object (readonly)

Returns the value of attribute use_detector.



17
18
19
# File 'lib/lyra/privacy/policy_integration.rb', line 17

def use_detector
  @use_detector
end

Instance Method Details

#allowed?(field_name, purpose) ⇒ Boolean

Check if field is allowed for purpose

Returns:

  • (Boolean)


159
160
161
162
# File 'lib/lyra/privacy/policy_integration.rb', line 159

def allowed?(field_name, purpose)
  return true unless policy_loaded?
  @policy.allowed?(field_name, purpose)
end

#allowed_purposes(field_name) ⇒ Object

Get allowed purposes for a field



147
148
149
150
151
152
153
154
155
156
# File 'lib/lyra/privacy/policy_integration.rb', line 147

def allowed_purposes(field_name)
  return [] unless policy_loaded?

  begin
    field = @policy.get_field(field_name)
    field.purposes
  rescue PamDsl::InvalidFieldError
    []
  end
end

Check if consent is required for purpose

Returns:

  • (Boolean)


135
136
137
138
139
140
141
142
143
144
# File 'lib/lyra/privacy/policy_integration.rb', line 135

def consent_required?(purpose)
  return false unless policy_loaded?

  begin
    purpose_obj = @policy.get_purpose(purpose)
    purpose_obj.requires_consent?
  rescue PamDsl::Error
    false
  end
end

#detect_pii(attributes) ⇒ Hash

Get PII fields from attributes using policy and/or detector

Priority:

  1. Fields defined in policy (explicit configuration)
  2. Fields detected by PIIDetector (pattern-based, if use_detector: true)

Parameters:

  • attributes (Hash) —

    Key-value pairs to check for PII

Returns:

  • (Hash) —

    PII fields with type, value, sensitivity



61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/lyra/privacy/policy_integration.rb', line 61

def detect_pii(attributes)
  return {} unless pam_dsl_available?

  pii_fields = {}

  attributes.each do |key, value|
    field_name = key.to_sym

    # Try policy first
    if policy_loaded?
      begin
        field = @policy.get_field(field_name)
        pii_fields[key] = {
          type: field.type,
          value: value,
          sensitive: field.sensitive?,
          sensitivity: field.sensitivity,
          source: :policy
        }
        next
      rescue PamDsl::InvalidFieldError
        # Field not in policy, try detector below
      end
    end

    # Fall back to PIIDetector if enabled
    if @use_detector
      detected = PamDsl::PIIDetector.detect({ key => value })
      if detected[key]
        pii_fields[key] = detected[key].merge(source: :detector)
      end
    end
  end

  pii_fields
end

#mask_pii(field_name, value, context = :display) ⇒ Object

Mask PII using policy transformations or PIIDetector

Parameters:

  • field_name (Symbol, String) —

    Field name

  • value (Object) —

    Value to mask

  • context (Symbol) (defaults to: :display) —

    Transformation context (default: :display)

Returns:

  • (Object) —

    Masked value, or original if no masking available



105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
# File 'lib/lyra/privacy/policy_integration.rb', line 105

def mask_pii(field_name, value, context = :display)
  return value unless pam_dsl_available?

  # Try policy transformation first
  if policy_loaded?
    begin
      field = @policy.get_field(field_name)
      return field.apply_transformation(context, value)
    rescue PamDsl::InvalidFieldError
      # Field not in policy, try detector below
    end
  end

  # Fall back to PIIDetector masking if enabled
  if @use_detector
    pii_type = PamDsl::PIIDetector.pii_type(field_name)
    return PamDsl::PIIDetector.mask(value, pii_type) if pii_type
  end

  value
end

#metadata ⇒ Object

Get policy metadata



177
178
179
180
# File 'lib/lyra/privacy/policy_integration.rb', line 177

def 
  return {} unless policy_loaded?
  @policy.
end

#pam_dsl_available? ⇒ Boolean

Check if PAM DSL is available

Returns:

  • (Boolean)


31
32
33
# File 'lib/lyra/privacy/policy_integration.rb', line 31

def pam_dsl_available?
  defined?(PamDsl)
end

#policy_loaded? ⇒ Boolean

Check if policy is loaded

Returns:

  • (Boolean)


36
37
38
# File 'lib/lyra/privacy/policy_integration.rb', line 36

def policy_loaded?
  !@policy.nil?
end

#restricted_fields ⇒ Object

Get all restricted fields



171
172
173
174
# File 'lib/lyra/privacy/policy_integration.rb', line 171

def restricted_fields
  return [] unless policy_loaded?
  @policy.restricted_fields.map(&:name)
end

#retention_duration(model_class, field_name: nil) ⇒ Object

Get retention duration for model and field Returns nil if no policy loaded (infinite/manual retention)



129
130
131
132
# File 'lib/lyra/privacy/policy_integration.rb', line 129

def retention_duration(model_class, field_name: nil)
  return nil unless policy_loaded?
  @policy.retention_for(model_class, field_name: field_name)
end

#sensitive_fields ⇒ Object

Get all sensitive fields



165
166
167
168
# File 'lib/lyra/privacy/policy_integration.rb', line 165

def sensitive_fields
  return [] unless policy_loaded?
  @policy.sensitive_fields.map(&:name)
end

#to_h ⇒ Object

Export policy information



183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
# File 'lib/lyra/privacy/policy_integration.rb', line 183

def to_h
  {
    pam_dsl_available: pam_dsl_available?,
    policy_loaded: policy_loaded?,
    use_detector: @use_detector
  }.tap do |info|
    if policy_loaded?
      info.merge!(
        policy_name: @policy.name,
        fields_count: @policy.fields.count,
        purposes_count: @policy.purposes.count,
        sensitive_fields: sensitive_fields,
        restricted_fields: restricted_fields,
        metadata: @policy.
      )
    end
  end
end

#validate_access!(field_names, purpose, consent_status = {}) ⇒ Object

Validate data access for a purpose



41
42
43
44
45
46
47
48
49
50
# File 'lib/lyra/privacy/policy_integration.rb', line 41

def validate_access!(field_names, purpose, consent_status = {})
  return true unless policy_loaded?

  @policy.validate_access!(
    field_names,
    purpose,
    consent_granted: consent_status[:granted] || false,
    consent_granted_at: consent_status[:granted_at]
  )
end