graphql-armory 0.1.0.pre → 0.1.0.pre2

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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +19 -0
  3. data/LICENSE +0 -0
  4. data/MIGRATING.md +0 -0
  5. data/README.md +20 -7
  6. data/lib/graphql/armory/authorization/adapters/cancan.rb +0 -0
  7. data/lib/graphql/armory/authorization/adapters/null.rb +0 -0
  8. data/lib/graphql/armory/authorization/adapters/pundit.rb +0 -0
  9. data/lib/graphql/armory/authorization/adapters/substitute.rb +0 -0
  10. data/lib/graphql/armory/authorization/audit/command.rb +0 -0
  11. data/lib/graphql/armory/authorization/audit/report.rb +0 -0
  12. data/lib/graphql/armory/authorization/audit.rb +53 -11
  13. data/lib/graphql/armory/authorization/authorize.rb +0 -0
  14. data/lib/graphql/armory/authorization/configuration.rb +0 -0
  15. data/lib/graphql/armory/authorization/controls/actor.rb +0 -0
  16. data/lib/graphql/armory/authorization/controls/context.rb +0 -0
  17. data/lib/graphql/armory/authorization/controls/rule.rb +0 -0
  18. data/lib/graphql/armory/authorization/controls/subject.rb +0 -0
  19. data/lib/graphql/armory/authorization/errors.rb +0 -0
  20. data/lib/graphql/armory/authorization/integration/argument.rb +0 -0
  21. data/lib/graphql/armory/authorization/integration/base.rb +3 -1
  22. data/lib/graphql/armory/authorization/integration/field.rb +1 -1
  23. data/lib/graphql/armory/authorization/integration/find_by.rb +0 -0
  24. data/lib/graphql/armory/authorization/integration/input_object.rb +31 -0
  25. data/lib/graphql/armory/authorization/integration/object.rb +0 -0
  26. data/lib/graphql/armory/authorization/integration/resolver.rb +1 -1
  27. data/lib/graphql/armory/authorization/integration/schema.rb +0 -0
  28. data/lib/graphql/armory/authorization/integration.rb +31 -0
  29. data/lib/graphql/armory/authorization/memo.rb +0 -0
  30. data/lib/graphql/armory/authorization/rspec.rb +0 -0
  31. data/lib/graphql/armory/authorization/rule.rb +0 -0
  32. data/lib/graphql/armory/authorization/scope.rb +0 -0
  33. data/lib/graphql/armory/authorization/telemetry.rb +0 -0
  34. data/lib/graphql/armory/authorization.rb +0 -0
  35. data/lib/graphql/armory/version.rb +1 -1
  36. data/lib/graphql/armory.rb +0 -0
  37. data/lib/graphql-armory.rb +0 -0
  38. data/lib/tasks/graphql_armory.rake +0 -0
  39. metadata +6 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9bb288f28bfb93054aa3ff328b791ff7936dfb546d289671650bd19182b56177
4
- data.tar.gz: 44d9b064622b91a9691499d48df611f4b544950fbbbdf8a49d470c7563c41a03
3
+ metadata.gz: 17454310f4c6b683fac81e5fc5e4ea5f810a645319eb083841e42910e7f881b3
4
+ data.tar.gz: cea12c9b171113f1cb3d6df4658676508056bab51335f8e937adabba18e28817
5
5
  SHA512:
