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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
module PamDsl
|
|
2
|
+
# Represents a data retention rule
|
|
3
|
+
class RetentionRule
|
|
4
|
+
attr_reader :model_class, :duration, :field_overrides, :conditions, :deletion_strategy
|
|
5
|
+
|
|
6
|
+
DELETION_STRATEGIES = [:hard_delete, :soft_delete, :anonymize, :archive].freeze
|
|
7
|
+
|
|
8
|
+
def initialize(model_class)
|
|
9
|
+
@model_class = model_class.to_s
|
|
10
|
+
@duration = nil
|
|
11
|
+
@field_overrides = {}
|
|
12
|
+
@conditions = []
|
|
13
|
+
@deletion_strategy = :soft_delete
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Set retention duration
|
|
17
|
+
def keep_for(duration)
|
|
18
|
+
@duration = duration
|
|
19
|
+
self
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Override retention for specific fields
|
|
23
|
+
def field(field_name, duration:)
|
|
24
|
+
@field_overrides[field_name.to_sym] = duration
|
|
25
|
+
self
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Add condition for retention
|
|
29
|
+
def when(&block)
|
|
30
|
+
@conditions << block
|
|
31
|
+
self
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Set deletion strategy
|
|
35
|
+
def on_expiry(strategy)
|
|
36
|
+
strategy_sym = strategy.to_sym
|
|
37
|
+
unless DELETION_STRATEGIES.include?(strategy_sym)
|
|
38
|
+
raise Error, "Invalid deletion strategy: #{strategy}. Must be one of #{DELETION_STRATEGIES.join(', ')}"
|
|
39
|
+
end
|
|
40
|
+
@deletion_strategy = strategy_sym
|
|
41
|
+
self
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Check if retention period has expired for a timestamp
|
|
45
|
+
def expired?(timestamp)
|
|
46
|
+
return false unless @duration
|
|
47
|
+
timestamp < (Time.current - @duration)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Get retention duration for a specific field
|
|
51
|
+
def duration_for_field(field_name)
|
|
52
|
+
@field_overrides[field_name.to_sym] || @duration
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Check if conditions match for given context
|
|
56
|
+
def applies_to?(context)
|
|
57
|
+
return true if @conditions.empty?
|
|
58
|
+
@conditions.all? { |condition| condition.call(context) }
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Container for retention rules
|
|
63
|
+
class RetentionPolicy
|
|
64
|
+
attr_reader :rules, :default_duration
|
|
65
|
+
|
|
66
|
+
def initialize
|
|
67
|
+
@rules = []
|
|
68
|
+
@default_duration = 7.years
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Set default retention duration
|
|
72
|
+
def default(duration)
|
|
73
|
+
@default_duration = duration
|
|
74
|
+
self
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# Define retention rule for a model
|
|
78
|
+
def for_model(model_class, &block)
|
|
79
|
+
rule = RetentionRule.new(model_class)
|
|
80
|
+
rule.instance_eval(&block) if block_given?
|
|
81
|
+
@rules << rule
|
|
82
|
+
rule
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Get retention rule for a model
|
|
86
|
+
def rule_for(model_class)
|
|
87
|
+
@rules.find { |rule| rule.model_class == model_class.to_s }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Get retention duration for a model and field
|
|
91
|
+
def duration_for(model_class, field_name: nil)
|
|
92
|
+
rule = rule_for(model_class)
|
|
93
|
+
return @default_duration unless rule
|
|
94
|
+
|
|
95
|
+
if field_name
|
|
96
|
+
rule.duration_for_field(field_name) || rule.duration || @default_duration
|
|
97
|
+
else
|
|
98
|
+
rule.duration || @default_duration
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# PAM DSL Privacy Tasks
|
|
4
|
+
#
|
|
5
|
+
# These tasks are automatically loaded in Rails apps that include the pam_dsl gem.
|
|
6
|
+
# Configure via config/initializers/pam_dsl.rb
|
|
7
|
+
|
|
8
|
+
namespace :pam_dsl do
|
|
9
|
+
namespace :report do
|
|
10
|
+
desc "Generate full privacy compliance report"
|
|
11
|
+
task full: :environment do
|
|
12
|
+
reporter = build_reporter
|
|
13
|
+
reporter.full_report
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
desc "Show PAM DSL policy summary"
|
|
17
|
+
task policy: :environment do
|
|
18
|
+
reporter = build_reporter
|
|
19
|
+
reporter.policy_summary
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
desc "Analyze PII in event store (requires Lyra)"
|
|
23
|
+
task pii: :environment do
|
|
24
|
+
reporter = build_reporter
|
|
25
|
+
reporter.pii_analysis
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
desc "Check retention compliance (requires Lyra)"
|
|
29
|
+
task retention: :environment do
|
|
30
|
+
reporter = build_reporter
|
|
31
|
+
reporter.retention_check
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
desc "Show PII access patterns (requires Lyra)"
|
|
35
|
+
task access_patterns: :environment do
|
|
36
|
+
reporter = build_reporter
|
|
37
|
+
reporter.access_patterns
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "Generate GDPR Article 30 report"
|
|
41
|
+
task article_30: :environment do
|
|
42
|
+
reporter = build_reporter
|
|
43
|
+
reporter.article_30_report
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
desc "Export privacy report to JSON"
|
|
47
|
+
task :export, [:output_path] => :environment do |_t, args|
|
|
48
|
+
output_path = args[:output_path] || "tmp/privacy_report_#{Time.current.strftime('%Y%m%d_%H%M%S')}.json"
|
|
49
|
+
reporter = build_reporter
|
|
50
|
+
reporter.export_json(output_path)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
desc "Compare two policies and generate a report"
|
|
54
|
+
task :compare, [:policy1, :policy2, :output_path] => :environment do |_t, args|
|
|
55
|
+
policy1_name = args[:policy1]&.to_sym
|
|
56
|
+
policy2_name = args[:policy2]&.to_sym
|
|
57
|
+
|
|
58
|
+
unless policy1_name && policy2_name
|
|
59
|
+
puts "Usage: rake pam_dsl:report:compare[policy1,policy2,output_path]"
|
|
60
|
+
puts ""
|
|
61
|
+
puts "Available policies:"
|
|
62
|
+
PamDsl.registry.policies.keys.each { |name| puts " - #{name}" }
|
|
63
|
+
abort
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
output_path = args[:output_path] || "reports/policy_comparison.md"
|
|
67
|
+
|
|
68
|
+
begin
|
|
69
|
+
comparator = PamDsl::PolicyComparator.new(policy1_name, policy2_name)
|
|
70
|
+
comparator.generate_report(output_path: output_path)
|
|
71
|
+
puts "Comparison report written to: #{output_path}"
|
|
72
|
+
rescue PamDsl::PolicyNotFoundError => e
|
|
73
|
+
abort "Error: #{e.message}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def build_reporter
|
|
78
|
+
config = Rails.application.config.pam_dsl
|
|
79
|
+
|
|
80
|
+
policy_name = config.default_policy
|
|
81
|
+
unless policy_name
|
|
82
|
+
# Try to find any defined policy
|
|
83
|
+
policy_name = PamDsl.registry.policies.keys.first
|
|
84
|
+
if policy_name
|
|
85
|
+
puts "Using policy: #{policy_name}"
|
|
86
|
+
else
|
|
87
|
+
abort "No PAM DSL policy found. Define one or set config.pam_dsl.default_policy"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Try to get Lyra's event store if available
|
|
92
|
+
event_store = nil
|
|
93
|
+
if defined?(Lyra) && Lyra.respond_to?(:event_store)
|
|
94
|
+
event_store = Lyra.event_store
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
PamDsl::Reporter.new(
|
|
98
|
+
policy_name,
|
|
99
|
+
organization: config.organization,
|
|
100
|
+
dpo_contact: config.dpo_contact,
|
|
101
|
+
event_store: event_store
|
|
102
|
+
)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
namespace :generate do
|
|
107
|
+
desc "Generate a PAM DSL policy file with sensible defaults"
|
|
108
|
+
task :policy, [:name] => :environment do |_t, args|
|
|
109
|
+
name = args[:name] || "application"
|
|
110
|
+
generator = PamDsl::PolicyGenerator.new(name)
|
|
111
|
+
generator.generate
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
desc "Generate policy from existing ActiveRecord models"
|
|
115
|
+
task :from_models, [:name] => :environment do |_t, args|
|
|
116
|
+
name = args[:name] || "application"
|
|
117
|
+
generator = PamDsl::PolicyGenerator.new(name)
|
|
118
|
+
generator.generate_from_models
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# Shorthand aliases
|
|
124
|
+
namespace :privacy do
|
|
125
|
+
desc "Generate full privacy compliance report (alias for pam_dsl:report:full)"
|
|
126
|
+
task report: "pam_dsl:report:full"
|
|
127
|
+
|
|
128
|
+
desc "Show policy summary (alias for pam_dsl:report:policy)"
|
|
129
|
+
task policy: "pam_dsl:report:policy"
|
|
130
|
+
|
|
131
|
+
desc "Check retention compliance (alias for pam_dsl:report:retention)"
|
|
132
|
+
task retention: "pam_dsl:report:retention"
|
|
133
|
+
|
|
134
|
+
desc "Generate Article 30 report (alias for pam_dsl:report:article_30)"
|
|
135
|
+
task article_30: "pam_dsl:report:article_30"
|
|
136
|
+
|
|
137
|
+
desc "Export report to JSON (alias for pam_dsl:report:export)"
|
|
138
|
+
task :export, [:output_path] => "pam_dsl:report:export"
|
|
139
|
+
end
|
data/lib/pam_dsl.rb
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
require "active_support"
|
|
2
|
+
require "active_support/core_ext"
|
|
3
|
+
|
|
4
|
+
require_relative "pam_dsl/version"
|
|
5
|
+
require_relative "pam_dsl/pii_detector"
|
|
6
|
+
require_relative "pam_dsl/pii_masker"
|
|
7
|
+
require_relative "pam_dsl/gdpr_compliance"
|
|
8
|
+
require_relative "pam_dsl/field"
|
|
9
|
+
require_relative "pam_dsl/purpose"
|
|
10
|
+
require_relative "pam_dsl/retention"
|
|
11
|
+
require_relative "pam_dsl/consent"
|
|
12
|
+
require_relative "pam_dsl/policy"
|
|
13
|
+
require_relative "pam_dsl/registry"
|
|
14
|
+
require_relative "pam_dsl/reporter"
|
|
15
|
+
require_relative "pam_dsl/policy_generator"
|
|
16
|
+
require_relative "pam_dsl/policy_comparator"
|
|
17
|
+
|
|
18
|
+
# Load Rails integration if Rails is available
|
|
19
|
+
require_relative "pam_dsl/railtie" if defined?(Rails::Railtie)
|
|
20
|
+
|
|
21
|
+
module PamDsl
|
|
22
|
+
class Error < StandardError; end
|
|
23
|
+
class PolicyNotFoundError < Error; end
|
|
24
|
+
class InvalidFieldError < Error; end
|
|
25
|
+
class ConsentRequiredError < Error; end
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
# Rails configuration (set by Railtie)
|
|
29
|
+
attr_accessor :rails_config
|
|
30
|
+
|
|
31
|
+
# Global registry for policies
|
|
32
|
+
def registry
|
|
33
|
+
@registry ||= Registry.new
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Define a privacy policy using block DSL
|
|
37
|
+
def define_policy(name, &block)
|
|
38
|
+
policy = Policy.new(name)
|
|
39
|
+
policy.instance_eval(&block)
|
|
40
|
+
registry.register(name, policy)
|
|
41
|
+
policy
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Get a defined policy
|
|
45
|
+
def policy(name)
|
|
46
|
+
registry.get(name) || raise(PolicyNotFoundError, "Policy '#{name}' not found")
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Reset all policies (useful for testing)
|
|
50
|
+
def reset!
|
|
51
|
+
@registry = Registry.new
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Convenience method to create a reporter
|
|
55
|
+
def reporter(policy_name = nil, **options)
|
|
56
|
+
policy_name ||= rails_config&.default_policy || registry.policies.keys.first
|
|
57
|
+
raise PolicyNotFoundError, "No policy defined" unless policy_name
|
|
58
|
+
|
|
59
|
+
Reporter.new(
|
|
60
|
+
policy_name,
|
|
61
|
+
organization: options[:organization] || rails_config&.organization,
|
|
62
|
+
dpo_contact: options[:dpo_contact] || rails_config&.dpo_contact,
|
|
63
|
+
event_store: options[:event_store]
|
|
64
|
+
)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: orfeas_pam_dsl
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.6.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Michail Pantelelis
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: activesupport
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '6.0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '6.0'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: minitest
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '5.0'
|
|
33
|
+
type: :development
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '5.0'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: minitest-reporters
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.5'
|
|
47
|
+
type: :development
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.5'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: rake
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '13.0'
|
|
61
|
+
type: :development
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '13.0'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: simplecov
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '0.22'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0.22'
|
|
82
|
+
description: A declarative DSL for defining privacy policies, PII fields, consent
|
|
83
|
+
requirements, and retention rules using the Privacy Attribute Matrix (PAM) model
|
|
84
|
+
for privacy-aware event monitoring. Part of the ORFEAS (Object-Relational to Event-Sourcing
|
|
85
|
+
Architecture) framework.
|
|
86
|
+
email:
|
|
87
|
+
- mpantel@aegean.gr
|
|
88
|
+
executables: []
|
|
89
|
+
extensions: []
|
|
90
|
+
extra_rdoc_files: []
|
|
91
|
+
files:
|
|
92
|
+
- CHANGELOG.md
|
|
93
|
+
- MIT-LICENSE
|
|
94
|
+
- README.md
|
|
95
|
+
- Rakefile
|
|
96
|
+
- lib/pam_dsl.rb
|
|
97
|
+
- lib/pam_dsl/consent.rb
|
|
98
|
+
- lib/pam_dsl/field.rb
|
|
99
|
+
- lib/pam_dsl/gdpr_compliance.rb
|
|
100
|
+
- lib/pam_dsl/pii_detector.rb
|
|
101
|
+
- lib/pam_dsl/pii_masker.rb
|
|
102
|
+
- lib/pam_dsl/policy.rb
|
|
103
|
+
- lib/pam_dsl/policy_comparator.rb
|
|
104
|
+
- lib/pam_dsl/policy_generator.rb
|
|
105
|
+
- lib/pam_dsl/purpose.rb
|
|
106
|
+
- lib/pam_dsl/railtie.rb
|
|
107
|
+
- lib/pam_dsl/registry.rb
|
|
108
|
+
- lib/pam_dsl/reporter.rb
|
|
109
|
+
- lib/pam_dsl/retention.rb
|
|
110
|
+
- lib/pam_dsl/tasks/privacy.rake
|
|
111
|
+
- lib/pam_dsl/version.rb
|
|
112
|
+
homepage: https://github.com/mpantel/lyra-engine/gems/pam-dsl
|
|
113
|
+
licenses:
|
|
114
|
+
- MIT
|
|
115
|
+
metadata:
|
|
116
|
+
homepage_uri: https://github.com/mpantel/lyra-engine/gems/pam-dsl
|
|
117
|
+
source_code_uri: https://github.com/mpantel/lyra-engine/gems/pam-dsl/tree/main/gems/pam_dsl
|
|
118
|
+
changelog_uri: https://github.com/mpantel/lyra-engine/gems/pam-dsl/blob/main/gems/pam_dsl/CHANGELOG.md
|
|
119
|
+
rdoc_options: []
|
|
120
|
+
require_paths:
|
|
121
|
+
- lib
|
|
122
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
123
|
+
requirements:
|
|
124
|
+
- - ">="
|
|
125
|
+
- !ruby/object:Gem::Version
|
|
126
|
+
version: '0'
|
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
128
|
+
requirements:
|
|
129
|
+
- - ">="
|
|
130
|
+
- !ruby/object:Gem::Version
|
|
131
|
+
version: '0'
|
|
132
|
+
requirements: []
|
|
133
|
+
rubygems_version: 4.0.3
|
|
134
|
+
specification_version: 4
|
|
135
|
+
summary: Privacy Attribute Matrix (PAM) DSL for ORFEAS Framework
|
|
136
|
+
test_files: []
|