decidim-api 0.30.4 → 0.30.5

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 (82) hide show
  1. checksums.yaml +4 -4
  2. data/app/controllers/decidim/api/queries_controller.rb +2 -1
  3. data/config/locales/am-ET.yml +1 -0
  4. data/config/locales/ar.yml +1 -0
  5. data/config/locales/bg.yml +1 -0
  6. data/config/locales/bn-BD.yml +1 -0
  7. data/config/locales/bs-BA.yml +1 -0
  8. data/config/locales/ca-IT.yml +8 -0
  9. data/config/locales/ca.yml +8 -0
  10. data/config/locales/cs.yml +1 -0
  11. data/config/locales/da.yml +1 -0
  12. data/config/locales/de.yml +1 -0
  13. data/config/locales/el.yml +1 -0
  14. data/config/locales/en.yml +8 -0
  15. data/config/locales/eo.yml +1 -0
  16. data/config/locales/es-MX.yml +8 -0
  17. data/config/locales/es-PY.yml +8 -0
  18. data/config/locales/es.yml +8 -0
  19. data/config/locales/et.yml +1 -0
  20. data/config/locales/eu.yml +6 -0
  21. data/config/locales/fa-IR.yml +1 -0
  22. data/config/locales/fi-plain.yml +8 -0
  23. data/config/locales/fi.yml +8 -0
  24. data/config/locales/fr-CA.yml +8 -0
  25. data/config/locales/fr.yml +8 -0
  26. data/config/locales/ga-IE.yml +1 -0
  27. data/config/locales/gl.yml +1 -0
  28. data/config/locales/gn-PY.yml +1 -0
  29. data/config/locales/he-IL.yml +1 -0
  30. data/config/locales/hr.yml +1 -0
  31. data/config/locales/hu.yml +1 -0
  32. data/config/locales/id-ID.yml +1 -0
  33. data/config/locales/is-IS.yml +1 -0
  34. data/config/locales/it.yml +1 -0
  35. data/config/locales/ja.yml +8 -0
  36. data/config/locales/ka-GE.yml +1 -0
  37. data/config/locales/kaa.yml +1 -0
  38. data/config/locales/ko.yml +1 -0
  39. data/config/locales/lb.yml +1 -0
  40. data/config/locales/lo-LA.yml +1 -0
  41. data/config/locales/lt.yml +1 -0
  42. data/config/locales/lv.yml +1 -0
  43. data/config/locales/mt.yml +1 -0
  44. data/config/locales/nl.yml +1 -0
  45. data/config/locales/no.yml +6 -0
  46. data/config/locales/oc-FR.yml +1 -0
  47. data/config/locales/om-ET.yml +1 -0
  48. data/config/locales/pl.yml +1 -0
  49. data/config/locales/pt-BR.yml +8 -0
  50. data/config/locales/pt.yml +1 -0
  51. data/config/locales/ro-RO.yml +8 -0
  52. data/config/locales/ru.yml +1 -0
  53. data/config/locales/si-LK.yml +1 -0
  54. data/config/locales/sk.yml +1 -0
  55. data/config/locales/sl.yml +1 -0
  56. data/config/locales/so-SO.yml +1 -0
  57. data/config/locales/sq-AL.yml +1 -0
  58. data/config/locales/sr-CS.yml +1 -0
  59. data/config/locales/sv.yml +1 -0
  60. data/config/locales/sw-KE.yml +1 -0
  61. data/config/locales/th-TH.yml +1 -0
  62. data/config/locales/ti-ER.yml +1 -0
  63. data/config/locales/tr-TR.yml +1 -0
  64. data/config/locales/uk.yml +1 -0
  65. data/config/locales/val-ES.yml +1 -0
  66. data/config/locales/vi.yml +1 -0
  67. data/config/locales/zh-CN.yml +1 -0
  68. data/config/locales/zh-TW.yml +1 -0
  69. data/lib/decidim/api/alias_analyzer.rb +23 -0
  70. data/lib/decidim/api/decidim_introspection.rb +51 -0
  71. data/lib/decidim/api/errors/introspection_disabled_error.rb +14 -0
  72. data/lib/decidim/api/errors/recursion_limit_exceeded_error.rb +14 -0
  73. data/lib/decidim/api/errors/too_many_aliases_error.rb +15 -0
  74. data/lib/decidim/api/recursion_analyzer.rb +74 -0
  75. data/lib/decidim/api/schema.rb +6 -0
  76. data/lib/decidim/api/test/component_context.rb +1 -0
  77. data/lib/decidim/api/test/type_context.rb +93 -2
  78. data/lib/decidim/api/test.rb +1 -0
  79. data/lib/decidim/api/types.rb +9 -0
  80. data/lib/decidim/api/version.rb +1 -1
  81. data/lib/decidim/api.rb +12 -0
  82. metadata +84 -12
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c9d19836694cb4a69659d71911f3c1f92345b64d3ac8b1b1291f75345bab238
4
- data.tar.gz: 6b9b5836bc079d9d1eef0a044edea68b52f5e22819f3dcf76790077e9520248b
3
+ metadata.gz: 7ca33c846496c357db67439e87dbdb4ad6639c13fd2a823a52c6f270be3d5d0e
4
+ data.tar.gz: 346529a751650d30742a0e90225971763ebab4fe3ff6d8e97d3ef1305440b37b
5
5
  SHA512:
