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.
@@ -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.