libkeycloak 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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: []