pdnd-ruby-client 0.1.1

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c13a6f6c53adb114f00fb2e5a8c3a1678ac18e674b69cb120f39d9770d923946
4
+ data.tar.gz: 36806f273b1e1cf8c75e811f55d7e7a6151cecf5b094bab6451bb05e7dc8811c
5
+ SHA512:
6
+ metadata.gz: 652a135f762aef625ecc67fbeab5c12491c719614c74ba220a70673b34073a288d3ecb9554e7c16d0443ac301fa5525f69a613acb0d319214340722ffe61af2c
7
+ data.tar.gz: ec24d9ffcfc9ac7bab1e1d7e39400ed0bc7dd75e835be58f953275e41865fa69b6d5a74dce3a4c441b9175516d03e272ad906db9afc0c0d724f2fea27943afb8
data/.env.sample ADDED
@@ -0,0 +1,6 @@
1
+ set PDND_KID=tuo_kid
2
+ set PDND_ISSUER=tuo_issuer
3
+ set PDND_CLIENT_ID=tuo_client_id
4
+ set PDND_PURPOSE_ID=tuo_purpose_id
5
+ set PDND_PRIVKEY_PATH=tuo_path
6
+ set PDND_URL=tuo_url
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --require spec_helper
data/.rubocop.yml ADDED
@@ -0,0 +1,291 @@
1
+ AllCops:
2
+ NewCops: enable
3
+ SuggestExtensions: false
4
+ Exclude:
5
+ - 'bin/console'
6
+
7
+
8
+ Gemspec/AddRuntimeDependency: # new in 1.65
9
+ Enabled: true
10
+ Gemspec/AttributeAssignment: # new in 1.77
11
+ Enabled: true
12
+ Gemspec/DeprecatedAttributeAssignment: # new in 1.30
13
+ Enabled: true
14
+ Gemspec/DevelopmentDependencies: # new in 1.44
15
+ Enabled: true
16
+ Gemspec/RequireMFA: # new in 1.23
17
+ Enabled: true
18
+ Layout/EmptyLinesAfterModuleInclusion: # new in 1.79
19
+ Enabled: true
20
+ Layout/LineContinuationLeadingSpace: # new in 1.31
21
+ Enabled: true
22
+ Layout/LineContinuationSpacing: # new in 1.31
23
+ Enabled: true
24
+ Layout/LineEndStringConcatenationIndentation: # new in 1.18
25
+ Enabled: true
26
+ Layout/SpaceBeforeBrackets: # new in 1.7
27
+ Enabled: true
28
+ Lint/AmbiguousAssignment: # new in 1.7
29
+ Enabled: true
30
+ Lint/AmbiguousOperatorPrecedence: # new in 1.21
31
+ Enabled: true
32
+ Lint/AmbiguousRange: # new in 1.19
33
+ Enabled: true
34
+ Lint/ArrayLiteralInRegexp: # new in 1.71
35
+ Enabled: true
36
+ Lint/ConstantOverwrittenInRescue: # new in 1.31
37
+ Enabled: true
38
+ Lint/ConstantReassignment: # new in 1.70
39
+ Enabled: true
40
+ Lint/CopDirectiveSyntax: # new in 1.72
41
+ Enabled: true
42
+ Lint/DeprecatedConstants: # new in 1.8
43
+ Enabled: true
44
+ Lint/DuplicateBranch: # new in 1.3
45
+ Enabled: true
46
+ Lint/DuplicateMagicComment: # new in 1.37
47
+ Enabled: true
48
+ Lint/DuplicateMatchPattern: # new in 1.50
49
+ Enabled: true
50
+ Lint/DuplicateRegexpCharacterClassElement: # new in 1.1
51
+ Enabled: true
52
+ Lint/DuplicateSetElement: # new in 1.67
53
+ Enabled: true
54
+ Lint/EmptyBlock: # new in 1.1
55
+ Enabled: true
56
+ Lint/EmptyClass: # new in 1.3
57
+ Enabled: true
58
+ Lint/EmptyInPattern: # new in 1.16
59
+ Enabled: true
60
+ Lint/HashNewWithKeywordArgumentsAsDefault: # new in 1.69
61
+ Enabled: true
62
+ Lint/IncompatibleIoSelectWithFiberScheduler: # new in 1.21
63
+ Enabled: true
64
+ Lint/ItWithoutArgumentsInBlock: # new in 1.59
65
+ Enabled: true
66
+ Lint/LambdaWithoutLiteralBlock: # new in 1.8
67
+ Enabled: true
68
+ Lint/LiteralAssignmentInCondition: # new in 1.58
69
+ Enabled: true
70
+ Lint/MixedCaseRange: # new in 1.53
71
+ Enabled: true
72
+ Lint/NoReturnInBeginEndBlocks: # new in 1.2
73
+ Enabled: true
74
+ Lint/NonAtomicFileOperation: # new in 1.31
75
+ Enabled: true
76
+ Lint/NumberedParameterAssignment: # new in 1.9
77
+ Enabled: true
78
+ Lint/NumericOperationWithConstantResult: # new in 1.69
79
+ Enabled: true
80
+ Lint/OrAssignmentToConstant: # new in 1.9
81
+ Enabled: true
82
+ Lint/RedundantDirGlobSort: # new in 1.8
83
+ Enabled: true
84
+ Lint/RedundantRegexpQuantifiers: # new in 1.53
85
+ Enabled: true
86
+ Lint/RedundantTypeConversion: # new in 1.72
87
+ Enabled: true
88
+ Lint/RefinementImportMethods: # new in 1.27
89
+ Enabled: true
90
+ Lint/RequireRangeParentheses: # new in 1.32
91
+ Enabled: true
92
+ Lint/RequireRelativeSelfPath: # new in 1.22
93
+ Enabled: true
94
+ Lint/SharedMutableDefault: # new in 1.70
95
+ Enabled: true
96
+ Lint/SuppressedExceptionInNumberConversion: # new in 1.72
97
+ Enabled: true
98
+ Lint/SymbolConversion: # new in 1.9
99
+ Enabled: true
100
+ Lint/ToEnumArguments: # new in 1.1
101
+ Enabled: true
102
+ Lint/TripleQuotes: # new in 1.9
103
+ Enabled: true
104
+ Lint/UnescapedBracketInRegexp: # new in 1.68
105
+ Enabled: true
106
+ Lint/UnexpectedBlockArity: # new in 1.5
107
+ Enabled: true
108
+ Lint/UnmodifiedReduceAccumulator: # new in 1.1
109
+ Enabled: true
110
+ Lint/UselessConstantScoping: # new in 1.72
111
+ Enabled: true
112
+ Lint/UselessDefaultValueArgument: # new in 1.76
113
+ Enabled: true
114
+ Lint/UselessDefined: # new in 1.69
115
+ Enabled: true
116
+ Lint/UselessNumericOperation: # new in 1.66
117
+ Enabled: true
118
+ Lint/UselessOr: # new in 1.76
119
+ Enabled: true
120
+ Lint/UselessRescue: # new in 1.43
121
+ Enabled: true
122
+ Lint/UselessRuby2Keywords: # new in 1.23
123
+ Enabled: true
124
+ Metrics/CollectionLiteralLength: # new in 1.47
125
+ Enabled: true
126
+ Metrics/AbcSize:
127
+ Enabled: false
128
+ Naming/FileName:
129
+ Enabled: false
130
+ Naming/BlockForwarding: # new in 1.24
131
+ Enabled: true
132
+ Naming/PredicateMethod: # new in 1.76
133
+ Enabled: true
134
+ Security/CompoundHash: # new in 1.28
135
+ Enabled: true
136
+ Security/IoMethods: # new in 1.22
137
+ Enabled: true
138
+ Style/AmbiguousEndlessMethodDefinition: # new in 1.68
139
+ Enabled: true
140
+ Style/ArgumentsForwarding: # new in 1.1
141
+ Enabled: true
142
+ Style/ArrayIntersect: # new in 1.40
143
+ Enabled: true
144
+ Style/BitwisePredicate: # new in 1.68
145
+ Enabled: true
146
+ Style/CollectionCompact: # new in 1.2
147
+ Enabled: true
148
+ Style/CollectionQuerying: # new in 1.77
149
+ Enabled: true
150
+ Style/CombinableDefined: # new in 1.68
151
+ Enabled: true
152
+ Style/ComparableBetween: # new in 1.74
153
+ Enabled: true
154
+ Style/ComparableClamp: # new in 1.44
155
+ Enabled: true
156
+ Style/ConcatArrayLiterals: # new in 1.41
157
+ Enabled: true
158
+ Style/DataInheritance: # new in 1.49
159
+ Enabled: true
160
+ Style/DigChain: # new in 1.69
161
+ Enabled: true
162
+ Style/DirEmpty: # new in 1.48
163
+ Enabled: true
164
+ Style/DocumentDynamicEvalDefinition: # new in 1.1
165
+ Enabled: true
166
+ Style/EmptyHeredoc: # new in 1.32
167
+ Enabled: true
168
+ Style/EmptyStringInsideInterpolation: # new in 1.76
169
+ Enabled: true
170
+ Style/EndlessMethod: # new in 1.8
171
+ Enabled: true
172
+ Style/EnvHome: # new in 1.29
173
+ Enabled: true
174
+ Style/ExactRegexpMatch: # new in 1.51
175
+ Enabled: true
176
+ Style/FetchEnvVar: # new in 1.28
177
+ Enabled: true
178
+ Style/FileEmpty: # new in 1.48
179
+ Enabled: true
180
+ Style/FileNull: # new in 1.69
181
+ Enabled: true
182
+ Style/FileRead: # new in 1.24
183
+ Enabled: true
184
+ Style/FileTouch: # new in 1.69
185
+ Enabled: true
186
+ Style/FileWrite: # new in 1.24
187
+ Enabled: true
188
+ Style/HashConversion: # new in 1.10
189
+ Enabled: true
190
+ Style/HashExcept: # new in 1.7
191
+ Enabled: true
192
+ Style/HashFetchChain: # new in 1.75
193
+ Enabled: true
194
+ Style/HashSlice: # new in 1.71
195
+ Enabled: true
196
+ Style/IfWithBooleanLiteralBranches: # new in 1.9
197
+ Enabled: true
198
+ Style/InPatternThen: # new in 1.16
199
+ Enabled: true
200
+ Style/ItAssignment: # new in 1.70
201
+ Enabled: true
202
+ Style/ItBlockParameter: # new in 1.75
203
+ Enabled: true
204
+ Style/KeywordArgumentsMerging: # new in 1.68
205
+ Enabled: true
206
+ Style/MagicCommentFormat: # new in 1.35
207
+ Enabled: true
208
+ Style/MapCompactWithConditionalBlock: # new in 1.30
209
+ Enabled: true
210
+ Style/MapIntoArray: # new in 1.63
211
+ Enabled: true
212
+ Style/MapToHash: # new in 1.24
213
+ Enabled: true
214
+ Style/MapToSet: # new in 1.42
215
+ Enabled: true
216
+ Style/MinMaxComparison: # new in 1.42
217
+ Enabled: true
218
+ Style/MultilineInPatternThen: # new in 1.16
219
+ Enabled: true
220
+ Style/NegatedIfElseCondition: # new in 1.2
221
+ Enabled: true
222
+ Style/NestedFileDirname: # new in 1.26
223
+ Enabled: true
224
+ Style/NilLambda: # new in 1.3
225
+ Enabled: true
226
+ Style/NumberedParameters: # new in 1.22
227
+ Enabled: true
228
+ Style/NumberedParametersLimit: # new in 1.22
229
+ Enabled: true
230
+ Style/ObjectThen: # new in 1.28
231
+ Enabled: true
232
+ Style/OpenStructUse: # new in 1.23
233
+ Enabled: true
234
+ Style/OperatorMethodCall: # new in 1.37
235
+ Enabled: true
236
+ Style/QuotedSymbols: # new in 1.16
237
+ Enabled: true
238
+ Style/RedundantArgument: # new in 1.4
239
+ Enabled: true
240
+ Style/RedundantArrayConstructor: # new in 1.52
241
+ Enabled: true
242
+ Style/RedundantArrayFlatten: # new in 1.76
243
+ Enabled: true
244
+ Style/RedundantConstantBase: # new in 1.40
245
+ Enabled: true
246
+ Style/RedundantCurrentDirectoryInPath: # new in 1.53
247
+ Enabled: true
248
+ Style/RedundantDoubleSplatHashBraces: # new in 1.41
249
+ Enabled: true
250
+ Style/RedundantEach: # new in 1.38
251
+ Enabled: true
252
+ Style/RedundantFilterChain: # new in 1.52
253
+ Enabled: true
254
+ Style/RedundantFormat: # new in 1.72
255
+ Enabled: true
256
+ Style/RedundantHeredocDelimiterQuotes: # new in 1.45
257
+ Enabled: true
258
+ Style/RedundantInitialize: # new in 1.27
259
+ Enabled: true
260
+ Style/RedundantInterpolationUnfreeze: # new in 1.66
261
+ Enabled: true
262
+ Style/RedundantLineContinuation: # new in 1.49
263
+ Enabled: true
264
+ Style/RedundantRegexpArgument: # new in 1.53
265
+ Enabled: true
266
+ Style/RedundantRegexpConstructor: # new in 1.52
267
+ Enabled: true
268
+ Style/RedundantSelfAssignmentBranch: # new in 1.19
269
+ Enabled: true
270
+ Style/RedundantStringEscape: # new in 1.37
271
+ Enabled: true
272
+ Style/ReturnNilInPredicateMethodDefinition: # new in 1.53
273
+ Enabled: true
274
+ Style/SafeNavigationChainLength: # new in 1.68
275
+ Enabled: true
276
+ Style/SelectByRegexp: # new in 1.22
277
+ Enabled: true
278
+ Style/SendWithLiteralMethodName: # new in 1.64
279
+ Enabled: true
280
+ Style/SingleLineDoEndBlock: # new in 1.57
281
+ Enabled: true
282
+ Style/StringChars: # new in 1.12
283
+ Enabled: true
284
+ Style/SuperArguments: # new in 1.64
285
+ Enabled: true
286
+ Style/SuperWithArgsParentheses: # new in 1.58
287
+ Enabled: true
288
+ Style/SwapValues: # new in 1.1
289
+ Enabled: true
290
+ Style/YAMLFileRead: # new in 1.53
291
+ Enabled: true
data/CHANGELOG.md ADDED
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+
4
+
5
+ \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Istituto Superiore per la Protezione e la Ricerca Ambientale (ISPRA)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,251 @@
1
+ # pdnd-ruby-client ![CI](https://img.shields.io/github/actions/workflow/status/isprambiente/pdnd-ruby-client/README.md) ![Versione Ruby](https://img.shields.io/badge/ruby-%3E%3D3.2-blue) ![Licenza MIT](https://img.shields.io/badge/license-MIT-green)
2
+
3
+ Client Ruby per autenticazione e interazione con le API della Piattaforma Digitale Nazionale Dati (PDND).
4
+
5
+ ## Licenza
6
+
7
+ MIT
8
+
9
+ ## Requisiti
10
+
11
+ - Ruby >= 3.2 (versioni precedenti sono [EOL](https://endoflife.date/ruby))
12
+
13
+ ## Installazione
14
+
15
+ 1. Installa la libreria al tuo Gemfile:
16
+ ```bash
17
+ gem 'pdnd-ruby-client'
18
+ ```
19
+
20
+ 2. Configura il file JSON con i parametri richiesti (esempio in `configs/sample.json`):
21
+ ```json
22
+ {
23
+ "collaudo": {
24
+ "kid": "kid",
25
+ "issuer": "issuer",
26
+ "clientId": "clientId",
27
+ "purposeId": "purposeId",
28
+ "privKeyPath": "/tmp/key.priv"
29
+ },
30
+ "produzione": {
31
+ "kid": "kid",
32
+ "issuer": "issuer",
33
+ "clientId": "clientId",
34
+ "purposeId": "purposeId",
35
+ "privKeyPath": "/tmp/key.priv"
36
+ }
37
+ }
38
+ ```
39
+ ## Istruzioni base
40
+
41
+ ```ruby
42
+ require "pdnd-ruby-client"
43
+
44
+ # Inizializza la configurazione
45
+ # Load the configuration from the specified JSON file and environment key.
46
+ config = PDND::ConfigLoader.load("configs/sample.json")
47
+ jwt = PDND::JWTGenerator.new(config)
48
+ token, exp = jwt.generate_token
49
+ client = PDND::Client.new(config)
50
+ client.token = token
51
+ client.token_exp = exp
52
+ client.api_url="https://www.tuogateway.example.it/indirizzo/della/api"
53
+ client.filters="id=1234"
54
+ code, body = client.request_api
55
+
56
+ # Stampa il risultato
57
+ puts body
58
+
59
+ ```
60
+
61
+ ## Leggi e Salva il token
62
+
63
+ ```ruby
64
+
65
+ require "pdnd-ruby-client"
66
+
67
+ # Inizializza la configurazione
68
+ # Load the configuration from the specified JSON file and environment key.
69
+ config = PDND::ConfigLoader.load("configs/sample.json")
70
+ # Inizializza il TokenManager per caricare o salvare i token su file esterno
71
+ token_mgr = PDND::TokenManager.new
72
+ # Carica il file salvato
73
+ token, exp = token_mgr.load
74
+ # Verifica se il token è stato precedentemente salvato ed è valido
75
+ if token.nil? || !token_mgr.valid?(exp)
76
+ # Se non è valido, genera un nuovo token
77
+ jwt = PDND::JWTGenerator.new(config)
78
+ token, exp = jwt.generate_token
79
+ # Salva il nuovo token
80
+ token_mgr.save(token, exp)
81
+ end
82
+ # Inizializza il client passando il token e la dada e ora di scadenza
83
+ client = PDND::Client.new(config)
84
+ client.token = token
85
+ client.token_exp = exp
86
+ # Imposta l'url dell'API
87
+ client.api_url="https://www.tuogateway.example.it/indirizzo/della/api"
88
+ # Imposta i paramentri per filtrare
89
+ # I parametri possono essere:
90
+ # - Stringa: "parametro1=1&parametro2=test&..."
91
+ # - Array: ["parametro1=1", "parametro2=test", ...]
92
+ # - Hash: {"parametro1" => 1, "parametro2" => "test", ... }
93
+ # sarà la funzione a verificare la tipologia e a convertire i filtri nel formato corretto.
94
+ # se il formato non fosse corretto, la funzione "request_api" restituisce errore.
95
+ client.filters="id=1234"
96
+ # Richiama la api
97
+ # @return code Number
98
+ # @return body Json
99
+ code, body = client.request_api
100
+
101
+ # Stampa il risultato
102
+ puts body
103
+
104
+ ```
105
+
106
+ ### Funzionalità aggiuntive
107
+
108
+ **Disabilita verifica certificato SSL**
109
+
110
+ La funzione `client.verify_ssl = false` Disabilita verifica SSL per ambiente impostato (es. collaudo).
111
+ Default: true
112
+
113
+ **Salva il token**
114
+
115
+ La funzione `token_mgr.save(token, exp)` consente a PDND::TokenManager di memorizzare il token e la scadenza e non doverlo richiedere a ogni chiamata.
116
+
117
+ **Carica il token salvato**
118
+
119
+ La funzione `token_mgr.load()` consente a PDND::TokenManager di richiamare il token precedentemente salvato.
120
+
121
+ **Valida il token salvato**
122
+
123
+ La funzione `token_mgr.valid?` PDND::TokenManager verifica la validità del token salvato.
124
+
125
+ **Imposta nome al token file**
126
+
127
+ La funzione `token_mgr.path("tmp/tuofile.json")` PDND::TokenManager imposta un nome personalizzato al file.
128
+
129
+ ## Utilizzo da CLI
130
+
131
+ Esegui il client dalla cartella principale:
132
+
133
+ ```ruby
134
+ ruby bin/pdnd_client.rb --api-url "https://api.pdnd.example.it/resource" --config /configs/sample.json
135
+ ```
136
+
137
+ ### Opzioni disponibili
138
+
139
+ - `--env` : Specifica l'ambiente da usare (es. collaudo, produzione). Default: `produzione`
140
+ - `--config` : Specifica il percorso completo del file di configurazione (es: `--config /configs/sample.json`)
141
+ - `--debug` : Abilita output dettagliato
142
+ - `--api-url` : URL dell’API da chiamare dopo la generazione del token
143
+ - `--api-url-filters` : Filtri da applicare all'API (es. ?parametro=valore)
144
+ - `--status-url` : URL dell’API di status per verificare la validità del token
145
+ - `--json`: Stampa le risposte delle API in formato JSON
146
+ - `--save`: Salva il token per evitare di richiederlo a ogni chiamata
147
+ - `--no-verify-ssl`: Disabilita la verifica SSL (utile per ambienti di collaudo)
148
+ - `--help`: Mostra questa schermata di aiuto
149
+
150
+ ### Esempi
151
+
152
+ **Chiamata API generica:**
153
+ ```bash
154
+ ruby bin/pdnd_client.rb --api-url="https://api.pdnd.example.it/resource" --config /configs/sample.json
155
+ ```
156
+
157
+ **Verifica validità token:**
158
+ ```bash
159
+ ruby bin/pdnd_client.rb --status-url="https://api.pdnd.example.it/status" --config /configs/sample.json
160
+ ```
161
+
162
+ **Debug attivo:**
163
+ ```bash
164
+ ruby bin/pdnd_client.rb --debug --api-url="https://api.pdnd.example.it/resource"
165
+ ```
166
+
167
+ ### Opzione di aiuto
168
+
169
+ Se esegui il comando con `--help` oppure senza parametri, viene mostrata una descrizione delle opzioni disponibili e alcuni esempi di utilizzo:
170
+
171
+ ```bash
172
+ ruby bin/pdnd_client.rb --help
173
+ ```
174
+
175
+ **Output di esempio:**
176
+ ```
177
+ Utilizzo:
178
+ ruby bin/pdnd_client.rb -c /percorso/config.json [opzioni]
179
+
180
+ Opzioni:
181
+ --env Specifica l'ambiente da usare (es. collaudo, produzione)
182
+ Default: produzione
183
+ --config Specifica il percorso completo del file di configurazione
184
+ --debug Abilita output dettagliato
185
+ --api-url URL dell’API da chiamare dopo la generazione del token
186
+ --api-url-filters Filtri da applicare all'API (es. ?parametro=valore)
187
+ --status-url URL dell’API di status per verificare la validità del token
188
+ --json Stampa le risposte delle API in formato JSON
189
+ --save Salva il token per evitare di richiederlo a ogni chiamata
190
+ --no-verify-ssl Disabilita la verifica SSL (utile per ambienti di collaudo)
191
+ --help Mostra questa schermata di aiuto
192
+
193
+ Esempi:
194
+ ruby bin/pdnd_client.rb --api-url="https://api.pdnd.example.it/resource" --config /percorso/config.json
195
+ ruby bin/pdnd_client.rb --status-url="https://api.pdnd.example.it/status" --config /percorso/config.json
196
+ ruby bin/pdnd_client.rb --debug --api-url="https://api.pdnd.example.it/resource"
197
+ ```
198
+
199
+ ## Variabili di ambiente supportate
200
+
201
+ Se un parametro non è presente nel file di configurazione, puoi definirlo come variabile di ambiente:
202
+
203
+ - `PDND_KID`
204
+ - `PDND_ISSUER`
205
+ - `PDND_CLIENT_ID`
206
+ - `PDND_PURPOSE_ID`
207
+ - `PDND_PRIVKEY_PATH`
208
+
209
+ ## Note
210
+
211
+ - Il token viene salvato in un file temporaneo e riutilizzato finché è valido.
212
+ - Gli errori specifici vengono gestiti tramite la classe `PdndException`.
213
+
214
+ ## Esempio di configurazione minima
215
+
216
+ ```json
217
+ {
218
+ "produzione": {
219
+ "kid": "kid",
220
+ "issuer": "issuer",
221
+ "clientId": "clientId",
222
+ "purposeId": "purposeId",
223
+ "privKeyPath": "/tmp/key.pem"
224
+ }
225
+ }
226
+ ```
227
+ ## Esempio di configurazione per collaudo e prosuzione
228
+
229
+ ```json
230
+ {
231
+ "collaudo": {
232
+ "kid": "kid",
233
+ "issuer": "issuer",
234
+ "clientId": "clientId",
235
+ "purposeId": "purposeId",
236
+ "privKeyPath": "/tmp/key.pem"
237
+ },
238
+ "produzione": {
239
+ "kid": "kid",
240
+ "issuer": "issuer",
241
+ "clientId": "clientId",
242
+ "purposeId": "purposeId",
243
+ "privKeyPath": "/tmp/key.pem"
244
+ }
245
+ }
246
+ ```
247
+ ---
248
+
249
+ ## Contribuire
250
+
251
+ Le pull request sono benvenute! Per problemi o suggerimenti, apri una issue.
data/Rakefile ADDED
@@ -0,0 +1,4 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ task default: %i[]
@@ -0,0 +1,16 @@
1
+ {
2
+ "collaudo": {
3
+ "kid": "kid",
4
+ "issuer": "issuer",
5
+ "clientId": "clientId",
6
+ "purposeId": "purposeId",
7
+ "privKeyPath": "/tmp/key.pem"
8
+ },
9
+ "produzione": {
10
+ "kid": "kid",
11
+ "issuer": "issuer",
12
+ "clientId": "clientId",
13
+ "purposeId": "purposeId",
14
+ "privKeyPath": "/tmp/key.pem"
15
+ }
16
+ }
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ # La classe PDND::Client è responsabile dell'invio di richieste HTTP all'API PDND.
4
+ # Gestisce autenticazione via token JWT, invio di richieste GET, parsing dei filtri,
5
+ # gestione dello stato, opzioni di debug e verifica del certificato SSL.
6
+
7
+ require 'net/http'
8
+ require 'json'
9
+ require 'faraday'
10
+
11
+ module PDND
12
+ # @!group PDND Client
13
+ # Questa classe gestisce le operazioni di comunicazione con il back-end PDND.
14
+ # @example
15
+ # client = PDND::Client.new(config)
16
+ # client.request_api
17
+ #
18
+ # @attr [String] token Il token di autenticazione JWT
19
+ # @attr [Boolean] debug Flag per attivare il logging in console
20
+ class Client
21
+ attr_accessor :token, :token_exp, :debug, :verify_ssl,
22
+ :api_url, :api_search, :status_url, :filters
23
+
24
+ def initialize(config)
25
+ @config = config
26
+ @verify_ssl = true
27
+ @debug = false
28
+ @api_url = ''
29
+ @api_search = ''
30
+ @status_url = ''
31
+ @filters = []
32
+ @token = ''
33
+ @token_exp = ''
34
+ end
35
+
36
+ def request_api
37
+ validate_api_config
38
+ build_api_uri
39
+ log_api_search
40
+
41
+ response = perform_request(@api_search.to_s)
42
+
43
+ raise PDND::APIError.new(response.status, response.body.force_encoding('UTF-8')) unless response.success?
44
+
45
+ puts "📡 Response: #{response.body}" if @debug
46
+ [response.status, JSON.parse(response.body)]
47
+ end
48
+
49
+ def check_status
50
+ validate_status_config
51
+
52
+ response = perform_request(@status_url)
53
+
54
+ puts "📡 Status response: #{response.body}" if @debug
55
+ [response.status, JSON.parse(response.body)]
56
+ end
57
+
58
+ private
59
+
60
+ def validate_api_config
61
+ raise PDND::APIError.new(0, 'URL dell\'API assente!') if @api_url.to_s.strip.empty?
62
+ raise PDND::APIError.new(0, 'Token assente!') if @token.to_s.strip.empty?
63
+ end
64
+
65
+ def validate_status_config
66
+ raise PDND::APIError.new(0, 'URL per controllare lo stato della API assente!') if @status_url.to_s.strip.empty?
67
+ raise PDND::APIError.new(0, 'Token assente!') if @token.to_s.strip.empty?
68
+ end
69
+
70
+ def build_api_uri
71
+ parse_filters
72
+ @api_search = URI(@api_url + (@filters ? "?#{@filters}" : ''))
73
+ end
74
+
75
+ def log_api_search
76
+ puts "📡 API SEARCH: #{@api_search}" if @debug
77
+ end
78
+
79
+ def perform_request(url)
80
+ conn = Faraday.new(url: url) do |faraday|
81
+ faraday.ssl.verify = false unless @verify_ssl
82
+ faraday.adapter Faraday.default_adapter
83
+ end
84
+
85
+ conn.get do |req|
86
+ req.headers['Authorization'] = "Bearer #{@token}"
87
+ end
88
+ end
89
+
90
+ def parse_filters
91
+ case @filters
92
+ when String
93
+ @filters
94
+ when Hash
95
+ URI.encode_www_form(flatten_filter_hash(@filters))
96
+ when Array
97
+ @filters.join('&')
98
+ else
99
+ raise ArgumentError, '❌ I filtri devono essere una stringa, un hash o un array.'
100
+ end
101
+ end
102
+
103
+ def flatten_filter_hash(hash)
104
+ hash.each_with_object({}) do |(key, val), acc|
105
+ if val.is_a?(Array)
106
+ val.each do |v|
107
+ acc["#{key}[]"] ||= []
108
+ acc["#{key}[]"] << v
109
+ end
110
+ else
111
+ acc[key] = val
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/pdnd_ruby_client/config_loader.rb
4
+
5
+ require 'json'
6
+ require 'dotenv/load'
7
+
8
+ # La classe Config viene inizializzata con un percorso verso un file di configurazione e una chiave di ambiente.
9
+ # Legge la configurazione dal file e la memorizza Hash.
10
+ module PDND
11
+ # Carica la configurazione da un file JSON e restituisce i parametri per l'ambiente specificato.
12
+ # Utilizzato per inizializzare i client PDND con le credenziali corrette.
13
+ class ConfigLoader
14
+ def self.load(path, env = 'produzione')
15
+ config = JSON.parse(File.read(path))
16
+ raise "❌ Ambiente '#{env}' non trovato" unless config[env]
17
+
18
+ config[env].transform_keys(&:to_sym)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ # 📎 Modulo contenente le classi di errore personalizzate per PDND Ruby Client.
4
+ module PDND
5
+ # Errore generico
6
+ class Error < StandardError
7
+ def initialize(msg = 'Errore generico PDND')
8
+ super
9
+ end
10
+ end
11
+
12
+ # Errore specifico per token scaduti o non validi
13
+ class TokenExpiredError < Error
14
+ def initialize
15
+ super('❌ Il token è scaduto o non valido')
16
+ end
17
+ end
18
+
19
+ # Errore di configurazione
20
+ class ConfigError < Error
21
+ def initialize(msg = '❌ Configurazione non valida')
22
+ super
23
+ end
24
+ end
25
+
26
+ # Errore restituito dalle API, con codice e corpo inclusi
27
+ class APIError < StandardError
28
+ def initialize(status, body)
29
+ super(body.dup.force_encoding('UTF-8'))
30
+ @status = status
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,165 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'jwt'
4
+ require 'openssl'
5
+ require 'faraday'
6
+ require 'json'
7
+ require 'securerandom'
8
+ require_relative 'errors'
9
+
10
+ # @!group JWT Generator
11
+ # Classe responsabile della generazione di token JWT per autenticare le richieste verso l'API PDND.
12
+ # Firma il token con una chiave RSA (algoritmo RS256) e lo invia al server OAuth2 per ottenere un access token.
13
+ # @example
14
+ # generator = PDND::JWTGenerator.new(config)
15
+ # token, exp = generator.generate_token
16
+ # @attr [Hash] config Configurazione con parametri JWT
17
+ # @attr [String] env Ambiente di esecuzione ('produzione' o 'collaudo')
18
+ # @attr [Boolean] debug Flag per attivare il logging
19
+ # @attr [String] token Token di accesso ottenuto
20
+ # @attr [String] token_exp Data di scadenza del token
21
+ module PDND
22
+ # @!group JWT Generator
23
+ # Classe responsabile della generazione di token JWT per autenticare le richieste verso l'API PDND.
24
+ # Firma il token con una chiave RSA (RS256) e lo invia al server OAuth2 per ottenere un access token.
25
+ # @example
26
+ # generator = PDND::JWTGenerator.new(config)
27
+ # token, exp = generator.generate_token
28
+ class JWTGenerator
29
+ attr_accessor :env, :config, :debug, :issuer, :client_id, :priv_key_path,
30
+ :purpose_id, :kid, :endpoint, :audience, :assertion,
31
+ :token, :token_exp
32
+
33
+ # @param config [Hash] Configurazione con chiavi come :issuer, :clientId, :privKeyPath, ecc.
34
+ # @param env [String] Ambiente ('produzione' o 'collaudo')
35
+ def initialize(config, env = 'produzione')
36
+ @config = config
37
+ @env = env
38
+ @assertion = ''
39
+ @token = ''
40
+ @token_exp = ''
41
+ @debug = false
42
+
43
+ assign_config_values(config)
44
+ configure_environment
45
+ end
46
+
47
+ # @return [Array<String>] Token di accesso e data di scadenza formattata
48
+ def generate_token
49
+ private_key = load_private_key
50
+ @assertion = JWT.encode(build_payload, private_key, 'RS256', build_header)
51
+ debug_log('🔐 Token JWT generato', @assertion)
52
+
53
+ response_body = post_assertion
54
+ @token = response_body['access_token']
55
+ @token_exp = format_expiration(response_body['expires_in'])
56
+
57
+ debug_log('✅ Token generato', @token)
58
+ debug_log('✅ Token scadenza', @token_exp)
59
+
60
+ [@token, @token_exp]
61
+ end
62
+
63
+ private
64
+
65
+ def assign_config_values(config)
66
+ @issuer = config[:issuer]
67
+ @client_id = config[:clientId]
68
+ @priv_key_path = config[:privKeyPath]
69
+ @kid = config[:kid]
70
+ @purpose_id = config[:purposeId]
71
+ end
72
+
73
+ # Imposta endpoint e audience in base all'ambiente
74
+ def configure_environment
75
+ if @env == 'collaudo'
76
+ @endpoint = 'https://auth.uat.interop.pagopa.it/token.oauth2'
77
+ @audience = 'auth.uat.interop.pagopa.it/client-assertion'
78
+ else
79
+ @endpoint = 'https://auth.interop.pagopa.it/token.oauth2'
80
+ @audience = 'auth.interop.pagopa.it/client-assertion'
81
+ end
82
+ end
83
+
84
+ # @return [OpenSSL::PKey::RSA] Chiave privata RSA
85
+ # @raise [PDND::ConfigError] Se la chiave è invalida o il file non esiste
86
+ def load_private_key
87
+ OpenSSL::PKey::RSA.new(File.read(@priv_key_path))
88
+ rescue OpenSSL::PKey::RSAError => e
89
+ raise PDND::ConfigError, "❌ Chiave privata non valida: #{e.message}"
90
+ rescue Errno::ENOENT => e
91
+ raise PDND::ConfigError, "❌ File della chiave non trovato: #{e.message}"
92
+ end
93
+
94
+ # @return [Hash] Payload JWT con claim standard
95
+ def build_payload
96
+ issued_at = Time.now.to_i
97
+ {
98
+ iss: @issuer,
99
+ sub: @client_id,
100
+ aud: @audience,
101
+ purposeId: @purpose_id,
102
+ jti: SecureRandom.hex(16),
103
+ iat: issued_at,
104
+ exp: issued_at + 300
105
+ }
106
+ end
107
+
108
+ # @return [Hash] Header JWT con chiave e algoritmo
109
+ def build_header
110
+ {
111
+ kid: @kid,
112
+ alg: 'RS256',
113
+ typ: 'JWT'
114
+ }
115
+ end
116
+
117
+ # @return [Hash] Corpo della risposta OAuth2 con access token
118
+ # @raise [PDND::APIError] Se la risposta HTTP è un errore
119
+ def post_assertion
120
+ response = send_assertion_request
121
+ raise_api_error(response) unless response.success?
122
+ JSON.parse(response.body)
123
+ end
124
+
125
+ # @return [Faraday::Response] Risposta HTTP dal server OAuth2
126
+ def send_assertion_request
127
+ Faraday.post(@endpoint) do |req|
128
+ req.headers['Content-Type'] = 'application/x-www-form-urlencoded'
129
+ req.headers['Accept'] = '*'
130
+ req.body = encoded_assertion_body
131
+ end
132
+ end
133
+
134
+ def encoded_assertion_body
135
+ URI.encode_www_form(
136
+ {
137
+ client_id: @client_id,
138
+ client_assertion: @assertion,
139
+ client_assertion_type: 'urn:ietf:params:oauth:client-assertion-type:jwt-bearer',
140
+ grant_type: 'client_credentials'
141
+ }
142
+ )
143
+ end
144
+
145
+ # @param response [Faraday::Response]
146
+ # @raise [PDND::APIError] con messaggio dettagliato
147
+ def raise_api_error(response)
148
+ parsed = JSON.parse(response.body)
149
+ message = parsed['error_description'] || parsed['error'] || response.body
150
+ raise PDND::APIError.new(response.status, message)
151
+ end
152
+
153
+ # @param expires_in [Integer] Secondi di validità
154
+ # @return [String] Timestamp formattato
155
+ def format_expiration(expires_in)
156
+ Time.at(Time.now.to_i + expires_in).strftime('%Y-%m-%d %H:%M:%S')
157
+ end
158
+
159
+ # @param title [String] Titolo del log
160
+ # @param value [String] Contenuto da stampare
161
+ def debug_log(title, value)
162
+ puts "\n#{title}:\n#{value}" if @debug
163
+ end
164
+ end
165
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # lib/pdnd_ruby_client/token_manager.rb
4
+
5
+ # Questa classe è responsabile del caricamento e salvataggio del token su file esterno.
6
+ module PDND
7
+ # Gestisce caricamento e salvataggio del token su file esterno.
8
+ class TokenManager
9
+ attr_reader :token, :exp, :path
10
+
11
+ def initialize(path = 'tmp/pdnd_token.json')
12
+ @path = path
13
+ @token = nil
14
+ @exp = nil
15
+ end
16
+
17
+ def load
18
+ return unless File.exist?(@path)
19
+
20
+ data = JSON.parse(File.read(@path))
21
+ @token = data['token']
22
+ @exp = data['exp']
23
+ end
24
+
25
+ def save(token, exp)
26
+ @token = token
27
+ @exp = exp
28
+ File.write(@path, { token: token, exp: exp }.to_json)
29
+ end
30
+
31
+ def valid?
32
+ @token && @exp && Time.now.to_i < @exp.to_i
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PDND
4
+ class ClientVersion
5
+ VERSION = '0.1.1'
6
+ end
7
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'pdnd-ruby-client/client'
4
+ require_relative 'pdnd-ruby-client/config_loader'
5
+ require_relative 'pdnd-ruby-client/errors'
6
+ require_relative 'pdnd-ruby-client/jwt_generator'
7
+ require_relative 'pdnd-ruby-client/token_manager'
8
+ require_relative 'pdnd-ruby-client/version'
@@ -0,0 +1,6 @@
1
+ module PDND
2
+ class ClientVersion
3
+ VERSION: String
4
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
5
+ end
6
+ end
data/tmp/.gitkeep ADDED
File without changes
@@ -0,0 +1 @@
1
+ {"token":"eyJhbGciOiJSUzI1NiIsInVzZSI6InNpZyIsInR5cCI6ImF0K2p3dCIsImtpZCI6Ijk0MzJjMTZiLTdhYWUtNDlkZi1iOWM0LWVhNjFiNTU2NjUyYiJ9.eyJqdGkiOiJlM2RjNDg2MS00NDkwLTRmMmItODQ3OC1mOThkMTM1YWRkNjUiLCJpc3MiOiJpbnRlcm9wLnBhZ29wYS5pdCIsImF1ZCI6IklTUFJBX3JlbmRpcyIsImNsaWVudF9pZCI6ImNjOWE1MWZlLTdhMzYtNGVjMC04MjI0LWMzZjliNWMyN2FjYSIsInN1YiI6ImNjOWE1MWZlLTdhMzYtNGVjMC04MjI0LWMzZjliNWMyN2FjYSIsImlhdCI6MTc1MzU0NTk1MywibmJmIjoxNzUzNTQ1OTUzLCJleHAiOjE3NTM1NDYxMzMsInB1cnBvc2VJZCI6IjFjYjY1NmJjLWVlY2MtNGM1YS1iZTU5LWJmZGE1YjdkMWNmNCIsInByb2R1Y2VySWQiOiIxNGRiOGI1Yy1jZWJhLTQzMzMtODYyYy1iODJiNjRlYmFhZGQiLCJjb25zdW1lcklkIjoiMTRkYjhiNWMtY2ViYS00MzMzLTg2MmMtYjgyYjY0ZWJhYWRkIiwiZXNlcnZpY2VJZCI6IjMyNjYyOGUxLWRkNzItNGI2MS04ZGRlLTRmOGY5NzEwODIxOSIsImRlc2NyaXB0b3JJZCI6IjhjNGE5NmQwLTBiNTMtNDI2MC04YzYyLWFlZThkNmMzODVlNyJ9.O_bjiaLSvHimuNubY-8gYQmzfnDPS381Lg9MOgFdhyN1HCPLhrLRqDDXu9nf-QMkuW5hkdwdeE04AXyHKVonexUK3waGW99eBZdXKpB07iKHpHPPyLxJs07R7VbABtJp668YiZW53CDqLU6kOoYIeBx1tLS-R7_5ZL68pCmu3KZ4yfD8vv0JzFrgxI6QSg3lbvRTT8ycAZruQ9VvNtQEBfJrXiXOuxkcDc-DfWVX7V8PSomgrnHpZbkp0ClXuGKN-Typ8aBB2gOFLb6gL1aL6trnAkZM9RoGjgB3YBGj3sduWgJ1wl49ZlK3SIjiNNqiR7sP2-8qtYbmeu0M_LQK-A","exp":"2025-07-26 18:08:52"}
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pdnd-ruby-client
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - Francesco Loreti
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: dotenv
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '2.8'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '2.8'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '2.13'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '2.13'
40
+ - !ruby/object:Gem::Dependency
41
+ name: json
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '2.0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '2.0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: jwt
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '3.1'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '3.1'
68
+ - !ruby/object:Gem::Dependency
69
+ name: net-http
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '0.3'
75
+ - - ">="
76
+ - !ruby/object:Gem::Version
77
+ version: 0.3.2
78
+ type: :runtime
79
+ prerelease: false
80
+ version_requirements: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - "~>"
83
+ - !ruby/object:Gem::Version
84
+ version: '0.3'
85
+ - - ">="
86
+ - !ruby/object:Gem::Version
87
+ version: 0.3.2
88
+ description: Client Ruby per interazione con le API della Piattaforma Digitale Nazionale
89
+ Dati (PDND).
90
+ email:
91
+ - francesco.loreti@isprambiente.it
92
+ executables: []
93
+ extensions: []
94
+ extra_rdoc_files: []
95
+ files:
96
+ - ".env.sample"
97
+ - ".rspec"
98
+ - ".rubocop.yml"
99
+ - CHANGELOG.md
100
+ - LICENSE
101
+ - README.md
102
+ - Rakefile
103
+ - configs/sample.json
104
+ - lib/pdnd-ruby-client.rb
105
+ - lib/pdnd-ruby-client/client.rb
106
+ - lib/pdnd-ruby-client/config_loader.rb
107
+ - lib/pdnd-ruby-client/errors.rb
108
+ - lib/pdnd-ruby-client/jwt_generator.rb
109
+ - lib/pdnd-ruby-client/token_manager.rb
110
+ - lib/pdnd-ruby-client/version.rb
111
+ - sig/pdnd-ruby-client.rbs
112
+ - tmp/.gitkeep
113
+ - tmp/pdnd_token.json
114
+ homepage: https://github.com/isprambiente/pdnd-ruby-client
115
+ licenses:
116
+ - MIT
117
+ metadata:
118
+ homepage_uri: https://github.com/isprambiente/pdnd-ruby-client
119
+ changelog_uri: https://github.com/isprambiente/pdnd-ruby-client/changelog.md
120
+ rubygems_mfa_required: 'true'
121
+ rdoc_options: []
122
+ require_paths:
123
+ - lib
124
+ required_ruby_version: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - ">="
127
+ - !ruby/object:Gem::Version
128
+ version: 3.2.0
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ requirements: []
135
+ rubygems_version: 3.7.1
136
+ specification_version: 4
137
+ summary: Client Ruby per la PDND.
138
+ test_files: []