6
- metadata.gz: 36e267e0ef727bba60999c69905b9dc84cbc137afbef76f2dec8a2d5d5be782dbe5524aeff4f7aa674e93c39583a117ef45fe5dd7cce0e26ebbace7f3402e146
7
- data.tar.gz: 25be574b0ef8f8269739033dc06c5c4142f85c93a90574506f4708112b44a3c9693be0894a45d7e7dfe8f85b03662ec3a46280eadb33cc269a55bd2401e1fa69
6
+ metadata.gz: 1584a295e5839cf7fb34107ccb4362f61f4c09d19c82b32839dda92282d45c2bce37a768d794962a99b83cfc82c271bdf5f92c693af0dbaa576302d55c9a0b79
7
+ data.tar.gz: 5447dff2e9c723b47189623b378d4818ba0680fdc4f32e825a0f04d65beb14852d0e9362bc2ef00e8c7cd1814fb1b0c9661c9cf128df67cec922f9de2676c27b
@@ -29,7 +29,8 @@ module Decidim
29
29
  def context
30
30
  {
31
31
  current_organization:,
32
- current_user:
32
+ current_user:,
33
+ can_introspect: Decidim::Api.enable_anonymous_introspection || current_user&.admin?
33
34
  }
34
35
  end
35
36
 