6
- metadata.gz: 4bc8f032f4f5305cfe298542ab63a7f0fac65118abb9011ee4af5fc48f7ddf66886b8cb11ac2ae300a296d0facd3a033e20e06daf23462b667bc9017d3dbea51
7
- data.tar.gz: 87c9adeafec6dd55dcb8f3cc597e941afff6bab620d6e38ebcc4a4a9880bdfc8200c9fd926c36f67badfa1520dd61f416f86563a68fc164217e19c218105729e
6
+ metadata.gz: 915b3068e7fecaee1f409b24cbc0b24832053322da848e7a242302c701479ae293d5b735d9d4ab49784b7f950fbfedb909034f07ea90bf877cb815471926e660
7
+ data.tar.gz: 9d4809e01699f91be71abc2b2ac4209417ca5d5b39484f91ba492fc9c3bdc24a2d048b1c83c9c9e2aaf6238e193bdd9d17f4b14bdae5ed6c31ae0de4b1977250
data/CHANGELOG.md CHANGED
@@ -2,6 +2,25 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.1.0.pre2 (2026-10-29)
6
+
7
+ Closes the known gaps of the authorization module.
8
+
9
+ - `loads:` inside input objects: `include GraphQL::Armory::Authorization::Base` in the base input object loads them through
10
+ the user's scope; the audit reports input objects whose `loads:` skip it.
11
+
12
+ - Audit of interfaces: an interface field is reported once, on the interface that declares it; `authorize: true` on an
13
+ interface field and interface fields whose field class lacks `Base` (such as Relay `Node.id`) on a bypassed implementer
14
+ are reported.
15
+
16
+ - Global IDs in `loads:`: when the schema defines `object_from_id`, the id is decoded with it, a record of another model is
17
+ refused, and the record is looked up again inside the user's scope.
18
+
19
+ ## 0.1.0.pre (2026-10-29)
20
+
21
+ First prerelease on RubyGems: Pundit and CanCanCan adapters, deny-by-default coverage, scoped lists and lookups, schema audit,
22
+ visibility, argument rules, mutations and resolvers, interfaces and unions. The entries below describe it.
23
+
5
24
  - Interfaces and unions: an interface field with no rule is denied when an implementing type is a bypass, and the audit
6
25
  reports it; `include Base` in an interface or union is refused with a pointer to the possible types.
7
26
 
