signet 0.4.3 → 0.4.4
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.
- data/CHANGELOG.md +4 -0
- data/lib/signet/oauth_2/client.rb +150 -0
- data/lib/signet/version.rb +1 -1
- data/spec/signet/oauth_2/client_spec.rb +85 -0
- metadata +17 -52
data/CHANGELOG.md
CHANGED
|
@@ -58,6 +58,14 @@ module Signet
|
|
|
58
58
|
# The resource owner's username.
|
|
59
59
|
# - <code>:password</code> —
|
|
60
60
|
# The resource owner's password.
|
|
61
|
+
# - <code>:issuer</code> —
|
|
62
|
+
# Issuer ID when using assertion profile
|
|
63
|
+
# - <code>:person</code> -
|
|
64
|
+
# Target user for assertions
|
|
65
|
+
# - <code>:expiry</code> -
|
|
66
|
+
# Number of seconds assertions are valid for
|
|
67
|
+
# - <code>:signing_key</code> —
|
|
68
|
+
# Signing key when using assertion profile
|
|
61
69
|
# - <code>:refresh_token</code> —
|
|
62
70
|
# The refresh token associated with the access token
|
|
63
71
|
# to be refreshed.
|
|
@@ -116,6 +124,16 @@ module Signet
|
|
|
116
124
|
# The resource owner's username.
|
|
117
125
|
# - <code>:password</code> —
|
|
118
126
|
# The resource owner's password.
|
|
127
|
+
# - <code>:issuer</code> —
|
|
128
|
+
# Issuer ID when using assertion profile
|
|
129
|
+
# - <code>:audience</code> —
|
|
130
|
+
# Target audience for assertions
|
|
131
|
+
# - <code>:person</code> -
|
|
132
|
+
# Target user for assertions
|
|
133
|
+
# - <code>:expiry</code> -
|
|
134
|
+
# Number of seconds assertions are valid for
|
|
135
|
+
# - <code>:signing_key</code> —
|
|
136
|
+
# Signing key when using assertion profile
|
|
119
137
|
# - <code>:refresh_token</code> —
|
|
120
138
|
# The refresh token associated with the access token
|
|
121
139
|
# to be refreshed.
|
|
@@ -149,6 +167,11 @@ module Signet
|
|
|
149
167
|
self.redirect_uri = options["redirect_uri"]
|
|
150
168
|
self.username = options["username"]
|
|
151
169
|
self.password = options["password"]
|
|
170
|
+
self.issuer = options["issuer"]
|
|
171
|
+
self.person = options["person"]
|
|
172
|
+
self.expiry = options["expiry"] || 60
|
|
173
|
+
self.audience = options["audience"]
|
|
174
|
+
self.signing_key = options["signing_key"]
|
|
152
175
|
self.extension_parameters = options["extension_parameters"] || {}
|
|
153
176
|
self.update_token!(options)
|
|
154
177
|
return self
|
|
@@ -451,6 +474,109 @@ module Signet
|
|
|
451
474
|
@password = new_password
|
|
452
475
|
end
|
|
453
476
|
|
|
477
|
+
##
|
|
478
|
+
# Returns the issuer ID associated with this client.
|
|
479
|
+
# Used only by the assertion grant type.
|
|
480
|
+
#
|
|
481
|
+
# @return [String] Issuer id.
|
|
482
|
+
def issuer
|
|
483
|
+
return @issuer
|
|
484
|
+
end
|
|
485
|
+
|
|
486
|
+
##
|
|
487
|
+
# Sets the issuer ID associated with this client.
|
|
488
|
+
# Used only by the assertion grant type.
|
|
489
|
+
#
|
|
490
|
+
# @param [String] new_issuer
|
|
491
|
+
# Issuer ID (typical in email adddress form).
|
|
492
|
+
def issuer=(new_issuer)
|
|
493
|
+
@issuer = new_issuer
|
|
494
|
+
end
|
|
495
|
+
|
|
496
|
+
##
|
|
497
|
+
# Returns the issuer ID associated with this client.
|
|
498
|
+
# Used only by the assertion grant type.
|
|
499
|
+
#
|
|
500
|
+
# @return [String] Target audience ID.
|
|
501
|
+
def audience
|
|
502
|
+
return @audience
|
|
503
|
+
end
|
|
504
|
+
|
|
505
|
+
##
|
|
506
|
+
# Sets the target audience ID when issuing assertions.
|
|
507
|
+
# Used only by the assertion grant type.
|
|
508
|
+
#
|
|
509
|
+
# @param [String] new_issuer
|
|
510
|
+
# Target audience ID
|
|
511
|
+
def audience=(new_audience)
|
|
512
|
+
@audience = new_audience
|
|
513
|
+
end
|
|
514
|
+
|
|
515
|
+
##
|
|
516
|
+
# Returns the target resource owner for impersonation.
|
|
517
|
+
# Used only by the assertion grant type.
|
|
518
|
+
#
|
|
519
|
+
# @return [String] Target user for impersonation.
|
|
520
|
+
def person
|
|
521
|
+
return @person
|
|
522
|
+
end
|
|
523
|
+
|
|
524
|
+
##
|
|
525
|
+
# Sets the target resource owner for impersonation.
|
|
526
|
+
# Used only by the assertion grant type.
|
|
527
|
+
#
|
|
528
|
+
# @param [String] new_person
|
|
529
|
+
# Target user for impersonation
|
|
530
|
+
def person=(new_person)
|
|
531
|
+
@person = new_person
|
|
532
|
+
end
|
|
533
|
+
|
|
534
|
+
##
|
|
535
|
+
# Returns the number of seconds assertions are valid for
|
|
536
|
+
# Used only by the assertion grant type.
|
|
537
|
+
#
|
|
538
|
+
# @return [Fixnum] Assertion expiry, in seconds
|
|
539
|
+
def expiry
|
|
540
|
+
return @expiry
|
|
541
|
+
end
|
|
542
|
+
|
|
543
|
+
##
|
|
544
|
+
# Sets the number of seconds assertions are valid for
|
|
545
|
+
# Used only by the assertion grant type.
|
|
546
|
+
#
|
|
547
|
+
# @param [String] new_expiry
|
|
548
|
+
# Assertion expiry, in seconds
|
|
549
|
+
def expiry=(new_expiry)
|
|
550
|
+
@expiry = new_expiry
|
|
551
|
+
end
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
##
|
|
555
|
+
# Returns the signing key associated with this client.
|
|
556
|
+
# Used only by the assertion grant type.
|
|
557
|
+
#
|
|
558
|
+
# @return [String,OpenSSL::PKey] Signing key
|
|
559
|
+
def signing_key
|
|
560
|
+
return @signing_key
|
|
561
|
+
end
|
|
562
|
+
|
|
563
|
+
##
|
|
564
|
+
# Sets the signing key when issuing assertions.
|
|
565
|
+
# Used only by the assertion grant type.
|
|
566
|
+
#
|
|
567
|
+
# @param [String, OpenSSL::Pkey] new_key
|
|
568
|
+
# Signing key. Either private key for RSA or string for HMAC algorithm
|
|
569
|
+
def signing_key=(new_key)
|
|
570
|
+
@signing_key = new_key
|
|
571
|
+
end
|
|
572
|
+
|
|
573
|
+
##
|
|
574
|
+
# Algorithm used for signing JWTs
|
|
575
|
+
# @return [String] Signing algorithm
|
|
576
|
+
def signing_algorithm
|
|
577
|
+
self.signing_key.is_a?(String) ? "HS256" : "RS256"
|
|
578
|
+
end
|
|
579
|
+
|
|
454
580
|
##
|
|
455
581
|
# Returns the set of extension parameters used by the client.
|
|
456
582
|
# Used only by extension access grant types.
|
|
@@ -637,6 +763,8 @@ module Signet
|
|
|
637
763
|
'refresh_token'
|
|
638
764
|
elsif self.username && self.password
|
|
639
765
|
'password'
|
|
766
|
+
elsif self.issuer && self.signing_key
|
|
767
|
+
'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
|
640
768
|
else
|
|
641
769
|
# We don't have sufficient auth information, assume an out-of-band
|
|
642
770
|
# authorization arrangement between the client and server, or an
|
|
@@ -656,6 +784,20 @@ module Signet
|
|
|
656
784
|
end
|
|
657
785
|
end
|
|
658
786
|
|
|
787
|
+
def to_jwt(options={})
|
|
788
|
+
now = Time.new
|
|
789
|
+
skew = options[:skew] || 60
|
|
790
|
+
assertion = {
|
|
791
|
+
"iss" => self.issuer,
|
|
792
|
+
"scope" => self.scope.join(' '),
|
|
793
|
+
"aud" => self.audience,
|
|
794
|
+
"exp" => (now + self.expiry).to_i,
|
|
795
|
+
"iat" => (now - skew).to_i
|
|
796
|
+
}
|
|
797
|
+
assertion['prn'] = self.person unless self.person.nil?
|
|
798
|
+
JWT.encode(assertion, self.signing_key, self.signing_algorithm)
|
|
799
|
+
end
|
|
800
|
+
|
|
659
801
|
##
|
|
660
802
|
# Generates a request for token credentials.
|
|
661
803
|
#
|
|
@@ -682,6 +824,8 @@ module Signet
|
|
|
682
824
|
parameters['password'] = self.password
|
|
683
825
|
when 'refresh_token'
|
|
684
826
|
parameters['refresh_token'] = self.refresh_token
|
|
827
|
+
when 'urn:ietf:params:oauth:grant-type:jwt-bearer'
|
|
828
|
+
parameters['assertion'] = self.to_jwt(options)
|
|
685
829
|
else
|
|
686
830
|
if self.redirect_uri
|
|
687
831
|
# Grant type was intended to be `authorization_code` because of
|
|
@@ -747,6 +891,12 @@ module Signet
|
|
|
747
891
|
return token_hash
|
|
748
892
|
end
|
|
749
893
|
|
|
894
|
+
##
|
|
895
|
+
# Refresh the access token, if possible
|
|
896
|
+
def refresh!
|
|
897
|
+
self.fetch_access_token!
|
|
898
|
+
end
|
|
899
|
+
|
|
750
900
|
##
|
|
751
901
|
# Generates an authenticated request for protected resources.
|
|
752
902
|
#
|
data/lib/signet/version.rb
CHANGED
|
@@ -139,6 +139,91 @@ describe Signet::OAuth2::Client, 'unconfigured' do
|
|
|
139
139
|
end
|
|
140
140
|
end
|
|
141
141
|
|
|
142
|
+
describe Signet::OAuth2::Client, 'configured for assertions profile' do
|
|
143
|
+
|
|
144
|
+
describe 'when using RSA keys' do
|
|
145
|
+
before do
|
|
146
|
+
@key = OpenSSL::PKey::RSA.new 2048
|
|
147
|
+
@client = Signet::OAuth2::Client.new(
|
|
148
|
+
:token_credential_uri =>
|
|
149
|
+
'https://accounts.google.com/o/oauth2/token',
|
|
150
|
+
:scope => 'https://www.googleapis.com/auth/userinfo.profile',
|
|
151
|
+
:issuer => 'app@example.com',
|
|
152
|
+
:audience => 'https://accounts.google.com/o/oauth2/token',
|
|
153
|
+
:signing_key => @key
|
|
154
|
+
)
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
it 'should generate valid JWTs' do
|
|
158
|
+
jwt = @client.to_jwt
|
|
159
|
+
jwt.should_not == nil
|
|
160
|
+
|
|
161
|
+
claim = JWT.decode(jwt, @key.public_key, true)
|
|
162
|
+
claim["iss"].should == 'app@example.com'
|
|
163
|
+
claim["scope"].should == 'https://www.googleapis.com/auth/userinfo.profile'
|
|
164
|
+
claim["aud"].should == 'https://accounts.google.com/o/oauth2/token'
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
it 'should generate valid JWTs for impersonation' do
|
|
168
|
+
@client.person = 'user@example.com'
|
|
169
|
+
jwt = @client.to_jwt
|
|
170
|
+
jwt.should_not == nil
|
|
171
|
+
|
|
172
|
+
claim = JWT.decode(jwt, @key.public_key, true)
|
|
173
|
+
claim["iss"].should == 'app@example.com'
|
|
174
|
+
claim["prn"].should == 'user@example.com'
|
|
175
|
+
claim["scope"].should == 'https://www.googleapis.com/auth/userinfo.profile'
|
|
176
|
+
claim["aud"].should == 'https://accounts.google.com/o/oauth2/token'
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
it 'should send valid access token request' do
|
|
180
|
+
stubs = Faraday::Adapter::Test::Stubs.new do |stub|
|
|
181
|
+
stub.post('/o/oauth2/token') do |env|
|
|
182
|
+
params = Addressable::URI.form_unencode(env[:body])
|
|
183
|
+
jwt = JWT.decode(params.assoc("assertion").last, @key.public_key)
|
|
184
|
+
params.assoc("grant_type").should == ['grant_type','urn:ietf:params:oauth:grant-type:jwt-bearer']
|
|
185
|
+
[200, {}, '{
|
|
186
|
+
"access_token" : "1/abcdef1234567890",
|
|
187
|
+
"token_type" : "Bearer",
|
|
188
|
+
"expires_in" : 3600
|
|
189
|
+
}']
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
connection = Faraday.new(:url => 'https://www.google.com') do |builder|
|
|
193
|
+
builder.adapter(:test, stubs)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
@client.fetch_access_token!(:connection => connection)
|
|
197
|
+
@client.access_token.should == "1/abcdef1234567890"
|
|
198
|
+
stubs.verify_stubbed_calls
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
describe 'when using shared secrets' do
|
|
203
|
+
before do
|
|
204
|
+
@key = 'my secret key'
|
|
205
|
+
@client = Signet::OAuth2::Client.new(
|
|
206
|
+
:token_credential_uri =>
|
|
207
|
+
'https://accounts.google.com/o/oauth2/token',
|
|
208
|
+
:scope => 'https://www.googleapis.com/auth/userinfo.profile',
|
|
209
|
+
:issuer => 'app@example.com',
|
|
210
|
+
:audience => 'https://accounts.google.com/o/oauth2/token',
|
|
211
|
+
:signing_key => @key
|
|
212
|
+
)
|
|
213
|
+
end
|
|
214
|
+
|
|
215
|
+
it 'should generate valid JWTs' do
|
|
216
|
+
jwt = @client.to_jwt
|
|
217
|
+
jwt.should_not == nil
|
|
218
|
+
|
|
219
|
+
claim = JWT.decode(jwt, @key, true)
|
|
220
|
+
claim["iss"].should == 'app@example.com'
|
|
221
|
+
claim["scope"].should == 'https://www.googleapis.com/auth/userinfo.profile'
|
|
222
|
+
claim["aud"].should == 'https://accounts.google.com/o/oauth2/token'
|
|
223
|
+
end
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
142
227
|
describe Signet::OAuth2::Client, 'configured for Google userinfo API' do
|
|
143
228
|
before do
|
|
144
229
|
@client = Signet::OAuth2::Client.new(
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: signet
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.4.
|
|
4
|
+
version: 0.4.4
|
|
5
5
|
prerelease:
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
@@ -9,11 +9,11 @@ authors:
|
|
|
9
9
|
autorequire:
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
|
-
date: 2012-
|
|
12
|
+
date: 2012-12-08 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
|
14
14
|
- !ruby/object:Gem::Dependency
|
|
15
15
|
name: addressable
|
|
16
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirement: &70335622285100 !ruby/object:Gem::Requirement
|
|
17
17
|
none: false
|
|
18
18
|
requirements:
|
|
19
19
|
- - ! '>='
|
|
@@ -21,15 +21,10 @@ dependencies:
|
|
|
21
21
|
version: 2.2.3
|
|
22
22
|
type: :runtime
|
|
23
23
|
prerelease: false
|
|
24
|
-
version_requirements:
|
|
25
|
-
none: false
|
|
26
|
-
requirements:
|
|
27
|
-
- - ! '>='
|
|
28
|
-
- !ruby/object:Gem::Version
|
|
29
|
-
version: 2.2.3
|
|
24
|
+
version_requirements: *70335622285100
|
|
30
25
|
- !ruby/object:Gem::Dependency
|
|
31
26
|
name: faraday
|
|
32
|
-
requirement: !ruby/object:Gem::Requirement
|
|
27
|
+
requirement: &70335622284080 !ruby/object:Gem::Requirement
|
|
33
28
|
none: false
|
|
34
29
|
requirements:
|
|
35
30
|
- - ~>
|
|
@@ -37,15 +32,10 @@ dependencies:
|
|
|
37
32
|
version: 0.8.1
|
|
38
33
|
type: :runtime
|
|
39
34
|
prerelease: false
|
|
40
|
-
version_requirements:
|
|
41
|
-
none: false
|
|
42
|
-
requirements:
|
|
43
|
-
- - ~>
|
|
44
|
-
- !ruby/object:Gem::Version
|
|
45
|
-
version: 0.8.1
|
|
35
|
+
version_requirements: *70335622284080
|
|
46
36
|
- !ruby/object:Gem::Dependency
|
|
47
37
|
name: multi_json
|
|
48
|
-
requirement: !ruby/object:Gem::Requirement
|
|
38
|
+
requirement: &70335622283520 !ruby/object:Gem::Requirement
|
|
49
39
|
none: false
|
|
50
40
|
requirements:
|
|
51
41
|
- - ! '>='
|
|
@@ -53,15 +43,10 @@ dependencies:
|
|
|
53
43
|
version: 1.0.0
|
|
54
44
|
type: :runtime
|
|
55
45
|
prerelease: false
|
|
56
|
-
version_requirements:
|
|
57
|
-
none: false
|
|
58
|
-
requirements:
|
|
59
|
-
- - ! '>='
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 1.0.0
|
|
46
|
+
version_requirements: *70335622283520
|
|
62
47
|
- !ruby/object:Gem::Dependency
|
|
63
48
|
name: jwt
|
|
64
|
-
requirement: !ruby/object:Gem::Requirement
|
|
49
|
+
requirement: &70335622282880 !ruby/object:Gem::Requirement
|
|
65
50
|
none: false
|
|
66
51
|
requirements:
|
|
67
52
|
- - ! '>='
|
|
@@ -69,15 +54,10 @@ dependencies:
|
|
|
69
54
|
version: 0.1.5
|
|
70
55
|
type: :runtime
|
|
71
56
|
prerelease: false
|
|
72
|
-
version_requirements:
|
|
73
|
-
none: false
|
|
74
|
-
requirements:
|
|
75
|
-
- - ! '>='
|
|
76
|
-
- !ruby/object:Gem::Version
|
|
77
|
-
version: 0.1.5
|
|
57
|
+
version_requirements: *70335622282880
|
|
78
58
|
- !ruby/object:Gem::Dependency
|
|
79
59
|
name: rake
|
|
80
|
-
requirement: !ruby/object:Gem::Requirement
|
|
60
|
+
requirement: &70335622281960 !ruby/object:Gem::Requirement
|
|
81
61
|
none: false
|
|
82
62
|
requirements:
|
|
83
63
|
- - ! '>='
|
|
@@ -85,15 +65,10 @@ dependencies:
|
|
|
85
65
|
version: 0.9.0
|
|
86
66
|
type: :development
|
|
87
67
|
prerelease: false
|
|
88
|
-
version_requirements:
|
|
89
|
-
none: false
|
|
90
|
-
requirements:
|
|
91
|
-
- - ! '>='
|
|
92
|
-
- !ruby/object:Gem::Version
|
|
93
|
-
version: 0.9.0
|
|
68
|
+
version_requirements: *70335622281960
|
|
94
69
|
- !ruby/object:Gem::Dependency
|
|
95
70
|
name: rspec
|
|
96
|
-
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirement: &70335622281020 !ruby/object:Gem::Requirement
|
|
97
72
|
none: false
|
|
98
73
|
requirements:
|
|
99
74
|
- - ! '>='
|
|
@@ -101,15 +76,10 @@ dependencies:
|
|
|
101
76
|
version: 2.11.0
|
|
102
77
|
type: :development
|
|
103
78
|
prerelease: false
|
|
104
|
-
version_requirements:
|
|
105
|
-
none: false
|
|
106
|
-
requirements:
|
|
107
|
-
- - ! '>='
|
|
108
|
-
- !ruby/object:Gem::Version
|
|
109
|
-
version: 2.11.0
|
|
79
|
+
version_requirements: *70335622281020
|
|
110
80
|
- !ruby/object:Gem::Dependency
|
|
111
81
|
name: launchy
|
|
112
|
-
requirement: !ruby/object:Gem::Requirement
|
|
82
|
+
requirement: &70335622273940 !ruby/object:Gem::Requirement
|
|
113
83
|
none: false
|
|
114
84
|
requirements:
|
|
115
85
|
- - ! '>='
|
|
@@ -117,12 +87,7 @@ dependencies:
|
|
|
117
87
|
version: 2.1.1
|
|
118
88
|
type: :development
|
|
119
89
|
prerelease: false
|
|
120
|
-
version_requirements:
|
|
121
|
-
none: false
|
|
122
|
-
requirements:
|
|
123
|
-
- - ! '>='
|
|
124
|
-
- !ruby/object:Gem::Version
|
|
125
|
-
version: 2.1.1
|
|
90
|
+
version_requirements: *70335622273940
|
|
126
91
|
description: ! 'Signet is an OAuth 1.0 / OAuth 2.0 implementation.
|
|
127
92
|
|
|
128
93
|
'
|
|
@@ -194,7 +159,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
194
159
|
version: '0'
|
|
195
160
|
requirements: []
|
|
196
161
|
rubyforge_project:
|
|
197
|
-
rubygems_version: 1.8.
|
|
162
|
+
rubygems_version: 1.8.10
|
|
198
163
|
signing_key:
|
|
199
164
|
specification_version: 3
|
|
200
165
|
summary: Signet is an OAuth 1.0 / OAuth 2.0 implementation.
|