real_savvy 0.0.6 → 0.0.7
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 +4 -4
- data/lib/real_savvy/jwt/abstract_token.rb +145 -0
- data/lib/real_savvy/jwt/config.rb +54 -0
- data/lib/real_savvy/jwt/share_token.rb +32 -0
- data/lib/real_savvy/jwt/token.rb +17 -171
- data/lib/real_savvy/jwt.rb +2 -0
- data/lib/real_savvy/version.rb +1 -1
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 83c3eb21fb475cd2006eeaeff541e57edba481c6
|
4
|
+
data.tar.gz: 3e9c22f823cb9e87dc86677df7d6c33215b95db3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6d417e599280f1bcea965a7da684ae7c1f55952d88592f52098140d02d9c97f4521fa6b76c08b10f737b06057dd51c178dc6298b1cf0661b8ee30f4cbde14b5d
|
7
|
+
data.tar.gz: 792908183863481ff23f39fce1707c55ce92d8f16bb3a77f17816105d98ef9abaf5b67af55352761eefe7a65cb4f5a14c4982d1d9975c52f29e498948662c435
|
@@ -0,0 +1,145 @@
|
|
1
|
+
module RealSavvy
|
2
|
+
module JWT
|
3
|
+
class AbstractToken
|
4
|
+
# In order of access level
|
5
|
+
SCOPE_VERBS = %w{public read write admin}.freeze
|
6
|
+
|
7
|
+
attr_reader :scopes, :user, :site, :token, :header
|
8
|
+
|
9
|
+
def initialize(token)
|
10
|
+
@token = token
|
11
|
+
standardized_token
|
12
|
+
retrieve_claims
|
13
|
+
retrieve_scopes
|
14
|
+
retrieve_audience
|
15
|
+
retrieve_site
|
16
|
+
retrieve_subject
|
17
|
+
retrieve_user
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.decode(token)
|
21
|
+
new(token)
|
22
|
+
end
|
23
|
+
|
24
|
+
def scope_includes?(*scope_parts)
|
25
|
+
!scope_parts.empty? && (
|
26
|
+
scope_parts = scope_parts.dup.map(&:to_s)
|
27
|
+
verbs_matches = self.class.verbs_matches(scope_parts.pop)
|
28
|
+
|
29
|
+
(0..scope_parts.length).any? do |depth|
|
30
|
+
verbs_matches.any? do |verb|
|
31
|
+
(scope_parts[0...depth] + [verb]).inject(scopes) do |m, v|
|
32
|
+
m&.[](v)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
)
|
37
|
+
end
|
38
|
+
|
39
|
+
def scope_includes!(*scope_parts)
|
40
|
+
scope_includes?(*scope_parts) || fail(::RealSavvy::JWT::Unauthorized)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.verbs_matches(verb)
|
44
|
+
verb_index = SCOPE_VERBS.index(verb)
|
45
|
+
verb_index ? SCOPE_VERBS[verb_index..-1] : []
|
46
|
+
end
|
47
|
+
|
48
|
+
def for_site?
|
49
|
+
audience_is_site? && subject_is_site?
|
50
|
+
end
|
51
|
+
|
52
|
+
def for_site!
|
53
|
+
for_site? || fail(::RealSavvy::JWT::Unauthorized)
|
54
|
+
end
|
55
|
+
|
56
|
+
def for_user?
|
57
|
+
audience_is_site? && (subject_is_user? || subject_is_imposter?)
|
58
|
+
end
|
59
|
+
|
60
|
+
def audience_is_site?
|
61
|
+
audience.respond_to?(:is_real_savvy_site?) &&
|
62
|
+
audience.is_real_savvy_site?
|
63
|
+
end
|
64
|
+
|
65
|
+
def subject_is_user?
|
66
|
+
subject.respond_to?(:is_real_savvy_user?) &&
|
67
|
+
subject.is_real_savvy_user?
|
68
|
+
end
|
69
|
+
|
70
|
+
def subject_is_imposter?
|
71
|
+
subject.respond_to?(:is_real_savvy_imposter?) &&
|
72
|
+
subject.is_real_savvy_imposter?
|
73
|
+
end
|
74
|
+
|
75
|
+
def subject_is_site?
|
76
|
+
subject.respond_to?(:is_real_savvy_site?) &&
|
77
|
+
subject.is_real_savvy_site?
|
78
|
+
end
|
79
|
+
|
80
|
+
def for_user!
|
81
|
+
for_user? || fail(::RealSavvy::JWT::Unauthorized)
|
82
|
+
end
|
83
|
+
|
84
|
+
def valid?
|
85
|
+
claims && claims.length > 0 && (for_site? || for_user?) && validate_token
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_token
|
89
|
+
raise NotImplementedError, "subclass did not define #validate_token"
|
90
|
+
end
|
91
|
+
|
92
|
+
def imposter?
|
93
|
+
@imposter ? true : false
|
94
|
+
end
|
95
|
+
|
96
|
+
def to_share_token
|
97
|
+
share_token_json = Hash[claims.slice('aud','sub').map{ |key,value| [key, value.to_s.split('/')[-2,2].join('/')] }].to_json
|
98
|
+
Base64.urlsafe_encode64(share_token_json, padding: false)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
|
103
|
+
attr_reader :claims, :audience, :subject
|
104
|
+
|
105
|
+
def retrieve_claims
|
106
|
+
raise NotImplementedError, "subclass did not define #retrieve_claims"
|
107
|
+
end
|
108
|
+
|
109
|
+
def retrieve_audience
|
110
|
+
@audience = ::RealSavvy::JWT::Config.retrieve_audience(claims) if claims
|
111
|
+
end
|
112
|
+
|
113
|
+
def retrieve_subject
|
114
|
+
@subject = ::RealSavvy::JWT::Config.retrieve_subject(claims) if claims
|
115
|
+
end
|
116
|
+
|
117
|
+
def retrieve_site
|
118
|
+
@site = audience
|
119
|
+
end
|
120
|
+
|
121
|
+
def retrieve_user
|
122
|
+
if subject_is_user?
|
123
|
+
@user = subject
|
124
|
+
elsif subject_is_imposter?
|
125
|
+
@user = subject.user
|
126
|
+
@imposter = true
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def raw_scopes
|
131
|
+
claims&.fetch('scopes', nil).to_a
|
132
|
+
end
|
133
|
+
|
134
|
+
def retrieve_scopes
|
135
|
+
@scopes = raw_scopes.each_with_object({}) do |scope, result|
|
136
|
+
scope.split(':').inject(result) { |m, v| m[v] ||= {} }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
def standardized_token
|
141
|
+
# If token needs to be cleaned up do it here in subclasses
|
142
|
+
end
|
143
|
+
end
|
144
|
+
end
|
145
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module RealSavvy
|
2
|
+
module JWT
|
3
|
+
module Config
|
4
|
+
def self.public_key
|
5
|
+
if block_given?
|
6
|
+
@public_key = Proc.new
|
7
|
+
else
|
8
|
+
result = @public_key.is_a?(Proc) ? @public_key.call : @public_key
|
9
|
+
result.is_a?(OpenSSL::PKey::RSA) ? result : OpenSSL::PKey::RSA.new(result)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.public_key= value
|
14
|
+
@public_key = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.retrieve_audience claims = nil
|
18
|
+
if block_given?
|
19
|
+
@retrieve_audience = Proc.new
|
20
|
+
else
|
21
|
+
@retrieve_audience.call(claims)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.retrieve_audience= value
|
26
|
+
@retrieve_audience = value
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.retrieve_subject claims = nil
|
30
|
+
if block_given?
|
31
|
+
@retrieve_subject = Proc.new
|
32
|
+
else
|
33
|
+
@retrieve_subject.call(claims)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.retrieve_subject= value
|
38
|
+
@retrieve_subject = value
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.validate_token token = nil
|
42
|
+
if block_given?
|
43
|
+
@validate_token = Proc.new
|
44
|
+
else
|
45
|
+
@validate_token.call(token)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.validate_token= value
|
50
|
+
@validate_token = value
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module RealSavvy
|
2
|
+
module JWT
|
3
|
+
class ShareToken < AbstractToken
|
4
|
+
|
5
|
+
def short_token
|
6
|
+
@token.split('.')[1]
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def retrieve_claims
|
12
|
+
@claims, @header = ::JWT.decode(
|
13
|
+
token,
|
14
|
+
nil,
|
15
|
+
false,
|
16
|
+
)
|
17
|
+
rescue ::JWT::DecodeError => e
|
18
|
+
raise ::RealSavvy::JWT::BadCredentials.new(e.message)
|
19
|
+
end
|
20
|
+
|
21
|
+
def validate_token
|
22
|
+
true
|
23
|
+
end
|
24
|
+
|
25
|
+
def standardized_token
|
26
|
+
token_parts = @token.split('.')
|
27
|
+
header = Base64.urlsafe_encode64({typ:"JWT",alg:"none"}.to_json, padding: false)
|
28
|
+
@token = [header, (token_parts.length == 1 ? token_parts[0] : token_parts[1]), nil].join('.')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/real_savvy/jwt/token.rb
CHANGED
@@ -1,186 +1,32 @@
|
|
1
1
|
module RealSavvy
|
2
2
|
module JWT
|
3
|
-
class Token
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
retrieve_scopes
|
13
|
-
retrieve_audience
|
14
|
-
retrieve_site
|
15
|
-
retrieve_subject
|
16
|
-
retrieve_user
|
17
|
-
end
|
18
|
-
|
19
|
-
def self.public_key
|
20
|
-
if block_given?
|
21
|
-
@public_key = Proc.new
|
22
|
-
else
|
23
|
-
result = @public_key.is_a?(Proc) ? @public_key.call : @public_key
|
24
|
-
result.is_a?(OpenSSL::PKey::RSA) ? result : OpenSSL::PKey::RSA.new(result)
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
def self.public_key= value
|
29
|
-
@public_key = value
|
30
|
-
end
|
31
|
-
|
32
|
-
def self.retrieve_audience claims = nil
|
33
|
-
if block_given?
|
34
|
-
@retrieve_audience = Proc.new
|
35
|
-
else
|
36
|
-
@retrieve_audience.call(claims)
|
37
|
-
end
|
38
|
-
end
|
39
|
-
|
40
|
-
def self.retrieve_audience= value
|
41
|
-
@retrieve_audience = value
|
42
|
-
end
|
43
|
-
|
44
|
-
def self.retrieve_subject claims = nil
|
45
|
-
if block_given?
|
46
|
-
@retrieve_subject = Proc.new
|
47
|
-
else
|
48
|
-
@retrieve_subject.call(claims)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def self.retrieve_subject= value
|
53
|
-
@retrieve_subject = value
|
54
|
-
end
|
55
|
-
|
56
|
-
def self.validate_token token = nil
|
57
|
-
if block_given?
|
58
|
-
@validate_token = Proc.new
|
59
|
-
else
|
60
|
-
@validate_token.call(token)
|
61
|
-
end
|
62
|
-
end
|
63
|
-
|
64
|
-
def self.validate_token= value
|
65
|
-
@validate_token = value
|
66
|
-
end
|
67
|
-
|
68
|
-
def self.decode(token)
|
69
|
-
new(token)
|
70
|
-
end
|
71
|
-
|
72
|
-
def scope_includes?(*scope_parts)
|
73
|
-
!scope_parts.empty? && (
|
74
|
-
scope_parts = scope_parts.dup.map(&:to_s)
|
75
|
-
verbs_matches = self.class.verbs_matches(scope_parts.pop)
|
76
|
-
|
77
|
-
(0..scope_parts.length).any? do |depth|
|
78
|
-
verbs_matches.any? do |verb|
|
79
|
-
(scope_parts[0...depth] + [verb]).inject(scopes) do |m, v|
|
80
|
-
m&.[](v)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
3
|
+
class Token < AbstractToken
|
4
|
+
|
5
|
+
def to_share_token
|
6
|
+
share_token_payload_keys = ['aud','sub']
|
7
|
+
share_token_payload = ::Hash[[share_token_payload_keys, claims.values_at(*share_token_payload_keys)].transpose]
|
8
|
+
ShareToken.new(
|
9
|
+
::JWT.encode(
|
10
|
+
share_token_payload, nil, 'none'
|
11
|
+
)
|
84
12
|
)
|
85
13
|
end
|
86
14
|
|
87
|
-
def scope_includes!(*scope_parts)
|
88
|
-
scope_includes?(*scope_parts) || fail(::RealSavvy::JWT::Unauthorized)
|
89
|
-
end
|
90
|
-
|
91
|
-
def self.verbs_matches(verb)
|
92
|
-
verb_index = SCOPE_VERBS.index(verb)
|
93
|
-
verb_index ? SCOPE_VERBS[verb_index..-1] : []
|
94
|
-
end
|
95
|
-
|
96
|
-
def for_site?
|
97
|
-
audience_is_site? && subject_is_site?
|
98
|
-
end
|
99
|
-
|
100
|
-
def for_site!
|
101
|
-
for_site? || fail(::RealSavvy::JWT::Unauthorized)
|
102
|
-
end
|
103
|
-
|
104
|
-
def for_user?
|
105
|
-
audience_is_site? && (subject_is_user? || subject_is_imposter?)
|
106
|
-
end
|
107
|
-
|
108
|
-
def audience_is_site?
|
109
|
-
audience.respond_to?(:is_real_savvy_site?) &&
|
110
|
-
audience.is_real_savvy_site?
|
111
|
-
end
|
112
|
-
|
113
|
-
def subject_is_user?
|
114
|
-
subject.respond_to?(:is_real_savvy_user?) &&
|
115
|
-
subject.is_real_savvy_user?
|
116
|
-
end
|
117
|
-
|
118
|
-
def subject_is_imposter?
|
119
|
-
subject.respond_to?(:is_real_savvy_imposter?) &&
|
120
|
-
subject.is_real_savvy_imposter?
|
121
|
-
end
|
122
|
-
|
123
|
-
def subject_is_site?
|
124
|
-
subject.respond_to?(:is_real_savvy_site?) &&
|
125
|
-
subject.is_real_savvy_site?
|
126
|
-
end
|
127
|
-
|
128
|
-
def for_user!
|
129
|
-
for_user? || fail(::RealSavvy::JWT::Unauthorized)
|
130
|
-
end
|
131
|
-
|
132
|
-
def valid?
|
133
|
-
claims && claims.length > 0 && (for_site? || for_user?) && self.class.validate_token(token)
|
134
|
-
end
|
135
|
-
|
136
|
-
def imposter?
|
137
|
-
@imposter ? true : false
|
138
|
-
end
|
139
|
-
|
140
15
|
private
|
141
16
|
|
142
|
-
attr_reader :claims, :audience, :subject
|
143
|
-
|
144
17
|
def retrieve_claims
|
145
|
-
@claims = ::JWT.decode(
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
18
|
+
@claims, @header = ::JWT.decode(
|
19
|
+
token,
|
20
|
+
::RealSavvy::JWT::Config.public_key,
|
21
|
+
true,
|
22
|
+
algorithm: 'RS256',
|
23
|
+
)
|
151
24
|
rescue ::JWT::DecodeError => e
|
152
25
|
raise ::RealSavvy::JWT::BadCredentials.new(e.message)
|
153
26
|
end
|
154
27
|
|
155
|
-
def
|
156
|
-
|
157
|
-
end
|
158
|
-
|
159
|
-
def retrieve_subject
|
160
|
-
@subject = self.class.retrieve_subject(claims) if claims
|
161
|
-
end
|
162
|
-
|
163
|
-
def retrieve_site
|
164
|
-
@site = audience
|
165
|
-
end
|
166
|
-
|
167
|
-
def retrieve_user
|
168
|
-
if subject_is_user?
|
169
|
-
@user = subject
|
170
|
-
elsif subject_is_imposter?
|
171
|
-
@user = subject.user
|
172
|
-
@imposter = true
|
173
|
-
end
|
174
|
-
end
|
175
|
-
|
176
|
-
def raw_scopes
|
177
|
-
claims&.fetch('scopes', nil).to_a
|
178
|
-
end
|
179
|
-
|
180
|
-
def retrieve_scopes
|
181
|
-
@scopes = raw_scopes.each_with_object({}) do |scope, result|
|
182
|
-
scope.split(':').inject(result) { |m, v| m[v] ||= {} }
|
183
|
-
end
|
28
|
+
def validate_token
|
29
|
+
::RealSavvy::JWT::Config.validate_token(token)
|
184
30
|
end
|
185
31
|
end
|
186
32
|
end
|
data/lib/real_savvy/jwt.rb
CHANGED
data/lib/real_savvy/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: real_savvy
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ryan Rauh
|
@@ -10,7 +10,7 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: exe
|
12
12
|
cert_chain: []
|
13
|
-
date: 2018-03-
|
13
|
+
date: 2018-03-26 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: faraday
|
@@ -166,8 +166,11 @@ files:
|
|
166
166
|
- lib/real_savvy/connection.rb
|
167
167
|
- lib/real_savvy/document.rb
|
168
168
|
- lib/real_savvy/jwt.rb
|
169
|
+
- lib/real_savvy/jwt/abstract_token.rb
|
169
170
|
- lib/real_savvy/jwt/bad_credentials.rb
|
171
|
+
- lib/real_savvy/jwt/config.rb
|
170
172
|
- lib/real_savvy/jwt/imposter.rb
|
173
|
+
- lib/real_savvy/jwt/share_token.rb
|
171
174
|
- lib/real_savvy/jwt/site.rb
|
172
175
|
- lib/real_savvy/jwt/token.rb
|
173
176
|
- lib/real_savvy/jwt/unauthorized.rb
|