data/LICENSE CHANGED
File without changes
data/MIGRATING.md CHANGED
File without changes
data/README.md CHANGED
@@ -18,7 +18,7 @@ The first module is **authorization**. It plugs [Pundit](https://github.com/varv
18
18
  ## Install
19
19
 
20
20
  ```ruby
21
- gem "graphql-armory"
21
+ gem "graphql-armory", "~> 0.1.0.pre2" # a prerelease: `gem install graphql-armory --pre`
22
22
  gem "pundit" # or "cancancan", whichever you use
23
23
  ```
24
24
 
@@ -97,7 +97,9 @@ field :invoice_via_ref, InvoiceType, authorize: :none, reason: "loaded through t
97
97
  end
98
98
  ```
99
99
 
100
- `find_by:` and `loads:` look the record up **inside the user's scope**, so another tenant's ID answers exactly like a missing one. Opt out of the scope for a `loads:` only on purpose, with `load_through_scope: false`; the audit lists it.
100
+ `find_by:` and `loads:` (also inside input objects whose base class includes `Base`) look the record up **inside the user's scope**, so another tenant's ID answers exactly like a missing one. Opt out of the scope for a field's `loads:` only on purpose, with `load_through_scope: false`; the audit lists it. On `Execution::Next` the opt-out also reaches `loads:` nested in that field's input objects (the interpreter keeps those scoped), and the audit lists them too (`Query.field(filter.invoice_id)`). An input object that overrides `object_from_id` or defines `load_<argument>` itself is honoured on the interpreter and not seen by the audit.
101
+
102
+ **Global IDs.** When your schema defines `object_from_id` (Relay global IDs), `loads:` decodes the id with it, refuses a record of another model, and looks the record up again inside the scope (one extra query per load). Without `object_from_id`, the id is the primary key. `object_from_id` must return the record itself, not a lazy value: a lazy value is denied. A decoder that raises `ActiveRecord::RecordNotFound` (like `GlobalID.find`) or `ArgumentError` (a malformed id) is answered like a missing record; other errors propagate. `loads:` of an interface or union (for example `Types::Node`) raises `ConfigurationError`: load a concrete type. Composite primary keys are not supported.
101
103
 
102
104
  ## The audit
103
105
 
@@ -224,7 +226,7 @@ end
224
226
  - An unknown `on_denied:` raises `ArgumentError`; a mutation with a rule but no model raises `ConfigurationError`.
225
227
  - **Class-level answers are weak.** With no loaded record of the model (no `loads:`, an empty `loads: [ID]`, or a `loads:` of another type) the rule is asked about the model's class: CanCan answers true for anyone holding the right, and a Pundit policy that reads `record` attributes raises. Load the record you are authorizing.
226
228
  - **`ready?` runs before authorization** (and, on the interpreter, before a failing `loads:`). Keep it free of side effects.
227
- - **`loads:` works on top-level arguments only.** Inside an input object it is not scoped, and the engines disagree (the interpreter raises, `Execution::Next` yields nil).
229
+ - **`loads:` inside an input object** goes through the scope only when your base input object includes `Base` (`class Types::BaseInputObject < GraphQL::Schema::InputObject; include GraphQL::Armory::Authorization::Base; end`); otherwise the audit reports it as `:unscoped_load`.
228
230
  - **Do not put `authorize:` on a mutation field or on payload fields**; the mutation's own rule is the one that counts. A subclass that redeclares `authorize :none` is open to everyone, on purpose.
229
231
 
230
232
  ## Interfaces and unions
@@ -235,8 +237,9 @@ What an interface adds is its fields, declared once and resolved on many types:
235
237
 
236
238
  - A field with no rule is **covered** by the rule of the Base types that implement the interface (a plain implementer runs no check at all), unless one of them is a bypass (`authorize :none`). Then the field is **denied for every implementer**, because it cannot tell which type it runs on, and the audit reports it (`:field_without_rule`, for example `Document.number`). Declare it (`authorize: :none, reason:` or an action) to fix it.
237
239
  - `visible_when:` is not supported on an interface field; put it on the concrete types' fields.
238
- - `authorize: true` is not valid on an interface field: it is always denied, and the audit does not flag it. Use an action or `:none`.
239
- - An interface whose field class does not include `Base`, such as graphql-ruby's built-in Relay `Node`, runs no field check, so its `id` stays readable on a bypassed implementer. Give your own `Node` interface a Base field class if that matters.
240
+ - `authorize: true` is not valid on an interface field: it is always denied, and the audit reports it. Use an action or `:none`.
241
+ - An interface whose field class does not include `Base`, such as graphql-ruby's built-in Relay `Node`, runs no field check, so its `id` stays readable on a bypassed implementer. The audit reports it (`:unprotected`, `Node.id`); give your own `Node` interface a Base field class if that matters.
242
+ - The audit reports an interface field once, on the interface that declares it, not again on each implementer.
240
243
 
241
244
  ## Telemetry
242
245
 
@@ -246,7 +249,7 @@ Pass `sinks: [->(signal, data) {}]`. Signals are `:allowed`, `:denied`, `:bypass
246
249
 
247
250
  Covered by the black-box suite in `spec/security`, on both engines: user A cannot read user B's objects by root field, nested association or `loads:`; lists never contain foreign objects; uncovered types fail closed and show in the audit; a field hidden with `visible?` is absent from introspection and error suggestions (that is graphql-ruby's behaviour, pinned by a test).
248
251
 
249
- Not covered yet: decoding global IDs in `loads:`. Policies must return `true`/`false`/`nil`; anything else raises `AdapterError`. Do not use `dataloader.request` inside policies; use `dataloader.with(...).load`.
252
+ Policies must return `true`/`false`/`nil`; anything else raises `AdapterError`. Do not use `dataloader.request` inside policies; use `dataloader.with(...).load`.
250
253
 
251
254
  ## Engines
252
255
 
