orfeas_pam_dsl 0.6.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 +7 -0
- data/CHANGELOG.md +84 -0
- data/MIT-LICENSE +21 -0
- data/README.md +1365 -0
- data/Rakefile +11 -0
- data/lib/pam_dsl/consent.rb +110 -0
- data/lib/pam_dsl/field.rb +76 -0
- data/lib/pam_dsl/gdpr_compliance.rb +560 -0
- data/lib/pam_dsl/pii_detector.rb +442 -0
- data/lib/pam_dsl/pii_masker.rb +121 -0
- data/lib/pam_dsl/policy.rb +175 -0
- data/lib/pam_dsl/policy_comparator.rb +296 -0
- data/lib/pam_dsl/policy_generator.rb +558 -0
- data/lib/pam_dsl/purpose.rb +78 -0
- data/lib/pam_dsl/railtie.rb +25 -0
- data/lib/pam_dsl/registry.rb +50 -0
- data/lib/pam_dsl/reporter.rb +789 -0
- data/lib/pam_dsl/retention.rb +102 -0
- data/lib/pam_dsl/tasks/privacy.rake +139 -0
- data/lib/pam_dsl/version.rb +3 -0
- data/lib/pam_dsl.rb +67 -0
- metadata +136 -0
data/Rakefile
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
module PamDsl
|
|
2
|
+
# Represents a consent requirement
|
|
3
|
+
class ConsentRequirement
|
|
4
|
+
attr_reader :purpose, :required, :granular, :withdrawable, :description, :expires_after
|
|
5
|
+
|
|
6
|
+
def initialize(purpose)
|
|
7
|
+
@purpose = purpose.to_sym
|
|
8
|
+
@required = true
|
|
9
|
+
@granular = false
|
|
10
|
+
@withdrawable = true
|
|
11
|
+
@description = ""
|
|
12
|
+
@expires_after = nil
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Set if consent is required
|
|
16
|
+
def required!(value = true)
|
|
17
|
+
@required = value
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Enable granular consent
|
|
22
|
+
def granular!(value = true)
|
|
23
|
+
@granular = value
|
|
24
|
+
self
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Set if consent is withdrawable
|
|
28
|
+
def withdrawable!(value = true)
|
|
29
|
+
@withdrawable = value
|
|
30
|
+
self
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Set description
|
|
34
|
+
def describe(text)
|
|
35
|
+
@description = text
|
|
36
|
+
self
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Set expiration duration
|
|
40
|
+
def expires_in(duration)
|
|
41
|
+
@expires_after = duration
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Check if consent is required
|
|
46
|
+
def required?
|
|
47
|
+
@required
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if consent is granular
|
|
51
|
+
def granular?
|
|
52
|
+
@granular
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if consent is withdrawable
|
|
56
|
+
def withdrawable?
|
|
57
|
+
@withdrawable
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Check if consent has expired
|
|
61
|
+
def expired?(granted_at)
|
|
62
|
+
return false unless @expires_after
|
|
63
|
+
granted_at < (Time.current - @expires_after)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Container for consent requirements
|
|
68
|
+
class ConsentPolicy
|
|
69
|
+
attr_reader :requirements
|
|
70
|
+
|
|
71
|
+
def initialize
|
|
72
|
+
@requirements = []
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Define consent requirement for a purpose
|
|
76
|
+
def for_purpose(purpose, &block)
|
|
77
|
+
requirement = ConsentRequirement.new(purpose)
|
|
78
|
+
requirement.instance_eval(&block) if block_given?
|
|
79
|
+
@requirements << requirement
|
|
80
|
+
requirement
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Get consent requirement for a purpose
|
|
84
|
+
def requirement_for(purpose)
|
|
85
|
+
@requirements.find { |req| req.purpose == purpose.to_sym }
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Check if consent is required for a purpose
|
|
89
|
+
def required_for?(purpose)
|
|
90
|
+
requirement = requirement_for(purpose)
|
|
91
|
+
requirement ? requirement.required? : false
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Validate consent for a purpose
|
|
95
|
+
def validate!(purpose, granted_at: nil, granted: false)
|
|
96
|
+
requirement = requirement_for(purpose)
|
|
97
|
+
return true unless requirement&.required?
|
|
98
|
+
|
|
99
|
+
unless granted
|
|
100
|
+
raise ConsentRequiredError, "Consent required for purpose: #{purpose}"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
if granted_at && requirement.expired?(granted_at)
|
|
104
|
+
raise ConsentRequiredError, "Consent expired for purpose: #{purpose}"
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
true
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
module PamDsl
|
|
2
|
+
# Represents a PII field definition
|
|
3
|
+
class Field
|
|
4
|
+
attr_reader :name, :type, :sensitivity, :purposes, :transformations, :metadata
|
|
5
|
+
|
|
6
|
+
SENSITIVITY_LEVELS = [:public, :internal, :confidential, :restricted].freeze
|
|
7
|
+
|
|
8
|
+
PII_TYPES = [
|
|
9
|
+
:email, :name, :phone, :address, :ssn, :date_of_birth,
|
|
10
|
+
:ip_address, :credit_card, :financial, :health, :biometric,
|
|
11
|
+
:location, :identifier, :credential, :token, :payment_token, :custom
|
|
12
|
+
].freeze
|
|
13
|
+
|
|
14
|
+
def initialize(name, type:, sensitivity: :internal)
|
|
15
|
+
@name = name.to_sym
|
|
16
|
+
@type = type.to_sym
|
|
17
|
+
@sensitivity = sensitivity.to_sym
|
|
18
|
+
@purposes = []
|
|
19
|
+
@transformations = {}
|
|
20
|
+
@metadata = {}
|
|
21
|
+
|
|
22
|
+
validate!
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Define allowed purposes for this field
|
|
26
|
+
def allow_for(*purpose_names)
|
|
27
|
+
@purposes.concat(purpose_names.map(&:to_sym))
|
|
28
|
+
self
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Define transformation rules for different contexts
|
|
32
|
+
def transform(context, &block)
|
|
33
|
+
@transformations[context.to_sym] = block
|
|
34
|
+
self
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Add metadata
|
|
38
|
+
def meta(key, value)
|
|
39
|
+
@metadata[key] = value
|
|
40
|
+
self
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Check if field is allowed for a purpose
|
|
44
|
+
def allowed_for?(purpose)
|
|
45
|
+
@purposes.empty? || @purposes.include?(purpose.to_sym)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Apply transformation if defined
|
|
49
|
+
def apply_transformation(context, value)
|
|
50
|
+
transformation = @transformations[context.to_sym]
|
|
51
|
+
transformation ? transformation.call(value) : value
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Check if field is sensitive
|
|
55
|
+
def sensitive?
|
|
56
|
+
[:confidential, :restricted].include?(@sensitivity)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Check if field is highly restricted
|
|
60
|
+
def restricted?
|
|
61
|
+
@sensitivity == :restricted
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def validate!
|
|
67
|
+
unless SENSITIVITY_LEVELS.include?(@sensitivity)
|
|
68
|
+
raise InvalidFieldError, "Invalid sensitivity level: #{@sensitivity}. Must be one of #{SENSITIVITY_LEVELS.join(', ')}"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
unless PII_TYPES.include?(@type)
|
|
72
|
+
raise InvalidFieldError, "Invalid PII type: #{@type}. Must be one of #{PII_TYPES.join(', ')}"
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|