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