@@ -279,13 +282,23 @@ GRAPHQL_ARMORY_ENGINE=next docker compose run --rm test # full suite, Ex
279
282
  GRAPHQL_VERSION="~> 2.4.0" docker compose run --rm test # another graphql version
280
283
  ```
281
284
 
285
+ ### Releasing
286
+
287
+ Releases are published from GitHub Actions with RubyGems Trusted Publishing (`.github/workflows/release.yml`): no API key is stored and no OTP is asked. Bump `GraphQL::Armory::VERSION` and the CHANGELOG on `main`, then push the matching tag:
288
+
289
+ ```
290
+ git tag v0.1.0.pre2 && git push origin v0.1.0.pre2
291
+ ```
292
+
293
+ The workflow checks that the tag matches the version, runs the specs and pushes the gem. It only publishes a tag on a commit of `main`. The `release` environment can require a maintainer's approval first (on a public repository, or a private one on a paid GitHub plan).
294
+
282
295
  ## Roadmap
283
296
 
284
297
  Status: ✅ done, 🚧 in progress, ⬜ not started. Update this table in the same PR that changes a module.
285
298
 
286
299
  | Order | Module | Status | Current limits |
287
300
  |---|---|---|---|
288
- | 1 | Authorization (Pundit, CanCanCan) | 🚧 | Pundit and CanCanCan done (the audit checks less with CanCanCan). Not covered yet: decoding global IDs in `loads:`. `Execution::Next` needs graphql >= 2.6.2. Unreleased. |
301
+ | 1 | Authorization (Pundit, CanCanCan) | ✅ | Released in 0.1.0.pre2 (prerelease). Pundit and CanCanCan (the audit checks less with CanCanCan). Global IDs in `loads:` decoded through `object_from_id`; `loads:` inside input objects scoped with `include Base`. `Execution::Next` needs graphql >= 2.6.2. |
289
302
  | 2 | Encrypted, versioned IDs and cursors | ⬜ | Research not started. |
290
303
  | 3 | Persisted queries / operation whitelisting | ⬜ | Research not started. |
291
304
  | 4 | Rate limiting | ⬜ | Research not started. |
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -38,6 +38,7 @@ module GraphQL
38
38
 
39
39
  audited_types(schema).each { |type| audit_type(type, findings) }
40
40
  schema.types.each_value { |type| audit_interface(type, schema, findings) if type.kind.interface? && !type.introspection? }
41
+ schema.types.each_value { |type| audit_input_loads(type, findings) if type.kind.input_object? && !type.introspection? }
41
42
  findings.report
42
43
  end
43
44
 
@@ -72,18 +73,32 @@ module GraphQL
72
73
  end
73
74
  end
74
75
 
75
- # An interface field with no rule is denied when any implementer is a bypass; the fix is to declare it.
76
+ # Interface fields are reported once, on the interface that declares them, not on every implementer.
77
+ # Undeclared ones are denied when an implementer is a bypass; ones whose field class lacks Base are
78
+ # never checked, so on a bypassed implementer they are open; `authorize: true` names no action.
76
79
  def audit_interface(interface, schema, findings)
77
- return unless Integration.bypassed_implementer?(interface, schema)
78
-
79
- interface.fields.each_value do |field|
80
- next unless field.respond_to?(:armory_declared) && field.armory_declared.nil?
80
+ bypassed = Integration.bypassed_implementer?(interface, schema)
81
+ interface.own_fields.each_value do |field|
82
+ path = "#{interface.graphql_name}.#{field.original_name}"
83
+ unless field.respond_to?(:armory_declared)
84
+ findings.problem(:unprotected, path, "its field class does not include Base, so it is not checked on a bypassed implementer") if bypassed
85
+ next
86
+ end
81
87
 
82
- findings.problem(:field_without_rule, "#{interface.graphql_name}.#{field.original_name}",
83
- "declares no rule and an implementing type is bypassed, so it is denied")
88
+ case field.armory_declared
89
+ when true
90
+ findings.problem(:field_without_rule, path, "uses authorize: true, but an interface has no action of its own, so it is always denied")
91
+ when nil
92
+ findings.problem(:field_without_rule, path, "declares no rule and an implementing type is bypassed, so it is denied") if bypassed
93
+ end
84
94
  end
85
95
  end
86
96
 
97
+ # A field an implementer inherits from an interface is reported on the interface.
98
+ def interface_field?(field)
99
+ field.owner.respond_to?(:kind) && field.owner.kind.interface?
100
+ end
101
+
87
102
  def model_of(type, path, findings)
88
103
  type.armory_model
89
104
  rescue ConfigurationError => error
@@ -101,11 +116,13 @@ module GraphQL
101
116
  def audit_fields(type, findings, model:, bypassed:, reported:)
102
117
  type.all_field_definitions.each do |field|
103
118
  path = "#{type.graphql_name}.#{field.original_name}"
104
- next findings.problem(:field_without_rule, path, "its field class does not include Base, so it is denied") if bypassed && !field.respond_to?(:armory_declared)
119
+ # An inherited interface field is reported as uncovered on its interface; every other check still runs here.
120
+ inherited = bypassed && interface_field?(field)
121
+ next findings.problem(:field_without_rule, path, "its field class does not include Base, so it is denied") if bypassed && !inherited && !field.respond_to?(:armory_declared)
105
122
  next unless field.respond_to?(:armory_declared)
106
123
  next audit_resolver(field, path, findings) if field.resolver.respond_to?(:armory_rule)
107
124
 
108
- audit_declaration(field, path, findings, model: model, bypassed: bypassed, reported: reported)
125
+ audit_declaration(field, path, findings, model: model, bypassed: bypassed && !inherited, reported: reported)
109
126
  audit_visibility(field, path, findings, model: model, reported: reported)
110
127
  audit_loads(field, path, findings)
111
128
  audit_arguments(field, path, findings, reported: reported)
@@ -170,12 +187,37 @@ module GraphQL
170
187
  def audit_loads(field, path, findings)
171
188
  return if field.armory_load_through_scope?
172
189
 
173
- field.all_argument_definitions.select(&:loads).each do |argument|
174
- name = GraphQL::Schema::Member::BuildType.underscore(argument.graphql_name)
190
+ opted_out_loads(field.all_argument_definitions, Set.new).each do |name|
175
191
  findings.problem(:unscoped_load, "#{path}(#{name})", "loads through the app's object_from_id, not through the user's scope")
176
192
  end
177
193
  end
178
194
 
195
+ # Execution::Next loads `loads:` nested in input objects with the field's loader, so a field's opt-out
196
+ # reaches them too: list them as `filter.invoice_id`. `seen` stops recursive input types.
197
+ def opted_out_loads(arguments, seen, prefix = nil)
198
+ arguments.flat_map do |argument|
199
+ name = [prefix, GraphQL::Schema::Member::BuildType.underscore(argument.graphql_name)].compact.join(".")
200
+ next [name] if argument.loads
201
+
202
+ input = argument.type.unwrap
203
+ next [] unless input.kind.input_object? && seen.add?(input)
204
+
205
+ opted_out_loads(input.all_argument_definitions, seen, name)
206
+ end
207
+ end
208
+
209
+ # An input object loads its own `loads:` arguments; without Base they skip the user's scope.
210
+ def audit_input_loads(input, findings)
211
+ return if input.respond_to?(:armory_scoped_loads?)
212
+
213
+ # A mutation's generated input (`from_resolver`) is loaded, scoped, by the mutation itself.
214
+ input.all_argument_definitions.select(&:loads).reject(&:from_resolver?).each do |argument|
215
+ name = GraphQL::Schema::Member::BuildType.underscore(argument.graphql_name)
216
+ findings.problem(:unscoped_load, "#{input.graphql_name}(#{name})",
217
+ "its input object class does not include Base, so it loads through the app's object_from_id, not the user's scope")
218
+ end
219
+ end
220
+
179
221
  # Collects what one audit finds.
180
222
  class Findings
181
223
  def initialize
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -21,11 +21,13 @@ module GraphQL
21
21
  elsif base <= GraphQL::Schema::Resolver
22
22
  base.extend(Integration::ResolverClass)
23
23
  base.include(Integration::Resolver)
24
+ elsif base <= GraphQL::Schema::InputObject
25
+ base.extend(Integration::InputObject)
24
26
  elsif base <= GraphQL::Schema::Argument
25
27
  base.include(Integration::Argument)
26
28
  else
27
29
  raise ArgumentError,
28
- "GraphQL::Armory::Authorization::Base can only be included in a GraphQL object, field, argument, resolver or mutation class, not #{base}. Interfaces and unions are not authorized themselves: declare the rule on their possible types"
30
+ "GraphQL::Armory::Authorization::Base can only be included in a GraphQL object, field, argument, input object, resolver or mutation class, not #{base}. Interfaces and unions are not authorized themselves: declare the rule on their possible types"
29
31
  end
30
32
  end
31
33
  end
@@ -38,7 +38,7 @@ module GraphQL
38
38
  def object_from_id(type, id, context)
39
39
  return super unless @armory_load_through_scope
40
40
 
41
- Integration.find(type, nil, id, context)
41
+ Integration.load(type, id, context)
42
42
  end
43
43
 
44
44
  # A `loads:` id that is not found is answered like a denial, so the client cannot tell the two apart.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GraphQL
4
+ module Armory
5
+ module Authorization
6
+ module Integration
7
+ # Extended into an input object class by `include GraphQL::Armory::Authorization::Base`.
8
+ # graphql-ruby loads an input object's `loads:` arguments with the class's own loader, not the
9
+ # field's, so the scoped lookup has to live here too.
10
+ #
11
+ # class Types::BaseInputObject < GraphQL::Schema::InputObject
12
+ # include GraphQL::Armory::Authorization::Base
13
+ # end
14
+ module InputObject
15
+ def armory_scoped_loads?
16
+ true
17
+ end
18
+
19
+ def object_from_id(type, id, context)
20
+ Integration.load(type, id, context)
21
+ end
22
+
23
+ # A `loads:` id that is not found is answered like a denial, so the client cannot tell the two apart.
24
+ def load_application_object_failed(error)
25
+ raise GraphQL::UnauthorizedError.new(object: nil, type: error.argument.loads, context: error.context)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -55,7 +55,7 @@ module GraphQL
55
55
 
56
56
  # `loads:` arguments are looked up inside the user's scope, so a foreign id answers like a missing one.
57
57
  def object_from_id(type, id, context)
58
- Integration.find(type, nil, id, context)
58
+ Integration.load(type, id, context)
59
59
  end
60
60
 
61
61
  def load_application_object_failed(error)
@@ -6,6 +6,7 @@ require "graphql/armory/authorization/integration/find_by"
6
6
  require "graphql/armory/authorization/integration/field"
7
7
  require "graphql/armory/authorization/integration/argument"
8
8
  require "graphql/armory/authorization/integration/resolver"
9
+ require "graphql/armory/authorization/integration/input_object"
9
10
  require "graphql/armory/authorization/integration/base"
10
11
 
11
12
  module GraphQL
@@ -62,6 +63,36 @@ module GraphQL
62
63
  record if record && allowed?(record, rule, context)
63
64
  end
64
65
 
66
+ # Loads a `loads:` argument inside the user's scope. When the schema defines `object_from_id` (global
67
+ # IDs), it decodes the id; the record must be of the type's model, and is then looked up again
68
+ # through the scope, so a foreign id still looks like a missing one. Otherwise the id is the primary key.
69
+ def self.load(type, id, context)
70
+ schema = context.schema
71
+ return find(type, nil, id, context) unless decodes_ids?(schema) && type.respond_to?(:armory_model)
72
+
73
+ model = type.armory_model
74
+ record = decode(schema, id, context)
75
+ return nil unless record.is_a?(model)
76
+
77
+ find(type, nil, record.public_send(model.primary_key), context)
78
+ end
79
+
80
+ # `GlobalID.find`, graphql-ruby's documented `object_from_id`, raises for a missing record, and a
81
+ # decoder may raise for a malformed id. Both must answer like a record out of scope: nil.
82
+ def self.decode(schema, id, context)
83
+ schema.object_from_id(id, context)
84
+ rescue StandardError => error
85
+ raise unless error.is_a?(ArgumentError) || error.class.name == "ActiveRecord::RecordNotFound"
86
+
87
+ nil
88
+ end
89
+ private_class_method :decode
90
+
91
+ # graphql-ruby's default `object_from_id` only raises, so an override is the app decoding its IDs.
92
+ def self.decodes_ids?(schema)
93
+ schema.method(:object_from_id).owner != GraphQL::Schema.singleton_class
94
+ end
95
+
65
96
  # An interface field with no rule of its own cannot name the type it is resolved on, so it is only
66
97
  # covered when no implementer is a bypass: a field on a bypassed type is denied, and so is this one.
67
98
  def self.bypassed_implementer?(interface, schema)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -2,6 +2,6 @@
2
2
 
3
3
  module GraphQL
4
4
  module Armory
5
- VERSION = "0.1.0.pre"
5
+ VERSION = "0.1.0.pre2"
6
6
  end
7
7
  end
File without changes
File without changes
File without changes
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: graphql-armory
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0.pre
4
+ version: 0.1.0.pre2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Israel
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2026-10-29 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: graphql
@@ -34,7 +33,6 @@ description: 'Open-source extensions for graphql-ruby built on its public extens
34
33
  points. The first module is authorization: deny-by-default coverage, scoped lists
35
34
  and connections, IDOR-safe lookups, mutations, interfaces and unions, visibility
36
35
  driven by the policy, and a schema audit for CI.'
37
- email:
38
36
  executables: []
39
37
  extensions: []
40
38
  extra_rdoc_files: []
@@ -65,6 +63,7 @@ files:
65
63
  - lib/graphql/armory/authorization/integration/base.rb
66
64
  - lib/graphql/armory/authorization/integration/field.rb
67
65
  - lib/graphql/armory/authorization/integration/find_by.rb
66
+ - lib/graphql/armory/authorization/integration/input_object.rb
68
67
  - lib/graphql/armory/authorization/integration/object.rb
69
68
  - lib/graphql/armory/authorization/integration/resolver.rb
70
69
  - lib/graphql/armory/authorization/integration/schema.rb
@@ -84,7 +83,6 @@ metadata:
84
83
  changelog_uri: https://github.com/israelb/graphql-armory/blob/main/CHANGELOG.md
85
84
  bug_tracker_uri: https://github.com/israelb/graphql-armory/issues
86
85
  rubygems_mfa_required: 'true'
87
- post_install_message:
88
86
  rdoc_options: []
89
87
  require_paths:
90
88
  - lib
@@ -95,12 +93,11 @@ required_ruby_version: !ruby/object:Gem::Requirement
95
93
  version: '3.1'
96
94
  required_rubygems_version: !ruby/object:Gem::Requirement
97
95
  requirements:
98
- - - ">"
96
+ - - ">="
99
97
  - !ruby/object:Gem::Version
100
- version: 1.3.1
98
+ version: '0'
101
99
  requirements: []
102
- rubygems_version: 3.4.19
103
- signing_key:
100
+ rubygems_version: 3.6.9
104
101
  specification_version: 4
105
102
  summary: 'Authorization for graphql-ruby: Pundit and CanCanCan, deny-by-default, with
106
103
  a schema audit.'