@@ -0,0 +1 @@
1
+ am:
@@ -0,0 +1 @@
1
+ ar:
@@ -0,0 +1 @@
1
+ bg:
@@ -0,0 +1 @@
1
+ bn:
@@ -0,0 +1 @@
1
+ bs:
@@ -0,0 +1,8 @@
1
+ ---
2
+ ca-IT:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: La introspecció està desactivada per a aquesta sol·licitud
7
+ recursion_limit_exceeded_error: S'han detectat massa consultes recurrents
8
+ too_many_aliases_error: S'han fet servir massa àlies (nicknames). Has fet servir %{size} àlies, però es permet un màxim de %{limit}.
@@ -0,0 +1,8 @@
1
+ ---
2
+ ca:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: La introspecció està desactivada per a aquesta sol·licitud
7
+ recursion_limit_exceeded_error: S'han detectat massa consultes recurrents
8
+ too_many_aliases_error: S'han fet servir massa àlies (nicknames). Has fet servir %{size} àlies, però es permet un màxim de %{limit}.
@@ -0,0 +1 @@
1
+ cs:
@@ -0,0 +1 @@
1
+ da:
@@ -0,0 +1 @@
1
+ de:
@@ -0,0 +1 @@
1
+ el:
@@ -0,0 +1,8 @@
1
+ ---
2
+ en:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: Introspection is disabled for this request
7
+ recursion_limit_exceeded_error: Too many recursions detected in query
8
+ too_many_aliases_error: Too many aliases used. You have used %{size} aliases, but %{limit} are allowed.
@@ -0,0 +1 @@
1
+ eo:
@@ -0,0 +1,8 @@
1
+ ---
2
+ es-MX:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: La introspección está desactivada para esta solicitud
7
+ recursion_limit_exceeded_error: Se han detectado demasiadas consultas recurrentes
8
+ too_many_aliases_error: Has utilizado demasiados alias (nicknames). Has usado %{size} alias, pero se permite un máximo de %{limit}.
@@ -0,0 +1,8 @@
1
+ ---
2
+ es-PY:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: La introspección está desactivada para esta solicitud
7
+ recursion_limit_exceeded_error: Se han detectado demasiadas consultas recurrentes
8
+ too_many_aliases_error: Has utilizado demasiados alias (nicknames). Has usado %{size} alias, pero se permite un máximo de %{limit}.
@@ -0,0 +1,8 @@
1
+ ---
2
+ es:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: La introspección está desactivada para esta solicitud
7
+ recursion_limit_exceeded_error: Se han detectado demasiadas consultas recurrentes
8
+ too_many_aliases_error: Has utilizado demasiados alias (nicknames). Has usado %{size} alias, pero se permite un máximo de %{limit}.
@@ -0,0 +1 @@
1
+ et:
@@ -0,0 +1,6 @@
1
+ ---
2
+ eu:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ too_many_aliases_error: Ezizen gehiegi erabiltzen dira. %{size} ezizen erabili dituzu, baina %{limit} onartuta daude.
@@ -0,0 +1 @@
1
+ fa:
@@ -0,0 +1,8 @@
1
+ ---
2
+ fi-pl:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: Rajapintamallin tarkastelu ei ole käytössä tälle pyynnölle
7
+ recursion_limit_exceeded_error: Rajapintakyselyssä on liikaa rekursiivisia kyselyitä
8
+ too_many_aliases_error: Rajapintakysely käyttää liian monta alias-nimitystä. Kyselyssä on %{size} aliasta, mutta maksimissaan %{limit} on sallittu.
@@ -0,0 +1,8 @@
1
+ ---
2
+ fi:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: Rajapintamallin tarkastelu ei ole käytössä tälle pyynnölle
7
+ recursion_limit_exceeded_error: Rajapintakyselyssä on liikaa rekursiivisia kyselyitä
8
+ too_many_aliases_error: Rajapintakysely käyttää liian monta alias-nimitystä. Kyselyssä on %{size} aliasta, mutta maksimissaan %{limit} on sallittu.
@@ -0,0 +1,8 @@
1
+ ---
2
+ fr-CA:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: L'introspection est désactivée pour cette requête
7
+ recursion_limit_exceeded_error: Trop de récursions détectées dans la requête
8
+ too_many_aliases_error: Trop d'alias utilisés. Vous avez utilisé %{size} alias, mais seulement %{limit} sont autorisés.
@@ -0,0 +1,8 @@
1
+ ---
2
+ fr:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: L'introspection est désactivée pour cette requête
7
+ recursion_limit_exceeded_error: Trop de récursions détectées dans la requête
8
+ too_many_aliases_error: Trop d'alias utilisés. Vous avez utilisé %{size} alias, mais seulement %{limit} sont autorisés.
@@ -0,0 +1 @@
1
+ ga:
@@ -0,0 +1 @@
1
+ gl:
@@ -0,0 +1 @@
1
+ gn:
@@ -0,0 +1 @@
1
+ he:
@@ -0,0 +1 @@
1
+ hr:
@@ -0,0 +1 @@
1
+ hu:
@@ -0,0 +1 @@
1
+ id:
@@ -0,0 +1 @@
1
+ is-IS:
@@ -0,0 +1 @@
1
+ it:
@@ -0,0 +1,8 @@
1
+ ---
2
+ ja:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: このリクエストのイントロスペクションは無効です
7
+ recursion_limit_exceeded_error: クエリで再帰が検出された回数が多すぎます
8
+ too_many_aliases_error: エイリアスが多すぎます。 %{size} 件のエイリアスを使用していますが、許可されているのは %{limit} 件までです。
@@ -0,0 +1 @@
1
+ ka:
@@ -0,0 +1 @@
1
+ kaa:
@@ -0,0 +1 @@
1
+ ko:
@@ -0,0 +1 @@
1
+ lb:
@@ -0,0 +1 @@
1
+ lo:
@@ -0,0 +1 @@
1
+ lt:
@@ -0,0 +1 @@
1
+ lv:
@@ -0,0 +1 @@
1
+ mt:
@@ -0,0 +1 @@
1
+ nl:
@@ -0,0 +1,6 @@
1
+ ---
2
+ "no":
3
+ decidim:
4
+ api:
5
+ errors:
6
+ recursion_limit_exceeded_error: For mange rekursjoner oppdaget i spørringen
@@ -0,0 +1 @@
1
+ oc:
@@ -0,0 +1 @@
1
+ om:
@@ -0,0 +1 @@
1
+ pl:
@@ -0,0 +1,8 @@
1
+ ---
2
+ pt-BR:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: A inspeção está desabilitada para esta requisição
7
+ recursion_limit_exceeded_error: Muitas recursões detectadas na consulta
8
+ too_many_aliases_error: Foram utilizados muitos aliases. Você usou %{size} aliases, mas são permitidos apenas %{limit}.
@@ -0,0 +1 @@
1
+ pt:
@@ -0,0 +1,8 @@
1
+ ---
2
+ ro:
3
+ decidim:
4
+ api:
5
+ errors:
6
+ introspection_disabled: Introspection este dezactivat pentru această solicitare
7
+ recursion_limit_exceeded_error: Prea multe recursii detectate în interogare
8
+ too_many_aliases_error: Prea multe aliasuri folosite. Ați folosit aliasuri %{size}, dar %{limit} sunt permise.
@@ -0,0 +1 @@
1
+ ru:
@@ -0,0 +1 @@
1
+ si:
@@ -0,0 +1 @@
1
+ sk:
@@ -0,0 +1 @@
1
+ sl:
@@ -0,0 +1 @@
1
+ so:
@@ -0,0 +1 @@
1
+ sq:
@@ -0,0 +1 @@
1
+ sr:
@@ -0,0 +1 @@
1
+ sv:
@@ -0,0 +1 @@
1
+ sw:
@@ -0,0 +1 @@
1
+ th:
@@ -0,0 +1 @@
1
+ ti:
@@ -0,0 +1 @@
1
+ tr:
@@ -0,0 +1 @@
1
+ uk:
@@ -0,0 +1 @@
1
+ val:
@@ -0,0 +1 @@
1
+ vi:
@@ -0,0 +1 @@
1
+ zh-CN:
@@ -0,0 +1 @@
1
+ zh-TW:
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Api
5
+ class AliasAnalyzer < GraphQL::Analysis::AST::Analyzer
6
+ def initialize(query)
7
+ super
8
+
9
+ @aliases = Set.new
10
+ end
11
+
12
+ def on_enter_field(node, _parent, _visitor)
13
+ @aliases.add(node.alias) if node.alias.present?
14
+ end
15
+
16
+ def result
17
+ if @aliases.size > Decidim::Api.max_aliases
18
+ Errors::TooManyAliasesError.new(I18n.t("decidim.api.errors.too_many_aliases_error", size: @aliases.size, limit: Decidim::Api.max_aliases))
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Api
5
+ module DecidimIntrospection
6
+ module FieldVisibility
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ def self.visible?(context)
11
+ raise Errors::IntrospectionDisabledError, I18n.t("decidim.api.errors.introspection_disabled") unless context[:can_introspect] == true
12
+
13
+ super
14
+ end
15
+ end
16
+ end
17
+
18
+ class SchemaType < GraphQL::Introspection::SchemaType
19
+ include FieldVisibility
20
+ end
21
+
22
+ class TypeType < GraphQL::Introspection::TypeType
23
+ include FieldVisibility
24
+ end
25
+
26
+ class DirectiveType < GraphQL::Introspection::DirectiveType
27
+ include FieldVisibility
28
+ end
29
+
30
+ class DirectiveLocationEnum < GraphQL::Introspection::DirectiveLocationEnum
31
+ include FieldVisibility
32
+ end
33
+
34
+ class EnumValueType < GraphQL::Introspection::EnumValueType
35
+ include FieldVisibility
36
+ end
37
+
38
+ class FieldType < GraphQL::Introspection::FieldType
39
+ include FieldVisibility
40
+ end
41
+
42
+ class InputValueType < GraphQL::Introspection::InputValueType
43
+ include FieldVisibility
44
+ end
45
+
46
+ class TypeKindEnum < GraphQL::Introspection::TypeKindEnum
47
+ include FieldVisibility
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Api
5
+ module Errors
6
+ # i18n-tasks-use t("decidim.api.errors.introspection_disabled")
7
+ class IntrospectionDisabledError < GraphQL::ExecutionError
8
+ def to_h
9
+ super.merge({ "extensions" => { "code" => "INTROSPECTION_DISABLED_ERROR" } })
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Api
5
+ module Errors
6
+ # i18n-tasks-use t("decidim.api.errors.recursion_limit_exceeded_error")
7
+ class RecursionLimitExceededError < GraphQL::AnalysisError
8
+ def to_h
9
+ super.merge({ "extensions" => { "code" => "RECURSION_LIMIT_EXCEEDED_ERROR" } })
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Decidim
4
+ module Api
5
+ module Errors
6
+ # i18n-tasks-use t("decidim.api.errors.too_many_aliases_error")
7
+
8
+ class TooManyAliasesError < GraphQL::AnalysisError
9
+ def to_h
10
+ super.merge({ "extensions" => { "code" => "TOO_MANY_ALIASES_ERROR" } })
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This analyzer checks for too many recursions in GraphQL queries.
4
+ # Copyright (c) GitLab B.V.
5
+ # License: MIT Expat license
6
+ # This content of the class was copied from the GitLab repository
7
+ # @see https://gitlab.com/gitlab-org/gitlab/-/blob/f59f7aa0d86f07496e68abf7172edd703669e7bd/lib/gitlab/graphql/query_analyzers/ast/recursion_analyzer.rb
8
+ # To which I have modified the result format to be compatible with decidim-api.
9
+
10
+ module Decidim
11
+ module Api
12
+ class RecursionAnalyzer < GraphQL::Analysis::AST::Analyzer
13
+ IGNORED_FIELDS = %w(node edges nodes ofType).freeze
14
+ RECURSION_THRESHOLD = 2
15
+
16
+ def initialize(query)
17
+ super
18
+
19
+ @node_visits = {}
20
+ @recurring_fields = {}
21
+ end
22
+
23
+ def on_enter_field(node, _parent, visitor)
24
+ return if skip_node?(node, visitor)
25
+
26
+ node_name = node.name
27
+ node_visits[node_name] ||= 0
28
+ node_visits[node_name] += 1
29
+
30
+ times_encountered = @node_visits[node_name]
31
+ recurring_fields[node_name] = times_encountered if recursion_too_deep?(node_name, times_encountered)
32
+ end
33
+
34
+ # Visitors are all defined on the AST::Analyzer base class
35
+ # We override them for custom analyzers.
36
+ def on_leave_field(node, _parent, visitor)
37
+ return if skip_node?(node, visitor)
38
+
39
+ node_name = node.name
40
+ node_visits[node_name] ||= 0
41
+ node_visits[node_name] -= 1
42
+ end
43
+
44
+ def result
45
+ @recurring_fields = @recurring_fields.select { |k, v| recursion_too_deep?(k, v) }
46
+
47
+ Decidim::Api::Errors::RecursionLimitExceededError.new I18n.t("decidim.api.errors.recursion_limit_exceeded_error") if @recurring_fields.any?
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :node_visits, :recurring_fields
53
+
54
+ def recursion_too_deep?(node_name, times_encountered)
55
+ return false if IGNORED_FIELDS.include?(node_name)
56
+
57
+ times_encountered > recursion_threshold
58
+ end
59
+
60
+ def skip_node?(node, visitor)
61
+ # We do not want to count skipped fields or fields
62
+ # inside fragment definitions
63
+ return false if visitor.skipping? || visitor.visiting_fragment_definition?
64
+
65
+ !node.is_a?(GraphQL::Language::Nodes::Field) || node.selections.empty?
66
+ end
67
+
68
+ # Separated into a method to allow overriding or customization of the recursion limit.
69
+ def recursion_threshold
70
+ RECURSION_THRESHOLD
71
+ end
72
+ end
73
+ end
74
+ end
@@ -7,10 +7,16 @@ module Decidim
7
7
  mutation(MutationType)
