rack-oauth2-server 2.5.1 → 2.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
|