json2sql 1.0.7 → 1.0.9

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 44981099a9e736eea37fae8938dbc885a7cf366a394ca46e884ff6abf14ab9a4
4
- data.tar.gz: bb1ebf19a636bf8e28be4d6720afa8e0d90a4f5310625c5b70beaddb7f9276df
3
+ metadata.gz: acb969a8408adbf24bf7794bcc4a6b057c90c0e53d8b57041f01e9b19bc073cc
4
+ data.tar.gz: 961326ecdf490e74c444acc01adbe5edca42b1c1d91b2c68f6fe68a47be14a57
5
5
  SHA512:
6
- metadata.gz: 01f29fa62d0d3a6e3d3f5f1fc3eded2a07db8b6500d3e2f8b72f4e5d37d96360ed719f3d9e01be88516eb8cf42297a39b97fee6ddff62eeaae48a190dfb7dee4
7
- data.tar.gz: 184b5d2e228ae4786d84d53f7eb26090bdbc2a539dced777bdb9bf5c2d0aeb95e1335922c7dd2530b8eb7a54421c2b3d779f49ffb1b053cf44a53e8f1cad7584
6
+ metadata.gz: a7c7d34b754e4d8b823610837dc4dfb165cb34e315cbf46b965b9f25ef7292ce5b1424375f0bc4ae397c55493ca518a5b285606914364e6feed878f0d90f17d8
7
+ data.tar.gz: 8cbee6aa534d976a64f2d4b233abc37421940cd8da6a6e1f2c2204ce9a1cc07f400286c697b520e3aa22f49ed8746f683cffc66ca2c2dfa957746d20bd64901e
@@ -0,0 +1,159 @@
1
+ module Json2sql
2
+
3
+ # Sanitizes a query input Hash before passing it to any Runner.
4
+ #
5
+ # Parameters:
6
+ # mode: :allow (default) — only tables listed in `tables:` are accessible.
7
+ # Tables absent from `tables:` are blocked entirely.
8
+ # Empty `tables:` = no restriction.
9
+ # :deny — all tables pass; only listed columns are stripped.
10
+ # tables: Per-table configuration Hash (recursive):
11
+ # { table_name => { columns: [...],
12
+ # children: { child_table => { columns: [...], ... } },
13
+ # parents: { parent_table => { columns: [...], ... } },
14
+ # where: { "and" => { col => val } } } }
15
+ # columns: column list — filtered by `mode` (allowed or denied).
16
+ # nil or absent = no column restriction for that table.
17
+ # children: nested hash of allowed/denied child tables with their own config.
18
+ # nil or absent = no restriction on children.
19
+ # parents: nested hash of allowed/denied parent tables with their own config.
20
+ # nil or absent = no restriction on parents.
21
+ # where: server-side conditions merged into "and". Forced keys overwrite
22
+ # user-supplied values — primary IDOR guard.
23
+ #
24
+ # Usage:
25
+ # policy = Json2sql::InputPolicy.new(
26
+ # mode: :allow,
27
+ # tables: {
28
+ # orders: {
29
+ # columns: %w[id total status],
30
+ # children: { order_items: { columns: %w[id price] } },
31
+ # parents: { users: { columns: %w[id name] } },
32
+ # where: { "and" => { "user_id" => 42 } }
33
+ # }
34
+ # }
35
+ # )
36
+ # safe_input = policy.apply(raw_params)
37
+ # sql = Json2sql::SelectRunner.build(safe_input)
38
+
39
+ class InputPolicy
40
+
41
+ def initialize(mode: :allow, tables: {})
42
+
43
+ @mode = mode
44
+
45
+ @tables = Json2sql.normalize(tables)
46
+ end
47
+
48
+ # Returns a sanitized copy of input ready to pass to any Runner.
49
+ # Runners remain unmodified — they receive only the clean Hash.
50
+
51
+ def apply(input)
52
+
53
+ input = Json2sql.normalize(input)
54
+
55
+ input = filter_tables(input)
56
+
57
+ input.each { |table, params| sanitize_table(params, @tables[table] || {}) }
58
+
59
+ input
60
+ end
61
+
62
+ private
63
+
64
+ def filter_tables(input)
65
+
66
+ return input if @mode == :deny || @tables.empty?
67
+
68
+ input.select { |table, _| @tables.key?(table) }
69
+ end
70
+
71
+ def sanitize_table(params, config)
72
+
73
+ return unless params.is_a?(Hash)
74
+
75
+ filter_columns(params, config)
76
+
77
+ inject_where(params, config)
78
+
79
+ %w[children parents].each do |relation|
80
+
81
+ filter_relations(params, config, relation)
82
+
83
+ next unless params[relation].is_a?(Hash)
84
+
85
+ relation_configs = config[relation].is_a?(Hash) ? config[relation] : {}
86
+
87
+ params[relation].each { |child_table, child_params| sanitize_table(child_params, relation_configs[child_table] || {}) }
88
+ end
89
+ end
90
+
91
+ # Filters children/parents relations using mode.
92
+ # In :allow mode, only relations present as keys in config[relation_key] pass.
93
+ # In :deny mode, relations present as keys in config[relation_key] are removed.
94
+ # If config[relation_key] is absent or not a Hash, relations are untouched.
95
+
96
+ def filter_relations(params, config, relation_key)
97
+
98
+ relations = params[relation_key]
99
+
100
+ return unless relations.is_a?(Hash)
101
+
102
+ relation_config = config[relation_key]
103
+
104
+ return unless relation_config.is_a?(Hash)
105
+
106
+ params[relation_key] = if @mode == :deny
107
+
108
+ relations.reject { |t, _| relation_config.key?(t) }
109
+
110
+ else
111
+
112
+ relations.select { |t, _| relation_config.key?(t) }
113
+
114
+ end
115
+ end
116
+
117
+ # Filters "columns" using mode (:allow or :deny).
118
+ # Handles Array (SELECT) and Hash (INSERT/UPDATE) column formats.
119
+ # Hash entries (function columns) always pass through in :allow mode.
120
+ # If no column list is defined for the table, columns are untouched.
121
+
122
+ def filter_columns(params, config)
123
+
124
+ columns = params["columns"]
125
+
126
+ return unless columns.is_a?(Array) || columns.is_a?(Hash)
127
+
128
+ list = config["columns"]
129
+
130
+ return unless list.is_a?(Array)
131
+
132
+ params["columns"] = if @mode == :deny
133
+
134
+ columns.is_a?(Array) ? columns.reject { |c| list.include?(c) } : columns.reject { |k, _| list.include?(k) }
135
+
136
+ else
137
+
138
+ columns.is_a?(Array) ? columns.select { |c| c.is_a?(Hash) || list.include?(c) } : columns.select { |k, _| list.include?(k) }
139
+
140
+ end
141
+ end
142
+
143
+ # Merges forced "and" conditions into params["and"].
144
+ # Forced keys overwrite user-supplied values for the same column,
145
+ # preventing IDOR (e.g. attacker cannot override user_id).
146
+
147
+ def inject_where(params, config)
148
+
149
+ forced_and = config.dig("where", "and")
150
+
151
+ return unless forced_and.is_a?(Hash)
152
+
153
+ params["and"] ||= {}
154
+
155
+ params["and"].merge!(forced_and)
156
+ end
157
+
158
+ end
159
+ end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Json2sql
4
- VERSION = "1.0.7"
4
+ VERSION = "1.0.9"
5
5
  end
