onlyoffice-docs_integration_sdk 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. checksums.yaml +7 -0
  2. data/lib/onlyoffice/docs_integration_sdk/document_editor/config.rb +1479 -0
  3. data/lib/onlyoffice/docs_integration_sdk/document_editor/config_test.rb +1713 -0
  4. data/lib/onlyoffice/docs_integration_sdk/document_editor.rb +29 -0
  5. data/lib/onlyoffice/docs_integration_sdk/document_server/client/command.rb +417 -0
  6. data/lib/onlyoffice/docs_integration_sdk/document_server/client/command_test.rb +672 -0
  7. data/lib/onlyoffice/docs_integration_sdk/document_server/client/conversion.rb +477 -0
  8. data/lib/onlyoffice/docs_integration_sdk/document_server/client/conversion_test.rb +682 -0
  9. data/lib/onlyoffice/docs_integration_sdk/document_server/client/healthcheck.rb +101 -0
  10. data/lib/onlyoffice/docs_integration_sdk/document_server/client/healthcheck_test.rb +209 -0
  11. data/lib/onlyoffice/docs_integration_sdk/document_server/client/jwt.rb +116 -0
  12. data/lib/onlyoffice/docs_integration_sdk/document_server/client/jwt_test.rb +70 -0
  13. data/lib/onlyoffice/docs_integration_sdk/document_server/client/response.rb +73 -0
  14. data/lib/onlyoffice/docs_integration_sdk/document_server/client/response_test.rb +49 -0
  15. data/lib/onlyoffice/docs_integration_sdk/document_server/client/service.rb +44 -0
  16. data/lib/onlyoffice/docs_integration_sdk/document_server/client/ua.rb +31 -0
  17. data/lib/onlyoffice/docs_integration_sdk/document_server/client/ua_test.rb +35 -0
  18. data/lib/onlyoffice/docs_integration_sdk/document_server/client.rb +321 -0
  19. data/lib/onlyoffice/docs_integration_sdk/document_server/client_test.rb +1259 -0
  20. data/lib/onlyoffice/docs_integration_sdk/document_server.rb +29 -0
  21. data/lib/onlyoffice/docs_integration_sdk/document_storage/callback.rb +276 -0
  22. data/lib/onlyoffice/docs_integration_sdk/document_storage/callback_test.rb +291 -0
  23. data/lib/onlyoffice/docs_integration_sdk/document_storage.rb +29 -0
  24. data/lib/onlyoffice/docs_integration_sdk/jwt.rb +448 -0
  25. data/lib/onlyoffice/docs_integration_sdk/jwt_test.rb +598 -0
  26. data/lib/onlyoffice/docs_integration_sdk/test_test.rb +113 -0
  27. data/lib/onlyoffice/docs_integration_sdk/version.rb +26 -0
  28. data/lib/onlyoffice/docs_integration_sdk/version_test.rb +33 -0
  29. data/lib/onlyoffice/docs_integration_sdk.rb +31 -0
  30. data/lib/onlyoffice.rb +21 -0
  31. metadata +283 -0