8
8
  query(QueryType)
9
9
 
10
+ introspection(DecidimIntrospection)
11
+ query_analyzer RecursionAnalyzer
12
+ query_analyzer AliasAnalyzer
13
+
10
14
  default_max_page_size Decidim::Api.schema_max_per_page
11
15
  max_depth Decidim::Api.schema_max_depth
12
16
  max_complexity Decidim::Api.schema_max_complexity
13
17
 
18
+ query_analyzer AliasAnalyzer
19
+
14
20
  orphan_types(Api.orphan_types)
15
21
  end
16
22
  end
@@ -4,6 +4,7 @@ require "decidim/api/test/type_context"
4
4
 
5
5
  shared_context "with a graphql decidim component" do
6
6
  include_context "with a graphql class type"
7
+ include_examples "when the introspection is disabled"
7
8
 
8
9
  let(:schema) { Decidim::Api::Schema }
9
10
 
@@ -8,6 +8,7 @@ shared_context "with a graphql class type" do
8
8
  let(:type_class) { described_class }
9
9
  let(:variables) { {} }
10
10
  let(:root_value) { model }
11
+ let(:can_introspect) { Decidim::Api.enable_anonymous_introspection || current_user&.admin? }
11
12
 
12
13
  let(:schema) do
13
14
  klass = type_class
