signet 0.4.3 → 0.4.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ # 0.4.4
2
+
3
+ * Add support for assertion profile
4
+
1
5
  # 0.4.3
2
6
 
3
7
  * Added method to clear credentials
@@ -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
  #
@@ -18,7 +18,7 @@ unless defined? Signet::VERSION
18
18
  module VERSION
19
19
  MAJOR = 0
20
20
  MINOR = 4
21
- TINY = 3
21
+ TINY = 4
22
22
 
23
23
  STRING = [MAJOR, MINOR, TINY].join('.')
24
24
  end
@@ -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.3
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-10-05 00:00:00.000000000 Z
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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: !ruby/object:Gem::Requirement
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.24
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.