@@ -0,0 +1,448 @@
1
+ #
2
+ # (c) Copyright Ascensio System SIA 2025
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ # typed: strict
18
+ # frozen_string_literal: true
19
+
20
+ require "json"
21
+ require "jwt"
22
+ require "sorbet-runtime"
23
+ require "uri"
24
+
25
+ module Onlyoffice
26
+ module DocsIntegrationSdk
27
+ # JwtEncoding is an interface that describes methods that must be
28
+ # implemented to be considered a JWT encoder.
29
+ #
30
+ # @since 0.1.0
31
+ module JwtEncoding
32
+ extend T::Sig
33
+ extend T::Helpers
34
+ interface!
35
+
36
+ # @param u The URI to encode.
37
+ # @return An encoded URI.
38
+ # @since 0.1.0
39
+ sig {abstract.params(u: URI::HTTP).returns(URI::HTTP)}
40
+ def encode_uri(u); end
41
+
42
+ # @param p The payload to encode.
43
+ # @return An encoded header.
44
+ # @since 0.1.0
45
+ sig {abstract.params(p: T.untyped).returns(String)}
46
+ def encode_header(p); end
47
+
48
+ # @param p The payload to encode.
49
+ # @return An encoded body.
50
+ # @since 0.1.0
51
+ sig {abstract.params(p: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])}
52
+ def encode_body(p); end
53
+
54
+ # @param p The payload to encode.
55
+ # @return An encoded token.
56
+ # @since 0.1.0
57
+ sig {abstract.params(p: T::Hash[T.untyped, T.untyped]).returns(String)}
58
+ def encode(p); end
59
+ end
60
+
61
+ # JwtDecoding is an interface that describes methods that must be
62
+ # implemented to be considered a JWT decoder.
63
+ #
64
+ # @since 0.1.0
65
+ module JwtDecoding
66
+ extend T::Sig
67
+ extend T::Helpers
68
+ interface!
69
+
70
+ # @param u The URI to decode.
71
+ # @return A decoded URI.
72
+ # @since 0.1.0
73
+ sig {abstract.params(u: URI::HTTP).returns(URI::HTTP)}
74
+ def decode_uri(u); end
75
+
76
+ # @param h The header to decode.
77
+ # @return A decoded header.
78
+ # @since 0.1.0
79
+ sig {abstract.params(h: String).returns(T.untyped)}
80
+ def decode_header(h); end
81
+
82
+ # @param b The body to decode.
83
+ # @return A decoded body.
84
+ # @since 0.1.0
85
+ sig {abstract.params(b: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])}
86
+ def decode_body(b); end
87
+
88
+ # @param t The token to decode.
89
+ # @return A decoded token.
90
+ # @since 0.1.0
91
+ sig {abstract.params(t: String).returns(T::Hash[T.untyped, T.untyped])}
92
+ def decode(t); end
93
+ end
94
+
95
+ # JwtCoding is an interface that describes methods that must be
96
+ # implemented to be considered a JWT encoder and decoder.
97
+ #
98
+ # @since 0.1.0
99
+ module JwtCoding
100
+ extend T::Sig
101
+ extend T::Helpers
102
+ include JwtEncoding
103
+ include JwtDecoding
104
+ interface!
105
+ end
106
+
107
+ # Jwt is an implementation of the {JwtCoding} interface. Under the hood, it
108
+ # uses Ruby JWT library to encode and decode JSON Web Tokens.
109
+ #
110
+ # This class has its own set of known claims. If an unfamiliar claim is
111
+ # passed to the class during its initialization, it will not be considered.
112
+ #
113
+ # Before the encoding process by Ruby JWT, this class forms a claims list.
114
+ # The {ExpClaim} will be included in the list only if its {ExpClaim.ttl} is
115
+ # not zero, otherwise it will be ignored.
116
+ #
117
+ # Before the decoding process by Ruby JWT, this class forms a claims list.
118
+ # The {ExpClaim} will be included in the list only if its {ExpClaim.leeway}
119
+ # is not zero, otherwise it will be ignored.
120
+ #
121
+ # [RFC 7519 Reference](https://datatracker.ietf.org/doc/html/rfc7519/),
122
+ # [Ruby JWT Reference](https://github.com/jwt/ruby-jwt/)
123
+ #
124
+ # @since 0.1.0
125
+ class Jwt
126
+ extend T::Sig
127
+ include JwtCoding
128
+
129
+ # Claim is an interface for claims that can be added to a JWT.
130
+ #
131
+ # [RFC 7519 Reference](https://datatracker.ietf.org/doc/html/rfc7519/#section-4.1)
132
+ #
133
+ # @since 0.1.0
134
+ module Claim
135
+ extend T::Sig
136
+ extend T::Helpers
137
+ include Kernel
138
+ interface!
139
+ end
140
+
141
+ # ExpClaim is a claim that represents the expiration time of a JWT.
142
+ #
143
+ # [RFC 7519 Reference](https://datatracker.ietf.org/doc/html/rfc7519/#section-4.1.4)
144
+ #
145
+ # @since 0.1.0
146
+ class ExpClaim
147
+ extend T::Sig
148
+ include Claim
149
+
150
+ # ttl is the time allowed for the token to be valid, measured in
151
+ # seconds.
152
+ #
153
+ # @since 0.1.0
154
+ sig {returns(Integer)}
155
+ attr_reader :ttl
156
+
157
+ # leeway is the time allowed to account for clock skew, measured in
158
+ # seconds.
159
+ #
160
+ # @since 0.1.0
161
+ sig {returns(Integer)}
162
+ attr_reader :leeway
163
+
164
+ # initialize initializes a new ExpClaim instance.
165
+ #
166
+ # @param ttl
167
+ # The time allowed for the token to be valid, measured in seconds.
168
+ # @param leeway
169
+ # The time allowed to account for clock skew, measured in seconds.
170
+ #
171
+ # @since 0.1.0
172
+ sig {params(ttl: Integer, leeway: Integer).void}
173
+ def initialize(ttl: 300, leeway: 30)
174
+ @ttl = ttl
175
+ @leeway = leeway
176
+ end
177
+ end
178
+
179
+ # IatClaim is a claim that represents the time at which the JWT was
180
+ # issued.
181
+ #
182
+ # [RFC 7519 Reference](https://datatracker.ietf.org/doc/html/rfc7519/#section-4.1.6)
183
+ #
184
+ # @since 0.1.0
185
+ class IatClaim
186
+ extend T::Sig
187
+ include Claim
188
+
189
+ # initialize initializes a new IatClaim instance.
190
+ #
191
+ # @since 0.1.0
192
+ sig {void}
193
+ def initialize; end
194
+ end
195
+
196
+ # secret is the secret key used to encode and decode JWTs.
197
+ #
198
+ # @since 0.1.0
199
+ sig {returns(String)}
200
+ attr_reader :secret
201
+
202
+ # algorithm is the algorithm used to encode and decode JWTs.
203
+ #
204
+ # @since 0.1.0
205
+ sig {returns(String)}
206
+ attr_reader :algorithm
207
+
208
+ # claims is the list of claims to add to the JWT.
209
+ #
210
+ # @since 0.1.0
211
+ sig {returns(T::Array[Claim])}
212
+ attr_reader :claims
213
+
214
+ # initialize initializes a new Jwt instance. It makes a shallow copy of
215
+ # the claims list.
216
+ #
217
+ # @param secret The secret key used to encode and decode JWTs.
218
+ # @param algorithm The algorithm used to encode and decode JWTs.
219
+ # @param claims The list of claims to add to the JWT.
220
+ # @since 0.1.0
221
+ sig {params(secret: String, algorithm: String, claims: T::Array[Claim]).void}
222
+ def initialize(secret:, algorithm: "HS256", claims: [ExpClaim.new, IatClaim.new])
223
+ @secret = secret
224
+ @algorithm = algorithm
225
+ @claims = T.let(claims.clone, T::Array[Claim])
226
+ end
227
+
228
+ # encode_uri encodes a URI by adding a token to the query string. This
229
+ # method returns a shallow copy of the URI with the token added. It does
230
+ # not modify the original URI.
231
+ #
232
+ # @example
233
+ # uri = jwt.encode_uri(uri)
234
+ #
235
+ # @param u The URI to encode.
236
+ # @return An encoded URI.
237
+ # @raise Inherited from {encode}.
238
+ # @since 0.1.0
239
+ sig {override.params(u: URI::HTTP).returns(URI::HTTP)}
240
+ def encode_uri(u)
241
+ u = u.clone
242
+
243
+ q = u.query
244
+ if q.nil?
245
+ q = ""
246
+ end
247
+
248
+ f = URI.decode_www_form(q)
249
+
250
+ t = encode({"url" => u.to_s})
251
+ f.append(["token", t])
252
+
253
+ u.query = URI.encode_www_form(f)
254
+
255
+ u
256
+ end
257
+
258
+ # encode_header encodes a payload by adding a token to the header.
259
+ #
260
+ # @example
261
+ # req["Authorization"] = "Bearer #{jwt.encode_header(payload)}"
262
+ #
263
+ # @param p The payload to encode.
264
+ # @return An encoded header.
265
+ # @raise Inherited from {encode}.
266
+ # @since 0.1.0
267
+ sig {override.params(p: T.untyped).returns(String)}
268
+ def encode_header(p)
269
+ encode({"payload" => p})
270
+ end
271
+
272
+ # encode_body encodes a payload by adding a token to the body. This method
273
+ # returns a shallow copy of the payload with the token added. It does not
274
+ # modify the original payload.
275
+ #
276
+ # @example
277
+ # req.body = jwt.encode_body(payload).to_json
278
+ #
279
+ # @param p The payload to encode.
280
+ # @return An encoded body.
281
+ # @raise Inherited from {encode}.
282
+ # @since 0.1.0
283
+ sig {override.params(p: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])}
284
+ def encode_body(p)
285
+ p = p.clone
286
+ p["token"] = encode(p)
287
+ p
288
+ end
289
+
290
+ # encode encodes a payload.
291
+ #
292
+ # @example
293
+ # token = jwt.encode(payload)
294
+ #
295
+ # @param p The payload to encode
296
+ # @return An encoded token.
297
+ # @raise Inherited from JWT.encode of Ruby JWT.
298
+ # @since 0.1.0
299
+ sig {override.params(p: T::Hash[T.untyped, T.untyped]).returns(String)}
300
+ def encode(p)
301
+ p = p.clone
302
+
303
+ t = Time.now.utc.to_i
304
+
305
+ for c in @claims
306
+ if c.is_a?(ExpClaim) && c.ttl != 0
307
+ p["exp"] = t + c.ttl
308
+ next
309
+ end
310
+
311
+ if c.is_a?(IatClaim)
312
+ p["iat"] = t
313
+ next
314
+ end
315
+ end
316
+
317
+ JWT.encode(p, @secret, @algorithm)
318
+ end
319
+
320
+ # decode_uri decodes a URI by extracting a token from the query string.
321
+ # This method returns a shallow copy of the URI with the token removed. It
322
+ # does not modify the original URI.
323
+ #
324
+ # @example
325
+ # uri = jwt.decode_uri(uri)
326
+ #
327
+ # @param u The URI to decode
328
+ # @return A decoded URI
329
+ # @raise [ArgumentError] If the URI does not have a query string.
330
+ # @raise [KeyError] If the query string does not have a 'token' key.
331
+ # @raise [KeyError] If the decoded token does not have a 'url' key.
332
+ # @raise [TypeError] If the 'url' key is not a String.
333
+ # @raise [TypeError] If the 'url' key is not a URI::HTTP.
334
+ # @raise Inherited from {decode}.
335
+ # @since 0.1.0
336
+ sig {override.params(u: URI::HTTP).returns(URI::HTTP)}
337
+ def decode_uri(u)
338
+ q = u.query
339
+ if q.nil?
340
+ raise ArgumentError, "Expected the URI to have a query string, but it did not"
341
+ end
342
+
343
+ f = URI.decode_www_form(q)
344
+
345
+ c = f.assoc("token")
346
+ if !c
347
+ raise KeyError, "Expected the query string to have a 'token' key, but it did not"
348
+ end
349
+
350
+ t = c[1]
351
+
352
+ d = decode(t)
353
+ if !d.key?("url")
354
+ raise KeyError, "Expected the decoded token to have a 'url' key, but it did not"
355
+ end
356
+
357
+ s = d["url"]
358
+ if !s.is_a?(String)
359
+ raise TypeError, "Expected the 'url' key to be a String, but it was a #{s.class}"
360
+ end
361
+
362
+ g = URI.parse(s)
363
+ if !g.is_a?(URI::HTTP)
364
+ raise TypeError, "Expected the 'url' key to be a URI::HTTP, but it was a #{g.class}"
365
+ end
366
+
367
+ g
368
+ end
369
+
370
+ # decode_header decodes a header.
371
+ #
372
+ # @example
373
+ # header = "Bearer ***"
374
+ # payload = jwt.decode_header(header[7..])
375
+ #
376
+ # @param h The header to decode
377
+ # @return A decoded header
378
+ # @raise [KeyError] If the decoded header does not have a 'payload' key.
379
+ # @raise Inherited from {decode}.
380
+ sig {override.params(h: String).returns(T.untyped)}
381
+ def decode_header(h)
382
+ d = decode(h)
383
+ if !d.key?("payload")
384
+ raise KeyError, "Expected the decoded header to have a 'payload' key, but it did not"
385
+ end
386
+
387
+ d["payload"]
388
+ end
389
+
390
+ # decode_body decodes a body by extracting a token from the body. This
391
+ # method returns a shallow copy of the body with the token removed. It
392
+ # does not modify the original body.
393
+ #
394
+ # @example
395
+ # json = JSON.parse(res.body)
396
+ # payload = jwt.decode_body(json)
397
+ #
398
+ # @param b The body to decode.
399
+ # @return A decoded body.
400
+ # @raise [KeyError] If the body does not have a 'token' key.
401
+ # @raise [TypeError] If the 'token' key is not a String.
402
+ # @raise Inherited from {decode}.
403
+ sig {override.params(b: T::Hash[T.untyped, T.untyped]).returns(T::Hash[T.untyped, T.untyped])}
404
+ def decode_body(b)
405
+ if !b.key?("token")
406
+ raise KeyError, "Expected the body to have a 'token' key, but it did not"
407
+ end
408
+
409
+ t = b["token"]
410
+ if !t.is_a?(String)
411
+ raise TypeError, "Expected the 'token' key to be a String, but it was a #{t.class}"
412
+ end
413
+
414
+ decode(t)
415
+ end
416
+
417
+ # decode decodes a token.
418
+ #
419
+ # @example
420
+ # payload = jwt.decode(token)
421
+ #
422
+ # @param t The token to decode.
423
+ # @return A decoded token.
424
+ # @raise Inherited from JWT.decode of Ruby JWT.
425
+ sig {override.params(t: String).returns(T::Hash[T.untyped, T.untyped])}
426
+ def decode(t)
427
+ o = T.let({}, T::Hash[Symbol, T.untyped])
428
+
429
+ o[:algorithm] = @algorithm
430
+
431
+ for c in @claims
432
+ if c.is_a?(ExpClaim) && c.leeway != 0
433
+ o[:exp_leeway] = c.leeway
434
+ next
435
+ end
436
+ end
437
+
438
+ d, _ = JWT.decode(t, @secret, true, o)
439
+ d = T.cast(d, T::Hash[T.untyped, T.untyped])
440
+
441
+ d.delete("exp")
442
+ d.delete("iat")
443
+
444
+ d
445
+ end
446
+ end
447
+ end
448
+ end