@@ -21,6 +22,20 @@ shared_context "with a graphql class type" do
21
22
  execute_query query, variables.stringify_keys
22
23
  end
23
24
 
25
+ def raise_proper_error(error)
26
+ code = error.dig("extensions", "code")
27
+
28
+ # Matches the error code with the Error class
29
+ # For instance, if the error code is NOT_FOUND_ERROR then it will raise the "Decidim::Api::Errors::NotFoundError" class
30
+ raise "Decidim::Api::Errors::#{code.downcase.classify}".constantize, error["message"] if %w(
31
+ INTROSPECTION_DISABLED_ERROR
32
+ TOO_MANY_ALIASES_ERROR
33
+ RECURSION_LIMIT_EXCEEDED_ERROR
34
+ ).include?(code)
35
+
36
+ raise GraphQL::ExecutionError, error["message"]
37
+ end
38
+
24
39
  def execute_query(query, variables)
25
40
  result = schema.execute(
26
41
  query,
@@ -28,17 +43,93 @@ shared_context "with a graphql class type" do
28
43
  context: {
29
44
  current_organization:,
30
45
  current_user:,
31
- current_component:
46
+ current_component:,
47
+ can_introspect:
32
48
  },
33
49
  variables:
34
50
  )
35
51
 
36
- raise StandardError, result["errors"].map { |e| e["message"] }.join(", ") if result["errors"]
52
+ raise_proper_error(result["errors"].first) if result["errors"]
37
53
 