data/lib/json2sql.rb CHANGED
@@ -10,6 +10,7 @@ require_relative "json2sql/update_model"
10
10
  require_relative "json2sql/update_runner"
11
11
  require_relative "json2sql/delete_model"
12
12
  require_relative "json2sql/delete_runner"
13
+ require_relative "json2sql/input_policy"
13
14
 
14
15
  # Json2sql — SQL builder that generates MySQL/MariaDB query strings from
15
16
  # plain Ruby Hashes (or parsed JSON).
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: json2sql
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.7
4
+ version: 1.0.9
5
5
  platform: ruby
6
6
  authors:
7
7
  - Tiago da Silva
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-05-03 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: minitest
@@ -50,6 +49,7 @@ files:
50
49
  - lib/json2sql.rb
51
50
  - lib/json2sql/delete_model.rb
52
51
  - lib/json2sql/delete_runner.rb
52
+ - lib/json2sql/input_policy.rb
53
53
  - lib/json2sql/insert_model.rb
54
54
  - lib/json2sql/insert_runner.rb
55
55
  - lib/json2sql/sanitizer.rb
@@ -66,7 +66,6 @@ licenses:
66
66
  metadata:
67
67
  homepage_uri: https://github.com/tyagoy/json2sql
68
68
  source_code_uri: https://github.com/tyagoy/json2sql
69
- post_install_message:
70
69
  rdoc_options: []
71
70
  require_paths:
72
71
  - lib
@@ -81,8 +80,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
80
  - !ruby/object:Gem::Version
82
81
  version: '0'
83
82
  requirements: []
84
- rubygems_version: 3.4.10
85
- signing_key:
83
+ rubygems_version: 3.6.7
86
84
  specification_version: 4
87
85
  summary: Translates Ruby Hashes (or parsed JSON) into MySQL/MariaDB query strings.
88
86
  test_files: []