slosilo 0.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.
- checksums.yaml +7 -0
- data/.github/CODEOWNERS +10 -0
- data/.gitignore +21 -0
- data/.gitleaks.toml +221 -0
- data/.kateproject +4 -0
- data/CHANGELOG.md +50 -0
- data/CONTRIBUTING.md +16 -0
- data/Gemfile +4 -0
- data/Jenkinsfile +132 -0
- data/LICENSE +22 -0
- data/README.md +152 -0
- data/Rakefile +17 -0
- data/SECURITY.md +42 -0
- data/dev/Dockerfile.dev +7 -0
- data/dev/docker-compose.yml +8 -0
- data/lib/slosilo/adapters/abstract_adapter.rb +23 -0
- data/lib/slosilo/adapters/file_adapter.rb +42 -0
- data/lib/slosilo/adapters/memory_adapter.rb +31 -0
- data/lib/slosilo/adapters/mock_adapter.rb +21 -0
- data/lib/slosilo/adapters/sequel_adapter/migration.rb +52 -0
- data/lib/slosilo/adapters/sequel_adapter.rb +96 -0
- data/lib/slosilo/attr_encrypted.rb +85 -0
- data/lib/slosilo/errors.rb +15 -0
- data/lib/slosilo/jwt.rb +122 -0
- data/lib/slosilo/key.rb +218 -0
- data/lib/slosilo/keystore.rb +89 -0
- data/lib/slosilo/random.rb +11 -0
- data/lib/slosilo/symmetric.rb +63 -0
- data/lib/slosilo/version.rb +22 -0
- data/lib/slosilo.rb +13 -0
- data/lib/tasks/slosilo.rake +32 -0
- data/publish.sh +5 -0
- data/secrets.yml +1 -0
- data/slosilo.gemspec +38 -0
- data/spec/encrypted_attributes_spec.rb +114 -0
- data/spec/file_adapter_spec.rb +81 -0
- data/spec/jwt_spec.rb +102 -0
- data/spec/key_spec.rb +258 -0
- data/spec/keystore_spec.rb +26 -0
- data/spec/random_spec.rb +19 -0
- data/spec/sequel_adapter_spec.rb +171 -0
- data/spec/slosilo_spec.rb +124 -0
- data/spec/spec_helper.rb +84 -0
- data/spec/symmetric_spec.rb +94 -0
- data/test.sh +8 -0
- metadata +238 -0
data/SECURITY.md
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# Security Policies and Procedures
|
2
|
+
|
3
|
+
This document outlines security procedures and general policies for the CyberArk Conjur
|
4
|
+
suite of tools and products.
|
5
|
+
|
6
|
+
* [Reporting a Bug](#reporting-a-bug)
|
7
|
+
* [Disclosure Policy](#disclosure-policy)
|
8
|
+
* [Comments on this Policy](#comments-on-this-policy)
|
9
|
+
|
10
|
+
## Reporting a Bug
|
11
|
+
|
12
|
+
The CyberArk Conjur team and community take all security bugs in the Conjur suite seriously.
|
13
|
+
Thank you for improving the security of the Conjur suite. We appreciate your efforts and
|
14
|
+
responsible disclosure and will make every effort to acknowledge your
|
15
|
+
contributions.
|
16
|
+
|
17
|
+
Report security bugs by emailing the lead maintainers at security@conjur.org.
|
18
|
+
|
19
|
+
The maintainers will acknowledge your email within 2 business days. Subsequently, we will
|
20
|
+
send a more detailed response within 2 business days of our acknowledgement indicating
|
21
|
+
the next steps in handling your report. After the initial reply to your report, the security
|
22
|
+
team will endeavor to keep you informed of the progress towards a fix and full
|
23
|
+
announcement, and may ask for additional information or guidance.
|
24
|
+
|
25
|
+
Report security bugs in third-party modules to the person or team maintaining
|
26
|
+
the module.
|
27
|
+
|
28
|
+
## Disclosure Policy
|
29
|
+
|
30
|
+
When the security team receives a security bug report, they will assign it to a
|
31
|
+
primary handler. This person will coordinate the fix and release process,
|
32
|
+
involving the following steps:
|
33
|
+
|
34
|
+
* Confirm the problem and determine the affected versions.
|
35
|
+
* Audit code to find any potential similar problems.
|
36
|
+
* Prepare fixes for all releases still under maintenance. These fixes will be
|
37
|
+
released as fast as possible.
|
38
|
+
|
39
|
+
## Comments on this Policy
|
40
|
+
|
41
|
+
If you have suggestions on how this process could be improved please submit a
|
42
|
+
pull request.
|
data/dev/Dockerfile.dev
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'slosilo/attr_encrypted'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class AbstractAdapter
|
6
|
+
def get_key id
|
7
|
+
raise NotImplementedError
|
8
|
+
end
|
9
|
+
|
10
|
+
def get_by_fingerprint fp
|
11
|
+
raise NotImplementedError
|
12
|
+
end
|
13
|
+
|
14
|
+
def put_key id, key
|
15
|
+
raise NotImplementedError
|
16
|
+
end
|
17
|
+
|
18
|
+
def each
|
19
|
+
raise NotImplementedError
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'slosilo/adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class FileAdapter < AbstractAdapter
|
6
|
+
attr_reader :dir
|
7
|
+
|
8
|
+
def initialize(dir)
|
9
|
+
@dir = dir
|
10
|
+
@keys = {}
|
11
|
+
@fingerprints = {}
|
12
|
+
Dir[File.join(@dir, "*.key")].each do |f|
|
13
|
+
key = Slosilo::EncryptedAttributes.decrypt File.read(f)
|
14
|
+
id = File.basename(f, '.key')
|
15
|
+
key = @keys[id] = Slosilo::Key.new(key)
|
16
|
+
@fingerprints[key.fingerprint] = id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def put_key id, value
|
21
|
+
raise "id should not contain a period" if id.index('.')
|
22
|
+
fname = File.join(dir, "#{id}.key")
|
23
|
+
File.write(fname, Slosilo::EncryptedAttributes.encrypt(value.to_der))
|
24
|
+
File.chmod(0400, fname)
|
25
|
+
@keys[id] = value
|
26
|
+
end
|
27
|
+
|
28
|
+
def get_key id
|
29
|
+
@keys[id]
|
30
|
+
end
|
31
|
+
|
32
|
+
def get_by_fingerprint fp
|
33
|
+
id = @fingerprints[fp]
|
34
|
+
[@keys[id], id]
|
35
|
+
end
|
36
|
+
|
37
|
+
def each(&block)
|
38
|
+
@keys.each(&block)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'slosilo/adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class MemoryAdapter < AbstractAdapter
|
6
|
+
def initialize
|
7
|
+
@keys = {}
|
8
|
+
@fingerprints = {}
|
9
|
+
end
|
10
|
+
|
11
|
+
def put_key id, key
|
12
|
+
key = Slosilo::Key.new(key) if key.is_a?(String)
|
13
|
+
@keys[id] = key
|
14
|
+
@fingerprints[key.fingerprint] = id
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_key id
|
18
|
+
@keys[id]
|
19
|
+
end
|
20
|
+
|
21
|
+
def get_by_fingerprint fp
|
22
|
+
id = @fingerprints[fp]
|
23
|
+
[@keys[id], id]
|
24
|
+
end
|
25
|
+
|
26
|
+
def each(&block)
|
27
|
+
@keys.each(&block)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Slosilo
|
2
|
+
module Adapters
|
3
|
+
class MockAdapter < Hash
|
4
|
+
def initialize
|
5
|
+
@fp = {}
|
6
|
+
end
|
7
|
+
|
8
|
+
def put_key id, key
|
9
|
+
@fp[key.fingerprint] = id
|
10
|
+
self[id] = key
|
11
|
+
end
|
12
|
+
|
13
|
+
alias :get_key :[]
|
14
|
+
|
15
|
+
def get_by_fingerprint fp
|
16
|
+
id = @fp[fp]
|
17
|
+
[self[id], id]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'sequel'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters::SequelAdapter::Migration
|
5
|
+
# The default name of the table to hold the keys
|
6
|
+
DEFAULT_KEYSTORE_TABLE = :slosilo_keystore
|
7
|
+
|
8
|
+
# Sets up default keystore table name
|
9
|
+
def self.extended(db)
|
10
|
+
db.keystore_table ||= DEFAULT_KEYSTORE_TABLE
|
11
|
+
end
|
12
|
+
|
13
|
+
# Keystore table name. If changing this do it immediately after loading the extension.
|
14
|
+
attr_accessor :keystore_table
|
15
|
+
|
16
|
+
# Create the table for holding keys
|
17
|
+
def create_keystore_table
|
18
|
+
# docs say to not use create_table? in migration;
|
19
|
+
# but we really want this to be robust in case there are any previous installs
|
20
|
+
# and we can't use table_exists? because it rolls back
|
21
|
+
create_table? keystore_table do
|
22
|
+
String :id, primary_key: true
|
23
|
+
bytea :key, null: false
|
24
|
+
String :fingerprint, unique: true, null: false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
# Drop the table
|
29
|
+
def drop_keystore_table
|
30
|
+
drop_table keystore_table
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
module Extension
|
35
|
+
def slosilo_keystore
|
36
|
+
extend Slosilo::Adapters::SequelAdapter::Migration
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
Sequel::Database.send :include, Extension
|
41
|
+
end
|
42
|
+
|
43
|
+
Sequel.migration do
|
44
|
+
up do
|
45
|
+
slosilo_keystore
|
46
|
+
create_keystore_table
|
47
|
+
end
|
48
|
+
down do
|
49
|
+
slosilo_keystore
|
50
|
+
drop_keystore_table
|
51
|
+
end
|
52
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'slosilo/adapters/abstract_adapter'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
module Adapters
|
5
|
+
class SequelAdapter < AbstractAdapter
|
6
|
+
def model
|
7
|
+
@model ||= create_model
|
8
|
+
end
|
9
|
+
|
10
|
+
def secure?
|
11
|
+
!Slosilo.encryption_key.nil?
|
12
|
+
end
|
13
|
+
|
14
|
+
def create_model
|
15
|
+
model = Sequel::Model(:slosilo_keystore)
|
16
|
+
model.unrestrict_primary_key
|
17
|
+
model.attr_encrypted(:key, aad: :id) if secure?
|
18
|
+
model
|
19
|
+
end
|
20
|
+
|
21
|
+
def put_key id, value
|
22
|
+
fail Error::InsecureKeyStorage unless secure? || !value.private?
|
23
|
+
|
24
|
+
attrs = { id: id, key: value.to_der }
|
25
|
+
attrs[:fingerprint] = value.fingerprint if fingerprint_in_db?
|
26
|
+
model.create attrs
|
27
|
+
end
|
28
|
+
|
29
|
+
def get_key id
|
30
|
+
stored = model[id]
|
31
|
+
return nil unless stored
|
32
|
+
Slosilo::Key.new stored.key
|
33
|
+
end
|
34
|
+
|
35
|
+
def get_by_fingerprint fp
|
36
|
+
if fingerprint_in_db?
|
37
|
+
stored = model[fingerprint: fp]
|
38
|
+
return nil unless stored
|
39
|
+
[Slosilo::Key.new(stored.key), stored.id]
|
40
|
+
else
|
41
|
+
warn "Please migrate to a new database schema using rake slosilo:migrate for efficient fingerprint lookups"
|
42
|
+
find_by_fingerprint fp
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def each
|
47
|
+
model.each do |m|
|
48
|
+
yield m.id, Slosilo::Key.new(m.key)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def recalculate_fingerprints
|
53
|
+
# Use a transaction to ensure that all fingerprints are updated together. If any update fails,
|
54
|
+
# we want to rollback all updates.
|
55
|
+
model.db.transaction do
|
56
|
+
model.each do |m|
|
57
|
+
m.update fingerprint: Slosilo::Key.new(m.key).fingerprint
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
|
63
|
+
def migrate!
|
64
|
+
unless fingerprint_in_db?
|
65
|
+
model.db.transaction do
|
66
|
+
model.db.alter_table :slosilo_keystore do
|
67
|
+
add_column :fingerprint, String
|
68
|
+
end
|
69
|
+
|
70
|
+
# reload the schema
|
71
|
+
model.set_dataset model.dataset
|
72
|
+
|
73
|
+
recalculate_fingerprints
|
74
|
+
|
75
|
+
model.db.alter_table :slosilo_keystore do
|
76
|
+
set_column_not_null :fingerprint
|
77
|
+
add_unique_constraint :fingerprint
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
private
|
84
|
+
|
85
|
+
def fingerprint_in_db?
|
86
|
+
model.columns.include? :fingerprint
|
87
|
+
end
|
88
|
+
|
89
|
+
def find_by_fingerprint fp
|
90
|
+
each do |id, k|
|
91
|
+
return [k, id] if k.fingerprint == fp
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'slosilo/symmetric'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
# we don't trust the database to keep all backups safe from the prying eyes
|
5
|
+
# so we encrypt sensitive attributes before storing them
|
6
|
+
module EncryptedAttributes
|
7
|
+
module ClassMethods
|
8
|
+
|
9
|
+
# @param options [Hash]
|
10
|
+
# @option :aad [#to_proc, #to_s] Provide additional authenticated data for
|
11
|
+
# encryption. This should be something unique to the instance having
|
12
|
+
# this attribute, such as a primary key; this will ensure that an attacker can't swap
|
13
|
+
# values around -- trying to decrypt value with a different auth data will fail.
|
14
|
+
# This means you have to be able to recover it in order to decrypt attributes.
|
15
|
+
# The following values are accepted:
|
16
|
+
#
|
17
|
+
# * Something proc-ish: will be called with self each time auth data is needed.
|
18
|
+
# * Something stringish: will be to_s-d and used for all instances as auth data.
|
19
|
+
# Note that this will only prevent swapping in data using another string.
|
20
|
+
#
|
21
|
+
# The recommended way to use this option is to pass a proc-ish that identifies the record.
|
22
|
+
# Note the proc-ish can be a simple method name; for example in case of a Sequel::Model:
|
23
|
+
# attr_encrypted :secret, aad: :pk
|
24
|
+
def attr_encrypted *a
|
25
|
+
options = a.last.is_a?(Hash) ? a.pop : {}
|
26
|
+
aad = options[:aad]
|
27
|
+
# note nil.to_s is "", which is exactly the right thing
|
28
|
+
auth_data = aad.respond_to?(:to_proc) ? aad.to_proc : proc{ |_| aad.to_s }
|
29
|
+
|
30
|
+
# In ruby 3 .arity for #proc returns both 1 and 2, depends on internal #proc
|
31
|
+
# This method is also being called with aad which is string, in such case the arity is 1
|
32
|
+
raise ":aad proc must take two arguments" unless (auth_data.arity.abs == 2 || auth_data.arity.abs == 1)
|
33
|
+
|
34
|
+
# push a module onto the inheritance hierarchy
|
35
|
+
# this allows calling super in classes
|
36
|
+
include(accessors = Module.new)
|
37
|
+
accessors.module_eval do
|
38
|
+
a.each do |attr|
|
39
|
+
define_method "#{attr}=" do |value|
|
40
|
+
super(EncryptedAttributes.encrypt(value, aad: auth_data[self]))
|
41
|
+
end
|
42
|
+
define_method attr do
|
43
|
+
EncryptedAttributes.decrypt(super(), aad: auth_data[self])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.included base
|
52
|
+
base.extend ClassMethods
|
53
|
+
end
|
54
|
+
|
55
|
+
class << self
|
56
|
+
def encrypt value, opts={}
|
57
|
+
return nil unless value
|
58
|
+
cipher.encrypt value, key: key, aad: opts[:aad]
|
59
|
+
end
|
60
|
+
|
61
|
+
def decrypt ctxt, opts={}
|
62
|
+
return nil unless ctxt
|
63
|
+
cipher.decrypt ctxt, key: key, aad: opts[:aad]
|
64
|
+
end
|
65
|
+
|
66
|
+
def key
|
67
|
+
Slosilo::encryption_key || (raise "Please set Slosilo::encryption_key")
|
68
|
+
end
|
69
|
+
|
70
|
+
def cipher
|
71
|
+
@cipher ||= Slosilo::Symmetric.new
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
class << self
|
77
|
+
attr_writer :encryption_key
|
78
|
+
|
79
|
+
def encryption_key
|
80
|
+
@encryption_key
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
Object.send :include, Slosilo::EncryptedAttributes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Slosilo
|
2
|
+
class Error < RuntimeError
|
3
|
+
# An error thrown when attempting to store a private key in an unecrypted
|
4
|
+
# storage. Set Slosilo.encryption_key to secure the storage or make sure
|
5
|
+
# to store just the public keys (using Key#public).
|
6
|
+
class InsecureKeyStorage < Error
|
7
|
+
def initialize msg = "can't store a private key in a plaintext storage"
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TokenValidationError < Error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/slosilo/jwt.rb
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Slosilo
|
4
|
+
# A JWT-formatted Slosilo token.
|
5
|
+
# @note This is not intended to be a general-purpose JWT implementation.
|
6
|
+
class JWT
|
7
|
+
# Create a new unsigned token with the given claims.
|
8
|
+
# @param claims [#to_h] claims to embed in this token.
|
9
|
+
def initialize claims = {}
|
10
|
+
@claims = JSONHash[claims]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Parse a token in compact representation
|
14
|
+
def self.parse_compact raw
|
15
|
+
load *raw.split('.', 3).map(&Base64.method(:urlsafe_decode64))
|
16
|
+
end
|
17
|
+
|
18
|
+
# Parse a token in JSON representation.
|
19
|
+
# @note only single signature is currently supported.
|
20
|
+
def self.parse_json raw
|
21
|
+
raw = JSON.load raw unless raw.respond_to? :to_h
|
22
|
+
parts = raw.to_h.values_at(*%w(protected payload signature))
|
23
|
+
fail ArgumentError, "input not a complete JWT" unless parts.all?
|
24
|
+
load *parts.map(&Base64.method(:urlsafe_decode64))
|
25
|
+
end
|
26
|
+
|
27
|
+
# Add a signature.
|
28
|
+
# @note currently only a single signature is handled;
|
29
|
+
# the token will be frozen after this operation.
|
30
|
+
def add_signature header, &sign
|
31
|
+
@claims = canonicalize_claims.freeze
|
32
|
+
@header = JSONHash[header].freeze
|
33
|
+
@signature = sign[string_to_sign].freeze
|
34
|
+
freeze
|
35
|
+
end
|
36
|
+
|
37
|
+
def string_to_sign
|
38
|
+
[header, claims].map(&method(:encode)).join '.'
|
39
|
+
end
|
40
|
+
|
41
|
+
# Returns the JSON serialization of this JWT.
|
42
|
+
def to_json *a
|
43
|
+
{
|
44
|
+
protected: encode(header),
|
45
|
+
payload: encode(claims),
|
46
|
+
signature: encode(signature)
|
47
|
+
}.to_json *a
|
48
|
+
end
|
49
|
+
|
50
|
+
# Returns the compact serialization of this JWT.
|
51
|
+
def to_s
|
52
|
+
[header, claims, signature].map(&method(:encode)).join('.')
|
53
|
+
end
|
54
|
+
|
55
|
+
attr_accessor :claims, :header, :signature
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
# Create a JWT token object from existing header, payload, and signature strings.
|
60
|
+
# @param header [#to_s] URLbase64-encoded representation of the protected header
|
61
|
+
# @param payload [#to_s] URLbase64-encoded representation of the token payload
|
62
|
+
# @param signature [#to_s] URLbase64-encoded representation of the signature
|
63
|
+
def self.load header, payload, signature
|
64
|
+
self.new(JSONHash.load payload).tap do |token|
|
65
|
+
token.header = JSONHash.load header
|
66
|
+
token.signature = signature.to_s.freeze
|
67
|
+
token.freeze
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def canonicalize_claims
|
72
|
+
claims[:iat] = Time.now unless claims.include? :iat
|
73
|
+
claims[:iat] = claims[:iat].to_time.to_i
|
74
|
+
claims[:exp] = claims[:exp].to_time.to_i if claims.include? :exp
|
75
|
+
JSONHash[claims.to_a]
|
76
|
+
end
|
77
|
+
|
78
|
+
# Convenience method to make the above code clearer.
|
79
|
+
# Converts to string and urlbase64-encodes.
|
80
|
+
def encode s
|
81
|
+
Base64.urlsafe_encode64 s.to_s
|
82
|
+
end
|
83
|
+
|
84
|
+
# a hash with a possibly frozen JSON stringification
|
85
|
+
class JSONHash < Hash
|
86
|
+
def to_s
|
87
|
+
@repr || to_json
|
88
|
+
end
|
89
|
+
|
90
|
+
def freeze
|
91
|
+
@repr = to_json.freeze
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.load raw
|
96
|
+
self[JSON.load raw.to_s].tap do |h|
|
97
|
+
h.send :repr=, raw
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
def repr= raw
|
104
|
+
@repr = raw.freeze
|
105
|
+
freeze
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Try to convert by detecting token representation and parsing
|
111
|
+
def self.JWT raw
|
112
|
+
if raw.is_a? JWT
|
113
|
+
raw
|
114
|
+
elsif raw.respond_to?(:to_h) || raw =~ /\A\s*\{/
|
115
|
+
JWT.parse_json raw
|
116
|
+
else
|
117
|
+
JWT.parse_compact raw
|
118
|
+
end
|
119
|
+
rescue
|
120
|
+
raise ArgumentError, "invalid value for JWT(): #{raw.inspect}"
|
121
|
+
end
|
122
|
+
end
|