38
54
  result["data"]
39
55
  end
40
56
  end
41
57
 
58
+ shared_examples "when the introspection is disabled" do
59
+ shared_examples "check introspection behavior" do
60
+ context "and the user is not authenticated" do
61
+ let!(:current_user) { nil }
62
+
63
+ it "raises an Decidim::Api::Errors::IntrospectionDisabledError" do
64
+ expect { response }.to raise_error(Decidim::Api::Errors::IntrospectionDisabledError, "Introspection is disabled for this request")
65
+ end
66
+ end
67
+
68
+ context "and the user is not an admin" do
69
+ let!(:current_user) { create(:user, :confirmed, organization: current_organization) }
70
+
71
+ it "raises an Decidim::Api::Errors::IntrospectionDisabledError" do
72
+ expect { response }.to raise_error(Decidim::Api::Errors::IntrospectionDisabledError, "Introspection is disabled for this request")
73
+ end
74
+ end
75
+
76
+ context "and the user is an admin" do
77
+ let!(:current_user) { create(:user, :confirmed, :admin, organization: current_organization) }
78
+
79
+ it "runs successfully" do
80
+ expect { response }.not_to raise_error
81
+ end
82
+ end
83
+
84
+ context "and the setting is true" do
85
+ before do
86
+ allow(Decidim::Api).to receive(:enable_anonymous_introspection).and_return(true)
87
+ end
88
+
89
+ it "runs successfully" do
90
+ expect { response }.not_to raise_error
91
+ end
92
+ end
93
+
94
+ context "and the setting is false" do
95
+ before do
96
+ allow(Decidim::Api).to receive(:enable_anonymous_introspection).and_return(false)
97
+ end
98
+ it "raises an Decidim::Api::Errors::IntrospectionDisabledError" do
99
+ expect { response }.to raise_error(Decidim::Api::Errors::IntrospectionDisabledError, "Introspection is disabled for this request")
100
+ end
101
+ end
102
+ end
103
+
104
+ context "when requesting the schema introspection" do
105
+ let(:query) do
106
+ %( query { __schema { types { fields { type { fields { type { name } } } } } } } )
107
+ end
108
+
109
+ it_behaves_like "check introspection behavior"
110
+ end
111
+
112
+ context "when requesting the type introspection" do
113
+ let(:query) do
114
+ %( query CircularIntrospection {
115
+ __type(name: "User") {
116
+ fields {
117
+ type {
118
+ fields {
119
+ type {
120
+ name
121
+ }
122
+ }
123
+ }
124
+ }
125
+ }
126
+ } )
127
+ end
128
+
129
+ it_behaves_like "check introspection behavior"
130
+ end
131
+ end
132
+
42
133
  shared_context "with a graphql scalar class type" do
43
134
  include_context "with a graphql class type"
44
135
 
@@ -1,3 +1,4 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "decidim/api/test/shared_examples/statistics_examples"
4
+ require "decidim/api/test/type_context"
@@ -2,9 +2,18 @@
2
2
 
3
3
  module Decidim
4
4
  module Api
5
+ autoload :AliasAnalyzer, "decidim/api/alias_analyzer"
5
6
  autoload :QueryType, "decidim/api/query_type"
6
7
  autoload :MutationType, "decidim/api/mutation_type"
7
8
  autoload :Schema, "decidim/api/schema"
9
+ autoload :DecidimIntrospection, "decidim/api/decidim_introspection"
10
+ autoload :RecursionAnalyzer, "decidim/api/recursion_analyzer"
11
+
12
+ module Errors
13
+ autoload :IntrospectionDisabledError, "decidim/api/errors/introspection_disabled_error"
14
+ autoload :RecursionLimitExceededError, "decidim/api/errors/recursion_limit_exceeded_error"
15
+ autoload :TooManyAliasesError, "decidim/api/errors/too_many_aliases_error"
16
+ end
8
17
 
