rack-oauth2-server 2.5.1 → 2.6.0
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 +5 -0
- data/README.md +70 -14
- data/VERSION +1 -1
- data/bin/oauth2-server +19 -0
- data/lib/rack/oauth2/models.rb +1 -1
- data/lib/rack/oauth2/models/issuer.rb +58 -0
- data/lib/rack/oauth2/server.rb +88 -0
- data/rack-oauth2-server.gemspec +1 -0
- data/test/oauth/access_grant_test.rb +129 -0
- data/test/oauth/server_methods_test.rb +12 -0
- data/test/rails2/log/test.log +58316 -0
- data/test/rails3/log/test.log +68922 -0
- data/test/setup.rb +1 -0
- metadata +15 -3
data/CHANGELOG
CHANGED
data/README.md
CHANGED
@@ -28,7 +28,7 @@ different database engine, send us a pull request.
|
|
28
28
|
For Rails 2.3/3.0, `Rack::OAuth2::Server` automatically adds itself as middleware when required, but you do need to
|
29
29
|
configure it from within `config/environment.rb` (or one of the specific environment files). For example:
|
30
30
|
|
31
|
-
```
|
31
|
+
```ruby
|
32
32
|
Rails::Initializer.run do |config|
|
33
33
|
. . .
|
34
34
|
config.after_initialize do
|
@@ -44,7 +44,7 @@ end
|
|
44
44
|
For Sinatra and Padrino, first require `rack/oauth2/sinatra` and register `Rack::OAuth2::Sinatra` into your application.
|
45
45
|
For example:
|
46
46
|
|
47
|
-
```
|
47
|
+
```ruby
|
48
48
|
require "rack/oauth2/sinatra"
|
49
49
|
|
50
50
|
class MyApp < Sinatra::Base
|
@@ -88,7 +88,7 @@ The authenticator is a block that receives either two or four parameters. The f
|
|
88
88
|
other two are the client identifier and scope. It authenticated, it returns an identity, otherwise it can return nil or
|
89
89
|
false. For example:
|
90
90
|
|
91
|
-
```
|
91
|
+
```ruby
|
92
92
|
oauth.authenticator = lambda do |username, password|
|
93
93
|
user = User.find_by_username(username)
|
94
94
|
user.id if user && user.authenticated?(password)
|
@@ -117,7 +117,7 @@ the user back to the client application with a suitable error code.
|
|
117
117
|
|
118
118
|
In Rails, the entire flow would look something like this:
|
119
119
|
|
120
|
-
```
|
120
|
+
```ruby
|
121
121
|
class OauthController < ApplicationController
|
122
122
|
def authorize
|
123
123
|
if current_user
|
@@ -143,7 +143,7 @@ cannot render anything, but can set the right response headers and return a stat
|
|
143
143
|
|
144
144
|
In Sinatra/Padrino, it would look something like this:
|
145
145
|
|
146
|
-
```
|
146
|
+
```ruby
|
147
147
|
get "/oauth/authorize" do
|
148
148
|
if current_user
|
149
149
|
render "oauth/authorize"
|
@@ -207,7 +207,7 @@ header `oauth.no_scope` to the scope name, or using `oauth_required` with the sc
|
|
207
207
|
|
208
208
|
In Rails, it would look something like this:
|
209
209
|
|
210
|
-
```
|
210
|
+
```ruby
|
211
211
|
class MyController < ApplicationController
|
212
212
|
|
213
213
|
before_filter :set_current_user
|
@@ -244,7 +244,7 @@ end
|
|
244
244
|
|
245
245
|
In Sinatra/Padrino, it would look something like this:
|
246
246
|
|
247
|
-
```
|
247
|
+
```ruby
|
248
248
|
before do
|
249
249
|
@current_user = User.find(oauth.identity) if oauth.authenticated?
|
250
250
|
end
|
@@ -309,7 +309,7 @@ Loading development environment (Rails 2.3.8)
|
|
309
309
|
You may want your application to register its own client application, always with the same client ID and secret, which
|
310
310
|
are also stored in a configuration file. For example, your `db/seed.rb` may contain:
|
311
311
|
|
312
|
-
```
|
312
|
+
```ruby
|
313
313
|
oauth2 = YAML.load_file(Rails.root + "config/oauth2.yml")
|
314
314
|
Rack::OAuth2::Server.register(id: oauth2["client_id"], secret: oauth2["client_secret"],
|
315
315
|
display_name: "UberClient", link: "http://example.com",
|
@@ -332,6 +332,41 @@ authorization process. This is typically used in server to server scenarios wher
|
|
332
332
|
two-legged flow, send the grant_type of "none" along with the client_id and client_secret to the access token path, and
|
333
333
|
a new access token will be generated (assuming the client_id and client_secret check out).
|
334
334
|
|
335
|
+
## Assertions
|
336
|
+
|
337
|
+
Rack::OAuth2::Server supports the use of assertions (in the form of the `assertion` grant_type)
|
338
|
+
to obtain access tokens. Currently JSON Web Tokens (JWT) are the only supported assertion_type.
|
339
|
+
|
340
|
+
http://tools.ietf.org/html/draft-ietf-oauth-v2-10#section-4.1.3
|
341
|
+
|
342
|
+
http://tools.ietf.org/html/draft-jones-oauth-jwt-bearer-03
|
343
|
+
|
344
|
+
In order to verify the signatures of assertions, you will need to create assertion Issuers.
|
345
|
+
You can register assertion Issuers using the command line tool `oauth2-server`:
|
346
|
+
|
347
|
+
```
|
348
|
+
$ oauth2-server register_issuer --db my_db
|
349
|
+
```
|
350
|
+
|
351
|
+
Programatically, registering a new Issuer is as simple as:
|
352
|
+
|
353
|
+
```
|
354
|
+
$ ./script/console
|
355
|
+
Loading development environment (Rails 2.3.8)
|
356
|
+
> issuer = Rack::OAuth2::Server.register_issuer(:identifier => "http://www.someidp.com",
|
357
|
+
:hmac_secret => "foo",
|
358
|
+
:public_key => "-----BEGIN RSA PUBLIC KEY-----\n....\n...=\n-----END RSA PUBLIC KEY-----\n")
|
359
|
+
```
|
360
|
+
|
361
|
+
When you call `register_issuer` it either registers a new issuer with these specific values, or if an issuer already exists with the given
|
362
|
+
identifier it will update it's properties.
|
363
|
+
|
364
|
+
Depending on the algorithm used for signing the assertion (HMAC SHA or RSA), you pass either `:hmac_secret` or `:public_key` to `Rack::OAuth2::Server.register_issuer`
|
365
|
+
(or both if you will use both with a single issuer). The value of `:public_key` can be either a PEM or DER encoded public key (as supported by `OpenSSL::PKey::RSA.new`).
|
366
|
+
|
367
|
+
Rack::OAuth2::Server validates that the issuer (`iss`), principal (`prn`), audience (`aud`) and expiration (`exp`) claims are present. It also validates that the expiration
|
368
|
+
claim has not passed (with a 10 minute padding added to account for server clock skew).
|
369
|
+
|
335
370
|
|
336
371
|
## OAuth Web Admin
|
337
372
|
|
@@ -352,7 +387,7 @@ $ oauth2-server setup --db my_db
|
|
352
387
|
Next, in your application, make sure to ONLY AUTHORIZE ADMINISTRATORS to access the Web admin, by granting them access
|
353
388
|
to the `oauth-admin` scope. For example:
|
354
389
|
|
355
|
-
```
|
390
|
+
```ruby
|
356
391
|
def grant
|
357
392
|
# Only admins allowed to authorize the scope oauth-admin
|
358
393
|
if oauth.scope.include?("oauth-admin") && !current_user.admin?
|
@@ -368,14 +403,14 @@ Make sure you do that, or you'll allow anyone access to the OAuth Web admin.
|
|
368
403
|
After this, remember to include the server admin module in your initializer (environemnt.rb or application.rb), because
|
369
404
|
this is an optional feature:
|
370
405
|
|
371
|
-
```
|
406
|
+
```ruby
|
372
407
|
require "rack/oauth2/server/admin"
|
373
408
|
```
|
374
409
|
|
375
410
|
Next, mount the OAuth Web admin as part of your application, and feed it the client ID/secret. For example, for Rails
|
376
411
|
2.3.x add this to `config/environment.rb`:
|
377
412
|
|
378
|
-
```
|
413
|
+
```ruby
|
379
414
|
Rails::Initializer.run do |config|
|
380
415
|
. . .
|
381
416
|
config.after_initialize do
|
@@ -389,7 +424,7 @@ end
|
|
389
424
|
|
390
425
|
For Rails 3.0.x, add this to you `config/application.rb`:
|
391
426
|
|
392
|
-
```
|
427
|
+
```ruby
|
393
428
|
module MyApp
|
394
429
|
class Application < Rails::Application
|
395
430
|
config.after_initialize do
|
@@ -403,11 +438,13 @@ For Rails 3.0.x, add this to you `config/application.rb`:
|
|
403
438
|
|
404
439
|
And add the follownig to `config/routes.rb`:
|
405
440
|
|
441
|
+
```ruby
|
406
442
|
mount Rack::OAuth2::Server::Admin=>"/oauth/admin"
|
443
|
+
```
|
407
444
|
|
408
445
|
For Sinatra, Padrino and other Rack-based applications, you'll want to mount like so (e.g. in `config.ru`):
|
409
446
|
|
410
|
-
```
|
447
|
+
```ruby
|
411
448
|
Rack::Builder.new do
|
412
449
|
map("/oauth/admin") { run Rack::OAuth2::Server::Admin }
|
413
450
|
map("/") { run MyApp }
|
@@ -499,6 +536,18 @@ end up sending the access token to any server you `curl`. Useful for development
|
|
499
536
|
production access tokens.
|
500
537
|
|
501
538
|
|
539
|
+
Example using the `assertion` grant_type:
|
540
|
+
|
541
|
+
```
|
542
|
+
$ curl -i http://localhost:3000/oauth/access_token \
|
543
|
+
-F client_id=4dca20453e4859cb000007 \
|
544
|
+
-F client_secret=981fa734e110496fcf667cbf52fbaf03 \
|
545
|
+
-F grant_type=assertion \
|
546
|
+
-F assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer \
|
547
|
+
-F assertion=eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJodHRwOi8vd3d3LnNvbWVjb21wYW55LmNvbSIsImF1ZCI6Imh0dHA6Ly93d3cubXljb21wYW55LmNvbSIsInBybiI6IjEyMzQ1Njc4OTAifQ.bDrcogybtJ9n5d2a971Q72ye7GN64u7WXmr2OLxSeyc
|
548
|
+
```
|
549
|
+
|
550
|
+
|
502
551
|
## Methods You'll Want To Use From Your App
|
503
552
|
|
504
553
|
You can use the Server module to create, fetch and otherwise work with access tokens and grants. Available methods
|
@@ -516,7 +565,9 @@ include:
|
|
516
565
|
client's ID and secret). Idempotent, so perfect for running during setup and migration.
|
517
566
|
- `get_auth_request` -- Resolves authorization request handle into an `AuthRequest` object. Could be useful during the
|
518
567
|
authorization flow.
|
519
|
-
|
568
|
+
- `register_issuer` -- Registers a new assertion issuer. Can also be used to change
|
569
|
+
existing issuers. Idempotent, so perfect for running during setup and migration.
|
570
|
+
- `get_issuer` -- Resolves an issuer identifier into an Issuer object.
|
520
571
|
|
521
572
|
## Mandatory ASCII Diagram
|
522
573
|
|
@@ -592,6 +643,11 @@ client).
|
|
592
643
|
An `Rack::OAuth2::Server::AccessToken` is created by copying values from an `AuthRequest` or `AccessGrant`, and remains
|
593
644
|
in effect until revoked. (OAuth 2.0 access tokens can also expire, but we don't support expiration at the moment)
|
594
645
|
|
646
|
+
### Issuer
|
647
|
+
|
648
|
+
An issuer is a identity provider which issues assertions that may be used to
|
649
|
+
obtain an access token.
|
650
|
+
|
595
651
|
|
596
652
|
## Credits
|
597
653
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.
|
1
|
+
2.6.0
|
data/bin/oauth2-server
CHANGED
@@ -63,6 +63,24 @@ when "register"
|
|
63
63
|
puts "ID\t#{client.id}"
|
64
64
|
puts "Secret\t#{client.secret}"
|
65
65
|
|
66
|
+
when "register_issuer"
|
67
|
+
fail "No database. Use the --db option to tell us which database to use" unless Server.options.database
|
68
|
+
begin
|
69
|
+
print "Identifier (typically a URL):\t"
|
70
|
+
identifier = $stdin.gets
|
71
|
+
print "HMAC secret:\t"
|
72
|
+
hmac_secret = $stdin.gets
|
73
|
+
print "RSA public key:\t\t"
|
74
|
+
public_key = $stdin.gets
|
75
|
+
issuer = Server.register_issuer(:identifier => identifier, :hmac_secret => hmac_secret, :public_key => public_key)
|
76
|
+
rescue
|
77
|
+
puts "\nFailed to register issuer: #{$!}"
|
78
|
+
exit -1
|
79
|
+
end
|
80
|
+
puts "Registered Issuer #{issuer.identifier}"
|
81
|
+
puts "HMAC secret\t#{issuer.hmac_secret}"
|
82
|
+
puts "RSA public key\t#{issuer.public_key}"
|
83
|
+
|
66
84
|
when "setup"
|
67
85
|
|
68
86
|
fail "No database. Use the --db option to tell us which database to use" unless Server.options.database
|
@@ -204,6 +222,7 @@ Commands:
|
|
204
222
|
migrate Run this when migrating from 1.x to 2.x
|
205
223
|
practice Runs a dummy OAuth 2.0 server, use this to test your OAuth 2.0 client
|
206
224
|
register Register a new client application
|
225
|
+
register_issuer Register a new assertion issuer
|
207
226
|
setup Create new admin account and help you setup the OAuth Web console
|
208
227
|
|
209
228
|
Options:
|
data/lib/rack/oauth2/models.rb
CHANGED
@@ -0,0 +1,58 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
class Server
|
4
|
+
# A third party that issues assertions
|
5
|
+
# http://tools.ietf.org/html/draft-ietf-oauth-assertions-01#section-5.1
|
6
|
+
class Issuer
|
7
|
+
class << self
|
8
|
+
|
9
|
+
# returns the Issuer object for the given identifier
|
10
|
+
def from_identifier(identifier)
|
11
|
+
Server.new_instance self, collection.find_one({:_id=>identifier})
|
12
|
+
end
|
13
|
+
|
14
|
+
# Create a new Issuer.
|
15
|
+
def create(args)
|
16
|
+
fields = {}
|
17
|
+
[:hmac_secret, :public_key, :notes].each do |key|
|
18
|
+
fields[key] = args[key] if args.has_key?(key)
|
19
|
+
end
|
20
|
+
fields[:created_at] = Time.now.to_i
|
21
|
+
fields[:updated_at] = Time.now.to_i
|
22
|
+
fields[:_id] = args[:identifier]
|
23
|
+
collection.insert(fields, :safe=>true)
|
24
|
+
Server.new_instance self, fields
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def collection
|
29
|
+
prefix = Server.options[:collection_prefix]
|
30
|
+
Server.database["#{prefix}.issuers"]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# The unique identifier of this Issuer. String or URI
|
35
|
+
attr_reader :_id
|
36
|
+
alias :identifier :_id
|
37
|
+
# shared secret used for verifying HMAC signatures
|
38
|
+
attr_reader :hmac_secret
|
39
|
+
# public key used for verifying RSA signatures
|
40
|
+
attr_reader :public_key
|
41
|
+
# notes about this Issuer
|
42
|
+
attr_reader :notes
|
43
|
+
|
44
|
+
|
45
|
+
def update(args)
|
46
|
+
fields = [:hmac_secret, :public_key, :notes].inject({}) {|h,k| v = args[k]; h[k] = v if v; h}
|
47
|
+
self.class.collection.update({:_id => identifier }, {:$set => fields})
|
48
|
+
self.class.from_identifier(identifier)
|
49
|
+
end
|
50
|
+
|
51
|
+
Server.create_indexes do
|
52
|
+
# Used to revoke all pending access grants when revoking client.
|
53
|
+
collection.create_index [[:identifier, Mongo::ASCENDING]]
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/rack/oauth2/server.rb
CHANGED
@@ -3,6 +3,7 @@ require "rack/oauth2/models"
|
|
3
3
|
require "rack/oauth2/server/errors"
|
4
4
|
require "rack/oauth2/server/utils"
|
5
5
|
require "rack/oauth2/server/helper"
|
6
|
+
require "iconv"
|
6
7
|
|
7
8
|
|
8
9
|
module Rack
|
@@ -119,8 +120,42 @@ module Rack
|
|
119
120
|
AccessToken.from_identity(identity)
|
120
121
|
end
|
121
122
|
|
123
|
+
# Registers and returns a new Issuer. Can also be used to update
|
124
|
+
# existing Issuer, by passing the identifier of an existing Issuer record.
|
125
|
+
# That way, your setup script can create a new client application and run
|
126
|
+
# repeatedly without fail.
|
127
|
+
#
|
128
|
+
# @param [Hash] args Arguments for registering Issuer
|
129
|
+
# @option args [String] :identifier Issuer identifier. Use this to update
|
130
|
+
# an existing Issuer
|
131
|
+
# @option args [String] :hmac_secret The HMAC secret for this Issuer
|
132
|
+
# @option args [String] :public_key The RSA public key (in PEM format) for this Issuer
|
133
|
+
# @option args [Array] :notes Free form text, for internal use.
|
134
|
+
#
|
135
|
+
# @example Registering new Issuer
|
136
|
+
# Server.register_issuer :hmac_secret=>"foo", :notes=>"Company A"
|
137
|
+
# @example Migration using configuration file
|
138
|
+
# config = YAML.load_file(Rails.root + "config/oauth.yml")
|
139
|
+
# Server.register_issuer config["id"],
|
140
|
+
# :hmac_secret=>"bar", :notes=>"Company A"
|
141
|
+
def register_issuer(args)
|
142
|
+
if args[:identifier] && (issuer = get_issuer(args[:identifier]))
|
143
|
+
issuer.update(args)
|
144
|
+
else
|
145
|
+
Issuer.create(args)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Returns an Issuer from it's identifier.
|
150
|
+
#
|
151
|
+
# @param [String] identifier the Issuer's identifier
|
152
|
+
# @return [Issuer]
|
153
|
+
def get_issuer(identifier)
|
154
|
+
Issuer.from_identifier(identifier)
|
155
|
+
end
|
122
156
|
end
|
123
157
|
|
158
|
+
|
124
159
|
# Options are:
|
125
160
|
# - :access_token_path -- Path for requesting access token. By convention
|
126
161
|
# defaults to /oauth/access_token.
|
@@ -381,6 +416,17 @@ module Rack
|
|
381
416
|
identity = options.authenticator.call(*args)
|
382
417
|
raise InvalidGrantError, "Username/password do not match" unless identity
|
383
418
|
access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
|
419
|
+
when "assertion"
|
420
|
+
# 4.1.3. Assertion
|
421
|
+
requested_scope = request.POST["scope"] ? Utils.normalize_scope(request.POST["scope"]) : client.scope
|
422
|
+
assertion_type, assertion = request.POST.values_at("assertion_type", "assertion")
|
423
|
+
raise InvalidGrantError, "Missing assertion_type/assertion" unless assertion_type && assertion
|
424
|
+
# TODO: Add other supported assertion types (i.e. SAML) here
|
425
|
+
raise InvalidGrantError, "Unsupported assertion_type" if assertion_type != "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
426
|
+
if assertion_type == "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
427
|
+
identity = process_jwt_assertion(assertion)
|
428
|
+
access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
|
429
|
+
end
|
384
430
|
else
|
385
431
|
raise UnsupportedGrantType
|
386
432
|
end
|
@@ -436,6 +482,48 @@ module Rack
|
|
436
482
|
return [401, { "WWW-Authenticate"=>challenge }, [error && error.message || ""]]
|
437
483
|
end
|
438
484
|
|
485
|
+
# Processes a JWT assertion
|
486
|
+
def process_jwt_assertion(assertion)
|
487
|
+
begin
|
488
|
+
require 'jwt'
|
489
|
+
require 'json'
|
490
|
+
require 'openssl'
|
491
|
+
require 'time'
|
492
|
+
# JWT.decode only returns the claims. Gotta get the header ourselves
|
493
|
+
header = JSON.parse(JWT.base64url_decode(assertion.split('.')[0]))
|
494
|
+
algorithm = header['alg']
|
495
|
+
payload = JWT.decode(assertion, nil, false)
|
496
|
+
|
497
|
+
raise InvalidGrantError, "missing issuer claim" if !payload.has_key?('iss')
|
498
|
+
|
499
|
+
issuer_identifier = payload['iss']
|
500
|
+
issuer = Issuer.from_identifier(issuer_identifier)
|
501
|
+
raise InvalidGrantError, 'Invalid issuer' if issuer.nil?
|
502
|
+
if algorithm =~ /^HS/
|
503
|
+
validated_payload = JWT.decode(assertion, issuer.hmac_secret, true)
|
504
|
+
elsif algorithm =~ /^RS/
|
505
|
+
validated_payload = JWT.decode(assertion, OpenSSL::PKey::RSA.new(issuer.public_key), true)
|
506
|
+
end
|
507
|
+
|
508
|
+
raise InvalidGrantError, "missing principal claim" if !validated_payload.has_key?('prn')
|
509
|
+
raise InvalidGrantError, "missing audience claim" if !validated_payload.has_key?('aud')
|
510
|
+
raise InvalidGrantError, "missing expiration claim" if !validated_payload.has_key?('exp')
|
511
|
+
|
512
|
+
expires = validated_payload['exp'].to_i
|
513
|
+
# add a 10 minute fudge factor for clock skew between servers
|
514
|
+
skewed_expires_time = expires + (10 * 60)
|
515
|
+
now = Time.now.utc.to_i
|
516
|
+
raise InvalidGrantError, "expired claims" if skewed_expires_time <= now
|
517
|
+
principal = validated_payload['prn']
|
518
|
+
principal
|
519
|
+
rescue JWT::DecodeError => de
|
520
|
+
raise InvalidGrantError, de.message
|
521
|
+
rescue JSON::ParserError => pe
|
522
|
+
raise InvalidGrantError, "Invalid segment encoding"
|
523
|
+
end
|
524
|
+
end
|
525
|
+
|
526
|
+
|
439
527
|
# Wraps Rack::Request to expose Basic and OAuth authentication
|
440
528
|
# credentials.
|
441
529
|
class OAuthRequest < Rack::Request
|
data/rack-oauth2-server.gemspec
CHANGED
@@ -1,4 +1,6 @@
|
|
1
1
|
require "test/setup"
|
2
|
+
require "jwt"
|
3
|
+
require "openssl"
|
2
4
|
|
3
5
|
|
4
6
|
# 4. Obtaining an Access Token
|
@@ -93,6 +95,14 @@ class AccessGrantTest < Test::Unit::TestCase
|
|
93
95
|
post "/oauth/access_token", params
|
94
96
|
end
|
95
97
|
|
98
|
+
def request_with_assertion(assertion_type, assertion)
|
99
|
+
basic_authorize client.id, client.secret
|
100
|
+
params = { :grant_type=>"assertion", :scope=>"read write" }
|
101
|
+
params[:assertion_type] = assertion_type if assertion_type
|
102
|
+
params[:assertion] = assertion if assertion
|
103
|
+
post "/oauth/access_token", params
|
104
|
+
end
|
105
|
+
|
96
106
|
|
97
107
|
# 4. Obtaining an Access Token
|
98
108
|
|
@@ -255,6 +265,125 @@ class AccessGrantTest < Test::Unit::TestCase
|
|
255
265
|
teardown { config.authenticator = @old }
|
256
266
|
end
|
257
267
|
|
268
|
+
# 4.1.3. Assertion
|
269
|
+
|
270
|
+
context "assertion" do
|
271
|
+
context "no assertion_type" do
|
272
|
+
setup { request_with_assertion nil, "myassertion" }
|
273
|
+
should_return_error :invalid_grant
|
274
|
+
end
|
275
|
+
|
276
|
+
context "no assertion" do
|
277
|
+
setup { request_with_assertion "urn:some:assertion:type", nil }
|
278
|
+
should_return_error :invalid_grant
|
279
|
+
end
|
280
|
+
|
281
|
+
context "unsupported assertion_type" do
|
282
|
+
setup { request_with_assertion "urn:some:assertion:type", "myassertion" }
|
283
|
+
should_return_error :invalid_grant
|
284
|
+
end
|
285
|
+
|
286
|
+
context "JWT" do
|
287
|
+
setup {
|
288
|
+
@hour_from_now = Time.now.utc.to_i + (60 * 60)
|
289
|
+
}
|
290
|
+
context "malformed assertion" do
|
291
|
+
setup { request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", "myassertion" }
|
292
|
+
should_return_error :invalid_grant
|
293
|
+
end
|
294
|
+
|
295
|
+
context "missing principal claim" do
|
296
|
+
setup {
|
297
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
298
|
+
@claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "exp" => @hour_from_now}
|
299
|
+
jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
|
300
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
301
|
+
}
|
302
|
+
should_return_error :invalid_grant
|
303
|
+
end
|
304
|
+
|
305
|
+
context "missing audience claim" do
|
306
|
+
setup {
|
307
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
308
|
+
@claims = {"iss" => @hmac_issuer.identifier, "prn" => "1234567890", "exp" => @hour_from_now}
|
309
|
+
jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
|
310
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
311
|
+
}
|
312
|
+
should_return_error :invalid_grant
|
313
|
+
end
|
314
|
+
|
315
|
+
context "missing expiration claim" do
|
316
|
+
setup {
|
317
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
318
|
+
@claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890"}
|
319
|
+
jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
|
320
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
321
|
+
}
|
322
|
+
should_return_error :invalid_grant
|
323
|
+
end
|
324
|
+
|
325
|
+
context "missing issuer claim" do
|
326
|
+
setup {
|
327
|
+
@claims = {"aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
|
328
|
+
jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
|
329
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
330
|
+
}
|
331
|
+
should_return_error :invalid_grant
|
332
|
+
end
|
333
|
+
|
334
|
+
context "unknown issuer" do
|
335
|
+
setup {
|
336
|
+
@claims = {"iss" => "unknown", "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
|
337
|
+
jwt_assertion = JWT.encode(@claims, "shhh", "HS256")
|
338
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
339
|
+
}
|
340
|
+
should_return_error :invalid_grant
|
341
|
+
end
|
342
|
+
|
343
|
+
context "valid HMAC assertion" do
|
344
|
+
setup {
|
345
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
346
|
+
@claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
|
347
|
+
jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
|
348
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
349
|
+
}
|
350
|
+
should_respond_with_access_token "read write"
|
351
|
+
end
|
352
|
+
|
353
|
+
context "valid RSA assertion" do
|
354
|
+
setup {
|
355
|
+
@private_key = OpenSSL::PKey::RSA.generate(512)
|
356
|
+
@rsa_issuer = Server.register_issuer(:identifier => "http://www.rsaissuer.com", :public_key => @private_key.public_key.to_pem, :notes => "Test RSA Issuer")
|
357
|
+
@claims = {"iss" => @rsa_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => @hour_from_now}
|
358
|
+
jwt_assertion = JWT.encode(@claims, @private_key, "RS256")
|
359
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
360
|
+
}
|
361
|
+
should_respond_with_access_token "read write"
|
362
|
+
end
|
363
|
+
|
364
|
+
context "expired claim set" do
|
365
|
+
setup {
|
366
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
367
|
+
@claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => Time.now.utc.to_i - (11 * 60)}
|
368
|
+
jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
|
369
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
370
|
+
}
|
371
|
+
should_return_error :invalid_grant
|
372
|
+
end
|
373
|
+
|
374
|
+
context "expiration claim within the fudge factor time" do
|
375
|
+
setup {
|
376
|
+
@hmac_issuer = Server.register_issuer(:identifier => "http://www.hmacissuer.com", :hmac_secret => "foo", :notes => "Test HMAC Issuer")
|
377
|
+
@claims = {"iss" => @hmac_issuer.identifier, "aud" => "http://www.mycompany.com", "prn" => "1234567890", "exp" => Time.now.utc.to_i - (9 * 60)}
|
378
|
+
jwt_assertion = JWT.encode(@claims, @hmac_issuer.hmac_secret, "HS256")
|
379
|
+
request_with_assertion "urn:ietf:params:oauth:grant-type:jwt-bearer", jwt_assertion
|
380
|
+
}
|
381
|
+
should_respond_with_access_token "read write"
|
382
|
+
end
|
383
|
+
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
258
387
|
|
259
388
|
# 4.2. Access Token Response
|
260
389
|
|