libkeycloak 1.0.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 (3) hide show
  1. checksums.yaml +7 -0
  2. data/keycloak.rb +583 -0
  3. metadata +152 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: '092425c0716ee45f8777708c27b2898b5a7af43edbe9c5cc194aa5998de2833d'
4
+ data.tar.gz: 75c900a8efc01cab2af35a0ce53e40435ff0b1e3c8870c7a7991c48cfe36efd3
5
+ SHA512:
6
+ metadata.gz: 73f0c5a082c94a0e51fa439d1182e20f9d8f8218374ad499c7a7b7591e66da0cd41c21fdc3a8ae6748fe7a39a05d9cdfacea6cf3ec901a2b41d2ceb2ff22f362
7
+ data.tar.gz: 48917e889a59329faa173044761c206463f0196fc55e9362eefaab15b46132c1c4b6c4cdb85c27f76833b0866b61beb09d57f67725d6374bb98211647501dd75
data/keycloak.rb ADDED
@@ -0,0 +1,583 @@
1
+ require 'ffi'
2
+ require 'ffi/libc'
3
+
4
+ module Keycloak_FFI
5
+ extend FFI::Library
6
+
7
+ ffi_lib 'keycloak'
8
+
9
+ # Error
10
+ enum :KeycloakErrorCode, [
11
+ :OK, 0,
12
+ :JSON_Parse,
13
+ :JSON_Field,
14
+ :No_JSON_Field,
15
+ :CURL,
16
+ :HTTP,
17
+ :JWTDecode,
18
+ :JWTInvalidClaimKey,
19
+ :OutOfMemory,
20
+ ]
21
+
22
+ class KeycloakErrorData < FFI::Union
23
+ layout :str, :string,
24
+ :code, :ulong
25
+ end
26
+
27
+ class KeycloakError < FFI::Struct
28
+ layout :errcode, :KeycloakErrorCode,
29
+ :data, KeycloakErrorData.val
30
+ end
31
+
32
+ attach_function :keycloak_errmsg, [KeycloakError.val, :pointer], :void
33
+
34
+ # Client
35
+ class KeycloakRealm < FFI::Struct
36
+ layout :_json, :pointer,
37
+ :public_key, :string,
38
+ :token_service, :string,
39
+ :account_service, :string
40
+
41
+ def public_key
42
+ self[:public_key]
43
+ end
44
+
45
+ def token_service
46
+ self[:token_service]
47
+ end
48
+
49
+ def account_service
50
+ self[:account_service]
51
+ end
52
+ end
53
+
54
+ class KeycloakClient < FFI::ManagedStruct
55
+ layout :_json, :pointer,
56
+ :realm, :string,
57
+ :resource, :string,
58
+ :auth_server_url, :string,
59
+ :secret, :string,
60
+ :realm_info, KeycloakRealm.val
61
+
62
+ def self.release ptr
63
+ unless ptr.nil?
64
+ Keycloak_FFI.keycloak_destroy_client(ptr)
65
+ FFI::LibC.free(ptr)
66
+ end
67
+ end
68
+ end
69
+
70
+ attach_function :keycloak_create_client, [
71
+ KeycloakClient.ptr,
72
+ :string, # jsonstr
73
+ :char # options
74
+ ], KeycloakError.val
75
+
76
+ attach_function :keycloak_destroy_client, [:pointer], :void
77
+
78
+ # Token service
79
+
80
+ class KeycloakToken < FFI::Struct
81
+ layout :token, :string,
82
+ :expiration, :int
83
+
84
+ def token
85
+ self[:token]
86
+ end
87
+
88
+ def expiration
89
+ self[:token]
90
+ end
91
+ end
92
+
93
+ class KeycloakTokens < FFI::ManagedStruct
94
+ layout :_json, :pointer,
95
+ :access_token, KeycloakToken.val,
96
+ :refresh_token, KeycloakToken.val,
97
+ :token_type, :string,
98
+ :not_before_policy, :int,
99
+ :session_state, :string,
100
+ :scope, :string
101
+
102
+ def self.release ptr
103
+ unless ptr.nil?
104
+ Keycloak_FFI.keycloak_destroy_tokens(ptr)
105
+ FFI::LibC.free(ptr)
106
+ end
107
+ end
108
+
109
+ def access_token
110
+ return self[:access_token]
111
+ end
112
+
113
+ def refresh_token
114
+ return self[:refresh_token]
115
+ end
116
+
117
+ def token_type
118
+ return self[:token_type]
119
+ end
120
+
121
+ def not_before_policy
122
+ return self[:not_before_policy]
123
+ end
124
+
125
+ def session_state
126
+ return self[:session_state]
127
+ end
128
+
129
+ def scope
130
+ return self[:scope]
131
+ end
132
+
133
+ def scopes
134
+ return self.scope.split " "
135
+ end
136
+ end
137
+
138
+ attach_function :keycloak_destroy_tokens, [:pointer], :void
139
+
140
+ attach_function :keycloak_get_token, [
141
+ KeycloakClient.ptr,
142
+ :string, # user
143
+ :string, # pass
144
+ :pointer, # scopes
145
+ :int, # scopes_len
146
+ KeycloakTokens.ptr, # out tokens
147
+ :pointer # out err_response
148
+ ], KeycloakError.val
149
+
150
+ attach_function :keycloak_refresh_token, [
151
+ KeycloakClient.ptr,
152
+ KeycloakToken.val, # refresh token
153
+ :pointer, # scopes
154
+ :int, # scopes len
155
+ KeycloakTokens.ptr, # out tokens
156
+ :pointer # out err_response
157
+ ], KeycloakError.val
158
+
159
+ class KeycloakUserinfo < FFI::ManagedStruct
160
+ layout :_json, :pointer,
161
+ :sub, :string,
162
+ :email_verified, :bool,
163
+ :name, :string,
164
+ :preferred_username, :string,
165
+ :given_name, :string,
166
+ :family_name, :string,
167
+ :email, :string
168
+
169
+ def sub
170
+ self[:sub]
171
+ end
172
+
173
+ def email_verified
174
+ self[:email_verified]
175
+ end
176
+
177
+ def name
178
+ self[:name]
179
+ end
180
+
181
+ def preferred_username
182
+ self[:preferred_username]
183
+ end
184
+
185
+ def given_name
186
+ self[:given_name]
187
+ end
188
+
189
+ def family_name
190
+ self[:family_name]
191
+ end
192
+
193
+ def email
194
+ self[:email]
195
+ end
196
+
197
+ def self.release ptr
198
+ unless ptr.nil?
199
+ Keycloak_FFI.keycloak_destroy_userinfo(ptr)
200
+ FFI::LibC.free(ptr)
201
+ end
202
+ end
203
+ end
204
+
205
+ attach_function :keycloak_destroy_userinfo, [:pointer], :void
206
+
207
+ attach_function :keycloak_get_userinfo, [
208
+ KeycloakClient.ptr,
209
+ KeycloakTokens.ptr,
210
+ KeycloakUserinfo.ptr, # out
211
+ :pointer # out err_response
212
+ ], KeycloakError.val
213
+
214
+ KeycloakJWTValidationResult = enum :VALID, 0,
215
+ :ISS_FAILURE, 1 << 0,
216
+ :SUB_FAILURE, 1 << 1,
217
+ :AUD_FAILURE, 1 << 2,
218
+ :JTI_FAILURE, 1 << 3,
219
+ :EXP_FAILURE, 1 << 4,
220
+ :NBF_FAILURE, 1 << 5,
221
+ :IAT_FAILURE, 1 << 6,
222
+ :SIGNATURE_VERIFICATION_FAILURE, 1 << 7,
223
+ :TYP_FAILURE, 1 << 8
224
+
225
+ class KeycloakJWT < FFI::ManagedStruct
226
+ layout :data, :pointer,
227
+ :len, :long
228
+
229
+ def [](claim_name)
230
+ claim = KeycloakJWTClaim.new
231
+ Keycloak_FFI.keycloak_jwt_get_claim(
232
+ self,
233
+ claim_name.to_s,
234
+ claim
235
+ )
236
+ return Keycloak._jwt_claim_value(claim)
237
+ end
238
+
239
+ def self.release ptr
240
+ unless ptr.nil?
241
+ Keycloak_FFI.keycloak_destroy_jwt(ptr)
242
+ FFI::LibC.free(ptr)
243
+ end
244
+ end
245
+ end
246
+
247
+ attach_function :keycloak_destroy_jwt, [:pointer], :void
248
+
249
+ enum :KeycloakClaimType, [
250
+ :String, 0,
251
+ :Int, 1,
252
+ :Double, 2,
253
+ :Bool, 3,
254
+ :Null, 4,
255
+ :Array, 5,
256
+ :Object, 6,
257
+ :Other, 7,
258
+ ]
259
+
260
+ class KeycloakJWTClaimValue < FFI::Union
261
+ layout :stringvalue, :string,
262
+ :intvalue, :int,
263
+ :doublevalue, :double,
264
+ :boolvalue, :bool,
265
+ :datavalue, :pointer
266
+ end
267
+
268
+ class KeycloakJWTClaim < FFI::Struct
269
+ layout :key, :string,
270
+ :value, KeycloakJWTClaimValue.val,
271
+ :type, :KeycloakClaimType
272
+ end
273
+
274
+ attach_function :keycloak_jwt_validation_reason_string, [KeycloakJWTValidationResult], :string
275
+
276
+ attach_function :keycloak_validate_jwt_ex, [
277
+ KeycloakClient.ptr,
278
+ KeycloakToken.ptr,
279
+ :string, #:validate_iss,
280
+ :int, #:validate_iss_length,
281
+ :string, #:validate_sub,
282
+ :int, #:validate_sub_length,
283
+ :string, #:validate_aud,
284
+ :int, #:validate_aud_length,
285
+ :string, #:validate_jti,
286
+ :int, #:validate_jti_length,
287
+ :string, #:validate_typ,
288
+ :int, #:validate_typ_length,
289
+ :bool, #:validate_exp,
290
+ :int, #:exp_tolerance_seconds,
291
+ :bool, #:validate_nbf,
292
+ :int, #:nbf_tolerance_seconds,
293
+ :bool, #:validate_iat,
294
+ :int, #:iat_tolerance_seconds,
295
+ :pointer # out validation result
296
+ ], KeycloakError.val
297
+
298
+ attach_function :keycloak_decode_and_validate_jwt_ex, [
299
+ KeycloakClient.ptr,
300
+ KeycloakToken.ptr,
301
+ :string, # validate_iss,
302
+ :int, # validate_iss_length,
303
+ :string, # validate_sub,
304
+ :int, # validate_sub_length,
305
+ :string, # validate_aud,
306
+ :int, # validate_aud_length,
307
+ :string, # validate_jti,
308
+ :int, # validate_jti_length,
309
+ :string, # validate_typ,
310
+ :int, # validate_typ_length,
311
+ :bool, # validate_exp,
312
+ :int, # exp_tolerance_seconds,
313
+ :bool, # validate_nbf,
314
+ :int, # nbf_tolerance_seconds,
315
+ :bool, # validate_iat,
316
+ :int, # iat_tolerance_seconds,
317
+ :pointer, # KeycloakJWTValidationResult.ptr, # out
318
+ KeycloakJWT.ptr # out
319
+ ], KeycloakError.val
320
+
321
+ attach_function :keycloak_jwt_get_claim, [
322
+ KeycloakJWT.ptr,
323
+ :string, # claim key
324
+ KeycloakJWTClaim.ptr # out claim value
325
+ ], KeycloakError.val
326
+ end
327
+
328
+ module Keycloak
329
+ class Error < StandardError
330
+ def initialize(err, str)
331
+ str = FFI::MemoryPointer.new(:string, 1024) # TODO: ERR_MAX macro
332
+ Keycloak_FFI.keycloak_errmsg(err, str)
333
+ super("Error " + err[:errcode].to_s + ": " + str.read_string)
334
+ @ptr = err
335
+ end
336
+
337
+ def errcode
338
+ return @ptr[:errcode]
339
+ end
340
+ end
341
+
342
+ Tokens = Keycloak_FFI::KeycloakTokens
343
+ Token = Keycloak_FFI::KeycloakToken
344
+ UserInfo = Keycloak_FFI::KeycloakUserinfo
345
+
346
+ # TODO: try adding functions to ffi class
347
+ class Client
348
+ def initialize(json, options: 0)
349
+ client_ptr = FFI::LibC.malloc(Keycloak_FFI::KeycloakClient.size)
350
+ @ptr = Keycloak_FFI::KeycloakClient.new(client_ptr)
351
+ err = Keycloak_FFI.keycloak_create_client(@ptr, json, options)
352
+ if err[:errcode] != :OK
353
+ raise Keycloak::Error, err
354
+ end
355
+ end
356
+
357
+ def realm
358
+ @ptr[:realm]
359
+ end
360
+
361
+ def resource
362
+ @ptr[:resource]
363
+ end
364
+
365
+ def auth_server_url
366
+ @ptr[:auth_server_url]
367
+ end
368
+
369
+ def secret
370
+ @ptr[:secret]
371
+ end
372
+
373
+ def realm_info
374
+ @ptr[:realm_info]
375
+ end
376
+
377
+ def get_token(user, pass, scopes: nil)
378
+ tokens_ptr = FFI::LibC.malloc(Keycloak_FFI::KeycloakTokens.size)
379
+ tokens = Keycloak_FFI::KeycloakTokens.new(tokens_ptr)
380
+ err_response_ptr = FFI::MemoryPointer.new(:string)
381
+ scopes_ptr = nil
382
+ scopes_len = 0
383
+ unless scopes.nil?
384
+ scopes_len = scopes.size
385
+ scopes_ptr = FFI::MemoryPointer.new(:pointer, scopes_len)
386
+ ptr_arr = scopes.map do |scope, i|
387
+ ptr = FFI::MemoryPointer.from_string scope
388
+ end
389
+ scopes_ptr.put_array_of_pointer 0, ptr_arr
390
+ end
391
+ err = Keycloak_FFI.keycloak_get_token(
392
+ @ptr,
393
+ user, pass,
394
+ scopes_ptr, scopes_len,
395
+ tokens,
396
+ err_response_ptr
397
+ )
398
+
399
+ if err[:errcode] == :HTTP
400
+ err = Keycloak::Error.new(err, err_response_ptr)
401
+ raise err
402
+ elsif err[:errcode] != :OK
403
+ raise Keycloak::Error, err
404
+ end
405
+
406
+ return tokens
407
+ end
408
+
409
+ def refresh_token(refresh_token, scopes: nil)
410
+ tokens_ptr = FFI::LibC.malloc(Keycloak_FFI::KeycloakTokens.size)
411
+ tokens = Keycloak_FFI::KeycloakTokens.new(tokens_ptr)
412
+ err_response_ptr = FFI::MemoryPointer.new(:string)
413
+ scopes_ptr = nil
414
+ scopes_len = 0
415
+ unless scopes.nil?
416
+ scopes_len = scopes.size
417
+ scopes_ptr = FFI::MemoryPointer.new(:pointer, scopes_len)
418
+ ptr_arr = scopes.map do |scope, i|
419
+ ptr = FFI::MemoryPointer.from_string scope
420
+ end
421
+ scopes_ptr.put_array_of_pointer 0, ptr_arr
422
+ end
423
+ err = Keycloak_FFI.keycloak_refresh_token(
424
+ @ptr,
425
+ refresh_token,
426
+ scopes_ptr, scopes_len,
427
+ tokens,
428
+ err_response_ptr
429
+ )
430
+
431
+ if err[:errcode] == :HTTP
432
+ err = Keycloak::Error.new(err, err_response_ptr)
433
+ raise err
434
+ elsif err[:errcode] != :OK
435
+ raise Keycloak::Error, err
436
+ end
437
+
438
+ return tokens
439
+ end
440
+
441
+ def get_userinfo(tokens)
442
+ userinfo_ptr = FFI::LibC.malloc(Keycloak_FFI::KeycloakUserinfo.size)
443
+ userinfo = Keycloak_FFI::KeycloakUserinfo.new(userinfo_ptr)
444
+
445
+ err_response_ptr = FFI::MemoryPointer.new(:string)
446
+
447
+ err = Keycloak_FFI.keycloak_get_userinfo(
448
+ @ptr,
449
+ tokens,
450
+ userinfo,
451
+ err_response_ptr
452
+ )
453
+
454
+ if err[:errcode] == :HTTP
455
+ err = Keycloak::Error.new(err, err_response_ptr)
456
+ raise err
457
+ elsif err[:errcode] != :OK
458
+ raise Keycloak::Error, err
459
+ end
460
+
461
+ return userinfo
462
+ end
463
+
464
+ def validate_jwt(
465
+ token,
466
+ # strings
467
+ iss: nil,
468
+ sub: nil,
469
+ aud: nil,
470
+ jti: nil,
471
+ typ: nil,
472
+ # booleans
473
+ validate_exp: false,
474
+ # Tolerance in seconds
475
+ exp_tolerance: 0,
476
+ validate_nbf: false,
477
+ nbf_tolerance: 0,
478
+ validate_iat: false,
479
+ iat_tolerance: 0
480
+ )
481
+ valid = FFI::MemoryPointer.new(:uint8)
482
+ err = Keycloak_FFI.keycloak_validate_jwt_ex(
483
+ @ptr,
484
+ token,
485
+ iss, iss.nil? ? 0 : iss.size,
486
+ sub, sub.nil? ? 0 : sub.size,
487
+ aud, aud.nil? ? 0 : aud.size,
488
+ jti, jti.nil? ? 0 : jti.size,
489
+ typ, typ.nil? ? 0 : typ.size,
490
+ validate_exp, exp_tolerance,
491
+ validate_nbf, nbf_tolerance,
492
+ validate_iat, iat_tolerance,
493
+ valid
494
+ )
495
+
496
+ if err[:errcode] != :OK
497
+ raise err
498
+ end
499
+
500
+ reason = Keycloak_FFI::KeycloakJWTValidationResult[valid.read(:uint8).to_i]
501
+ return JWTValidationResult.new(
502
+ reason == :VALID,
503
+ reason
504
+ )
505
+ end
506
+
507
+ def decode_and_validate_jwt(
508
+ token,
509
+ iss: nil,
510
+ sub: nil,
511
+ aud: nil,
512
+ jti: nil,
513
+ typ: nil,
514
+ validate_exp: false,
515
+ exp_tolerance: 0,
516
+ validate_nbf: false,
517
+ nbf_tolerance: 0,
518
+ validate_iat: false,
519
+ iat_tolerance: 0
520
+ )
521
+ valid = FFI::MemoryPointer.new(:uint8)
522
+ jwt_ptr = FFI::LibC.malloc(Keycloak_FFI::KeycloakJWT.size)
523
+ jwt = Keycloak_FFI::KeycloakJWT.new(jwt_ptr)
524
+ err = Keycloak_FFI.keycloak_decode_and_validate_jwt_ex(
525
+ @ptr,
526
+ token,
527
+ iss, iss.nil? ? 0 : iss.size,
528
+ sub, sub.nil? ? 0 : sub.size,
529
+ aud, aud.nil? ? 0 : aud.size,
530
+ jti, jti.nil? ? 0 : jti.size,
531
+ typ, typ.nil? ? 0 : typ.size,
532
+ validate_exp, exp_tolerance,
533
+ validate_nbf, nbf_tolerance,
534
+ validate_iat, iat_tolerance,
535
+ valid,
536
+ jwt
537
+ )
538
+
539
+ if err[:errcode] != :OK
540
+ raise err
541
+ end
542
+
543
+ reason = Keycloak_FFI::KeycloakJWTValidationResult[valid.read(:uint8).to_i]
544
+ return JWTDecodeResult.new(
545
+ JWTValidationResult.new(
546
+ reason == :VALID,
547
+ reason
548
+ ),
549
+ jwt
550
+ )
551
+ end
552
+ end
553
+
554
+ JWTValidationResult = Struct.new(:valid, :reason) do
555
+ def reason_string
556
+ return Keycloak_FFI.keycloak_jwt_validation_reason_string(self.reason)
557
+ end
558
+ end
559
+
560
+ JWTDecodeResult = Struct.new(:valid, :jwt)
561
+
562
+ def self._jwt_claim_value claim
563
+ case claim[:type]
564
+ when :String
565
+ return claim[:value][:stringvalue]
566
+ when :Int
567
+ return claim[:value][:intvalue]
568
+ when :Double
569
+ return claim[:value][:doublevalue]
570
+ when :Bool
571
+ return claim[:value][:boolvalue]
572
+ when :Null
573
+ return nil
574
+ # TODO
575
+ when :Array
576
+ return claim[:value][:datavalue]
577
+ when :Object
578
+ return claim[:value][:datavalue]
579
+ when :Other
580
+ return claim[:value][:datavalue]
581
+ end
582
+ end
583
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: libkeycloak
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Jonas Everaert
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2024-10-16 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: ffi
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.17'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.17'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ffi-libc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.1'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.1'
41
+ description: |
42
+ # Keycloak C
43
+
44
+ A wrapper for the Keycloak API in C. Includes some bindings for different languages
45
+
46
+ ## Usage
47
+
48
+ ### Installation
49
+
50
+ #### C
51
+
52
+ see [Building](#building).
53
+
54
+ #### Ruby
55
+
56
+ - Install the C library as a [dynamic library](#dynamic-library)
57
+ - Include the ruby file at `bindings/ruby/keycloak.rb` in your project
58
+
59
+ <!-- - OR: `gem install keycloak-api` -->
60
+
61
+ ## Building
62
+
63
+ You need Ruby and the colorize gem (`gem install colorize`)
64
+
65
+ ### Static library
66
+
67
+ ```sh
68
+ ruby build.rb build static
69
+ ```
70
+
71
+ There is only one header to be included, located at src/keycloak.h
72
+
73
+ ### Dynamic library
74
+
75
+ ```sh
76
+ ruby build.rb build dynamic
77
+ ```
78
+
79
+ The dynamc libraries are now located in the `build` folder. Copy them to a folder
80
+ in `echo $LD_LIBRARY_PATH`.
81
+
82
+ ## Keycloak.json
83
+
84
+ The file `keycloak.json` is required by the library, you can obtain it from going to a Client in the Keycloak console
85
+ and clicking "action" > "Download adapter config" and choose JSON.
86
+
87
+ In settings, make sure "Client authentication" is turned on.
88
+
89
+ ## Examples
90
+
91
+ To run the examples, you need a keycloak.json. You can obtain it from going to a Client in the Keycloak console
92
+ and clicking "action" > "Download adapter config" and choose JSON.
93
+
94
+ Paste the file in the root of this repository to try out the examples.
95
+
96
+ All the example code can be found in the root of this repository as `example.[lang]`.
97
+
98
+ The `[user]` and `[pass]` arguments are used to log in to a user in the keycloak realm specified by the `keycloak.json`.
99
+
100
+ ### C
101
+
102
+ ```sh
103
+ ruby build.rb test
104
+ ./build/test/a.out [user] [pass]
105
+ ```
106
+
107
+ ### Ruby
108
+
109
+ ```sh
110
+ ruby build.rb build dynamic
111
+ LD_LIBRARY_PATH="$LD_LIBRARY_PATH:$(pwd)/build" ruby example.rb [user] [pass]
112
+ # OR: copy the dynamic library to a path in LD_LIBRARY_PATH
113
+ ```
114
+
115
+ ## Questions
116
+
117
+ Feel free to ask any questions regarding the library as an issue.
118
+
119
+ ## License
120
+
121
+ Licensed under the [MIT license](LICENSE).
122
+ email:
123
+ executables: []
124
+ extensions: []
125
+ extra_rdoc_files: []
126
+ files:
127
+ - keycloak.rb
128
+ homepage: https://github.com/Jomy10/libkeycloak
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '2.5'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubygems_version: 3.5.10
148
+ signing_key:
149
+ specification_version: 4
150
+ summary: A library for interacting with Keycloak and validating jwt tokens returned
151
+ from it.
152
+ test_files: []