9
18
  module Types
10
19
  autoload :BaseArgument, "decidim/api/types/base_argument"
@@ -4,7 +4,7 @@ module Decidim
4
4
  # This holds the decidim-api version.
5
5
  module Api
6
6
  def self.version
7
- "0.30.4"
7
+ "0.30.5"
8
8
  end
9
9
  end
10
10
  end
data/lib/decidim/api.rb CHANGED
@@ -19,6 +19,11 @@ module Decidim
19
19
  5000
20
20
  end
21
21
 
22
+ # defines how many aliases are permitted in a query
23
+ config_accessor :max_aliases do
24
+ ENV.fetch("API_SCHEMA_MAX_ALIASES", 5).to_i
25
+ end
26
+
22
27
  # defines the schema max_depth to configure GraphQL query max_depth
23
28
  config_accessor :schema_max_depth do
24
29
  15
@@ -28,6 +33,13 @@ module Decidim
28
33
  %w(1 true yes).include?(ENV.fetch("DECIDIM_API_DISCLOSE_SYSTEM_VERSION", nil))
29
34
  end
30
35
 
36
+ # allows anonymous introspection queries
37
+ # If you are not sure, leave it set to false. In this way only administrator users will be able to access the introspection query.
38
+ # Otherwise, anyone can access it, causing security issues.
39
+ config_accessor :enable_anonymous_introspection do
40
+ ENV.fetch("DECIDIM_API_ENABLE_ANONYMOUS_INTROSPECTION", nil) == "true"
41
+ end
42
+
31
43
  # This declares all the types an interface or union can resolve to. This needs
32
44
  # to be done in order to be able to have them found. This is a shortcoming of
33
45
  # graphql-ruby and the way it deals with loading types, in combination with
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: decidim-api
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.4
4
+ version: 0.30.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Josep Jaume Rey Peroy
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2025-11-20 00:00:00.000000000 Z
13
+ date: 2026-01-28 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: decidim-core
@@ -18,14 +18,14 @@ dependencies:
18
18
  requirements:
19
19
  - - '='
20
20
  - !ruby/object:Gem::Version
21
- version: 0.30.4
21
+ version: 0.30.5
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - '='
27
27
  - !ruby/object:Gem::Version
28
- version: 0.30.4
28
+ version: 0.30.5
29
29
  - !ruby/object:Gem::Dependency
30
30
  name: graphql
31
31
  requirement: !ruby/object:Gem::Requirement
@@ -74,56 +74,56 @@ dependencies:
74
74
  requirements:
75
75
  - - '='
76
76
  - !ruby/object:Gem::Version
77
- version: 0.30.4
77
+ version: 0.30.5
78
78
  type: :development
79
79
  prerelease: false
80
80
  version_requirements: !ruby/object:Gem::Requirement
81
81
  requirements:
82
82
  - - '='
83
83
  - !ruby/object:Gem::Version
84
- version: 0.30.4
84
+ version: 0.30.5
85
85
  - !ruby/object:Gem::Dependency
86
86
  name: decidim-comments
87
87
  requirement: !ruby/object:Gem::Requirement
88
88
  requirements:
89
89
  - - '='
90
90
  - !ruby/object:Gem::Version
91
- version: 0.30.4
91
+ version: 0.30.5
92
92
  type: :development
93
93
  prerelease: false
94
94
  version_requirements: !ruby/object:Gem::Requirement
95
95
  requirements:
96
96
  - - '='
97
97
  - !ruby/object:Gem::Version
98
- version: 0.30.4
98
+ version: 0.30.5
99
99
  - !ruby/object:Gem::Dependency
100
100
  name: decidim-dev
101
101
  requirement: !ruby/object:Gem::Requirement
102
102
  requirements:
103
103
  - - '='
104
104
  - !ruby/object:Gem::Version
105
- version: 0.30.4
105
+ version: 0.30.5
106
106
  type: :development
107
107
  prerelease: false
108
108
  version_requirements: !ruby/object:Gem::Requirement
109
109
  requirements:
110
110
  - - '='
111
111
  - !ruby/object:Gem::Version
112
- version: 0.30.4
112
+ version: 0.30.5
113
113
  - !ruby/object:Gem::Dependency
114
114
  name: decidim-participatory_processes
115
115
  requirement: !ruby/object:Gem::Requirement
116
116
  requirements:
117
117
  - - '='
118
118
  - !ruby/object:Gem::Version
119
- version: 0.30.4
119
+ version: 0.30.5
120
120
  type: :development
121
121
  prerelease: false
122
122
  version_requirements: !ruby/object:Gem::Requirement
123
123
  requirements:
124
124
  - - '='
125
125
  - !ruby/object:Gem::Version
126
- version: 0.30.4
126
+ version: 0.30.5
127
127
  description: API engine for decidim
128
128
  email:
129
129
  - josepjaume@gmail.com
@@ -148,15 +148,87 @@ files:
148
148
  - app/views/decidim/api/graphiql/show.html.erb
149
149
  - app/views/layouts/decidim/api/documentation.html.erb
150
150
  - config/assets.rb
151
+ - config/locales/am-ET.yml
152
+ - config/locales/ar.yml
153
+ - config/locales/bg.yml
154
+ - config/locales/bn-BD.yml
155
+ - config/locales/bs-BA.yml
156
+ - config/locales/ca-IT.yml
157
+ - config/locales/ca.yml
158
+ - config/locales/cs.yml
159
+ - config/locales/da.yml
160
+ - config/locales/de.yml
161
+ - config/locales/el.yml
162
+ - config/locales/en.yml
163
+ - config/locales/eo.yml
164
+ - config/locales/es-MX.yml
165
+ - config/locales/es-PY.yml
166
+ - config/locales/es.yml
167
+ - config/locales/et.yml
168
+ - config/locales/eu.yml
169
+ - config/locales/fa-IR.yml
170
+ - config/locales/fi-plain.yml
171
+ - config/locales/fi.yml
172
+ - config/locales/fr-CA.yml
173
+ - config/locales/fr.yml
174
+ - config/locales/ga-IE.yml
175
+ - config/locales/gl.yml
176
+ - config/locales/gn-PY.yml
177
+ - config/locales/he-IL.yml
178
+ - config/locales/hr.yml
179
+ - config/locales/hu.yml
180
+ - config/locales/id-ID.yml
181
+ - config/locales/is-IS.yml
182
+ - config/locales/it.yml
183
+ - config/locales/ja.yml
184
+ - config/locales/ka-GE.yml
185
+ - config/locales/kaa.yml
186
+ - config/locales/ko.yml
187
+ - config/locales/lb.yml
188
+ - config/locales/lo-LA.yml
189
+ - config/locales/lt.yml
190
+ - config/locales/lv.yml
191
+ - config/locales/mt.yml
192
+ - config/locales/nl.yml
193
+ - config/locales/no.yml
194
+ - config/locales/oc-FR.yml
195
+ - config/locales/om-ET.yml
196
+ - config/locales/pl.yml
197
+ - config/locales/pt-BR.yml
198
+ - config/locales/pt.yml
199
+ - config/locales/ro-RO.yml
200
+ - config/locales/ru.yml
201
+ - config/locales/si-LK.yml
202
+ - config/locales/sk.yml
203
+ - config/locales/sl.yml
204
+ - config/locales/so-SO.yml
205
+ - config/locales/sq-AL.yml
206
+ - config/locales/sr-CS.yml
207
+ - config/locales/sv.yml
208
+ - config/locales/sw-KE.yml
209
+ - config/locales/th-TH.yml
210
+ - config/locales/ti-ER.yml
211
+ - config/locales/tr-TR.yml
212
+ - config/locales/uk.yml
213
+ - config/locales/val-ES.yml
214
+ - config/locales/vi.yml
215
+ - config/locales/zh-CN.yml
216
+ - config/locales/zh-TW.yml
151
217
  - config/routes.rb
152
218
  - decidim-api.gemspec
153
219
  - docs/usage.md
154
220
  - lib/decidim/api.rb
221
+ - lib/decidim/api/alias_analyzer.rb
222
+ - lib/decidim/api/decidim_introspection.rb
155
223
  - lib/decidim/api/engine.rb
224
+ - lib/decidim/api/errors/introspection_disabled_error.rb
225
+ - lib/decidim/api/errors/recursion_limit_exceeded_error.rb
226
+ - lib/decidim/api/errors/too_many_aliases_error.rb
156
227
  - lib/decidim/api/graphiql-initial-query.txt
157
228
  - lib/decidim/api/graphiql/config.rb
158
229
  - lib/decidim/api/mutation_type.rb
159
230
  - lib/decidim/api/query_type.rb
231
+ - lib/decidim/api/recursion_analyzer.rb
160
232
  - lib/decidim/api/schema.rb
161
233
  - lib/decidim/api/test.rb
162
234
  - lib/decidim/api/test/component_context.rb