rack-oauth2 0.1.0 → 0.2.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/README.rdoc +15 -11
- data/Rakefile +2 -1
- data/VERSION +1 -1
- data/lib/rack/oauth2/server/abstract/request.rb +13 -7
- data/lib/rack/oauth2/server/authorize.rb +3 -1
- data/lib/rack/oauth2/server/error.rb +18 -18
- data/lib/rack/oauth2/server/error/authorize.rb +54 -0
- data/lib/rack/oauth2/server/error/resource.rb +50 -0
- data/lib/rack/oauth2/server/error/token.rb +59 -0
- data/lib/rack/oauth2/server/resource.rb +14 -11
- data/lib/rack/oauth2/server/token.rb +16 -5
- data/rack-oauth2.gemspec +14 -6
- data/spec/rack/oauth2/server/authorize/code_and_token_spec.rb +14 -4
- data/spec/rack/oauth2/server/authorize/code_spec.rb +14 -4
- data/spec/rack/oauth2/server/authorize/token_spec.rb +14 -4
- data/spec/rack/oauth2/server/error/authorize_spec.rb +103 -0
- data/spec/rack/oauth2/server/error/resource_spec.rb +69 -0
- data/spec/rack/oauth2/server/error/token_spec.rb +115 -0
- data/spec/rack/oauth2/server/error_spec.rb +35 -5
- data/spec/rack/oauth2/server/resource_spec.rb +36 -6
- data/spec/rack/oauth2/server/token/assertion_spec.rb +9 -6
- data/spec/rack/oauth2/server/token/authorization_code_spec.rb +60 -18
- data/spec/rack/oauth2/server/token/password_spec.rb +9 -6
- data/spec/rack/oauth2/server/token/refresh_token_spec.rb +9 -6
- data/spec/rack/oauth2/server/util_spec.rb +26 -0
- metadata +16 -8
- data/example/server/authorize.rb +0 -57
- data/example/server/oauth2_controller.rb +0 -100
- data/example/server/token.rb +0 -20
data/README.rdoc
CHANGED
@@ -7,7 +7,7 @@ http://tools.ietf.org/html/draft-ietf-oauth-v2-10
|
|
7
7
|
|
8
8
|
== Installation
|
9
9
|
|
10
|
-
gem install
|
10
|
+
gem install rack-oauth2
|
11
11
|
|
12
12
|
== Resources
|
13
13
|
|
@@ -17,23 +17,27 @@ http://tools.ietf.org/html/draft-ietf-oauth-v2-10
|
|
17
17
|
|
18
18
|
== Usage
|
19
19
|
|
20
|
-
|
20
|
+
=== Rails
|
21
21
|
|
22
|
-
|
22
|
+
==== Resource Owner Authorization & Token Endpoint
|
23
23
|
|
24
|
-
|
25
|
-
* example/server/authorize.rb (Sinatra)
|
24
|
+
http://gist.github.com/584594
|
26
25
|
|
27
|
-
|
26
|
+
==== Protected Resource Middleware Setting
|
28
27
|
|
29
|
-
|
30
|
-
* example/server/token.rb (Sinatra)
|
28
|
+
http://gist.github.com/584565
|
31
29
|
|
32
|
-
===
|
30
|
+
=== Sinatra
|
33
31
|
|
34
|
-
|
32
|
+
==== Resource Owner Authorization Endpoint
|
35
33
|
|
36
|
-
|
34
|
+
http://gist.github.com/584595
|
35
|
+
|
36
|
+
==== Token Endpoint
|
37
|
+
|
38
|
+
http://gist.github.com/584574
|
39
|
+
|
40
|
+
== Note on Patches/Pull Requests
|
37
41
|
|
38
42
|
* Fork the project.
|
39
43
|
* Make your feature addition or bug fix.
|
data/Rakefile
CHANGED
@@ -5,7 +5,7 @@ begin
|
|
5
5
|
require 'jeweler'
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = 'rack-oauth2'
|
8
|
-
gem.summary = %Q{Rack Middleware for OAuth2
|
8
|
+
gem.summary = %Q{Rack Middleware for OAuth2 Server}
|
9
9
|
gem.description = %Q{Rack Middleware for OAuth2. Currently support only Server/Provider, not Client/Consumer.}
|
10
10
|
gem.email = 'nov@matake.jp'
|
11
11
|
gem.homepage = 'http://github.com/nov/rack-oauth2'
|
@@ -30,6 +30,7 @@ Spec::Rake::SpecTask.new(:rcov) do |spec|
|
|
30
30
|
spec.libs << 'lib' << 'spec'
|
31
31
|
spec.pattern = 'spec/**/*_spec.rb'
|
32
32
|
spec.rcov = true
|
33
|
+
spec.rcov_opts = ['--exclude spec,gems']
|
33
34
|
end
|
34
35
|
|
35
36
|
task :spec => :check_dependencies
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -7,24 +7,30 @@ module Rack
|
|
7
7
|
|
8
8
|
def initialize(env)
|
9
9
|
super
|
10
|
-
verify_required_params
|
11
|
-
@client_id
|
10
|
+
missing_params = verify_required_params
|
11
|
+
@client_id ||= params['client_id']
|
12
12
|
@scope = Array(params['scope'].to_s.split(' '))
|
13
|
+
missing_params << :client_id if @client_id.blank?
|
14
|
+
unless missing_params.blank?
|
15
|
+
invalid_request!("'#{missing_params.join('\', \'')}' required.", :state => @state, :redirect_uri => @redirect_uri)
|
16
|
+
end
|
17
|
+
if params['client_id'].present? && @client_id != params['client_id']
|
18
|
+
invalid_client!("Multiple client credentials are provided.")
|
19
|
+
end
|
13
20
|
end
|
14
21
|
|
15
22
|
def required_params
|
16
|
-
[
|
23
|
+
[]
|
17
24
|
end
|
18
25
|
|
19
|
-
def verify_required_params
|
26
|
+
def verify_required_params
|
20
27
|
missing_params = []
|
21
28
|
required_params.each do |key|
|
22
29
|
missing_params << key unless params[key.to_s]
|
23
30
|
end
|
24
|
-
|
25
|
-
raise BadRequest.new(:invalid_request, "'#{missing_params.join('\', \'')}' required", :state => @state, :redirect_uri => @redirect_uri)
|
26
|
-
end
|
31
|
+
missing_params
|
27
32
|
end
|
33
|
+
|
28
34
|
end
|
29
35
|
end
|
30
36
|
end
|
@@ -11,6 +11,7 @@ module Rack
|
|
11
11
|
end
|
12
12
|
|
13
13
|
class Request < Abstract::Request
|
14
|
+
include Error::Authorize
|
14
15
|
attr_accessor :response_type, :redirect_uri, :state
|
15
16
|
|
16
17
|
def initialize(env)
|
@@ -32,9 +33,10 @@ module Rack
|
|
32
33
|
when 'code_and_token'
|
33
34
|
CodeAndToken
|
34
35
|
else
|
35
|
-
|
36
|
+
unsupported_response_type!("'#{params['response_type']}' isn't supported.")
|
36
37
|
end
|
37
38
|
end
|
39
|
+
|
38
40
|
end
|
39
41
|
|
40
42
|
class Response < Abstract::Response
|
@@ -14,14 +14,6 @@ module Rack
|
|
14
14
|
@realm = options[:realm]
|
15
15
|
@scope = Array(options[:scope])
|
16
16
|
@redirect_uri = Util.parse_uri(options[:redirect_uri]) if options[:redirect_uri]
|
17
|
-
@www_authenticate =
|
18
|
-
@channel = if options[:www_authenticate].present?
|
19
|
-
:www_authenticate
|
20
|
-
elsif @redirect_uri.present?
|
21
|
-
:query_string
|
22
|
-
else
|
23
|
-
:json_body
|
24
|
-
end
|
25
17
|
end
|
26
18
|
|
27
19
|
def finish
|
@@ -35,39 +27,47 @@ module Rack
|
|
35
27
|
value.blank?
|
36
28
|
end
|
37
29
|
response = Rack::Response.new
|
38
|
-
|
39
|
-
when :www_authenticate
|
40
|
-
response.status = status
|
41
|
-
response.header['WWW-Authenticate'] = "OAuth realm='#{realm}' #{params.collect { |key, value| "#{key}='#{value.to_s}'" }.join(' ')}"
|
42
|
-
response.write params.to_json
|
43
|
-
when :query_string
|
30
|
+
if @redirect_uri.present?
|
44
31
|
redirect_uri.query = if redirect_uri.query
|
45
32
|
[redirect_uri.query, params.to_query].join('&')
|
46
33
|
else
|
47
34
|
params.to_query
|
48
35
|
end
|
49
36
|
response.redirect redirect_uri.to_s
|
50
|
-
|
37
|
+
else
|
51
38
|
response.status = status
|
52
39
|
response.header['Content-Type'] = 'application/json'
|
40
|
+
if realm.present?
|
41
|
+
response.header['WWW-Authenticate'] = "OAuth realm='#{realm}' #{params.collect { |key, value| "#{key}='#{value.to_s}'" }.join(' ')}"
|
42
|
+
end
|
53
43
|
response.write params.to_json
|
54
44
|
end
|
55
45
|
response.finish
|
56
46
|
end
|
57
47
|
end
|
58
48
|
|
49
|
+
class BadRequest < Error
|
50
|
+
def initialize(error, description = "", options = {})
|
51
|
+
super(400, error, description, options)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
59
55
|
class Unauthorized < Error
|
60
56
|
def initialize(error, description = "", options = {})
|
61
57
|
super(401, error, description, options)
|
62
58
|
end
|
63
59
|
end
|
64
60
|
|
65
|
-
class
|
61
|
+
class Forbidden < Error
|
66
62
|
def initialize(error, description = "", options = {})
|
67
|
-
super(
|
63
|
+
super(403, error, description, options)
|
68
64
|
end
|
69
65
|
end
|
70
66
|
|
71
67
|
end
|
72
68
|
end
|
73
|
-
end
|
69
|
+
end
|
70
|
+
|
71
|
+
require 'rack/oauth2/server/error/authorize'
|
72
|
+
require 'rack/oauth2/server/error/token'
|
73
|
+
require 'rack/oauth2/server/error/resource'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
module Server
|
4
|
+
class Error
|
5
|
+
module Authorize
|
6
|
+
|
7
|
+
DEFAULT_DESCRIPTION = {
|
8
|
+
:invalid_request => "The request is missing a required parameter, includes an unsupported parameter or parameter value, or is otherwise malformed.",
|
9
|
+
:invalid_client => "The client identifier provided is invalid.",
|
10
|
+
:unauthorized_client => "The client is not authorized to use the requested response type.",
|
11
|
+
:redirect_uri_mismatch => "The redirection URI provided does not match a pre-registered value.",
|
12
|
+
:access_denied => "The end-user or authorization server denied the request.",
|
13
|
+
:unsupported_response_type => "The requested response type is not supported by the authorization server.",
|
14
|
+
:invalid_scope => "The requested scope is invalid, unknown, or malformed."
|
15
|
+
}
|
16
|
+
|
17
|
+
def error!(error, description = nil, options = {})
|
18
|
+
description ||= DEFAULT_DESCRIPTION[error]
|
19
|
+
raise BadRequest.new(error, description, options.merge(:state => state, :redirect_uri => redirect_uri))
|
20
|
+
end
|
21
|
+
|
22
|
+
def invalid_request!(description = nil, options = {})
|
23
|
+
error!(:invalid_request, description, options)
|
24
|
+
end
|
25
|
+
|
26
|
+
def invalid_client!(description = nil, options = {})
|
27
|
+
error!(:invalid_client, description, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def unauthorized_client!(description = nil, options = {})
|
31
|
+
error!(:unauthorized_client, description, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def redirect_uri_mismatch!(description = nil, options = {})
|
35
|
+
error!(:redirect_uri_mismatch, description, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def access_denied!(description = nil, options = {})
|
39
|
+
error!(:access_denied, description, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def unsupported_response_type!(description = nil, options = {})
|
43
|
+
error!(:unsupported_response_type, description, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
def invalid_scope!(description = nil, options = {})
|
47
|
+
error!(:invalid_scope, description, options)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
module Server
|
4
|
+
class Error
|
5
|
+
module Resource
|
6
|
+
|
7
|
+
DEFAULT_DESCRIPTION = {
|
8
|
+
:invalid_request => "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats the same parameter, uses more than one method for including an access token, or is otherwise malformed.",
|
9
|
+
:invalid_token => "The access token provided is invalid.",
|
10
|
+
:expired_token => "The access token provided has expired.",
|
11
|
+
:insufficient_scope => "The request requires higher privileges than provided by the access token."
|
12
|
+
}
|
13
|
+
|
14
|
+
def error!(error, description = nil, options = {})
|
15
|
+
description ||= DEFAULT_DESCRIPTION[error]
|
16
|
+
options[:realm] = realm
|
17
|
+
exception = case error
|
18
|
+
when :invalid_token, :expired_token
|
19
|
+
Unauthorized
|
20
|
+
when :insufficient_scope
|
21
|
+
Forbidden
|
22
|
+
when :invalid_request
|
23
|
+
BadRequest
|
24
|
+
else
|
25
|
+
raise Error.new(options[:status] || 400, error, description, options)
|
26
|
+
end
|
27
|
+
raise exception.new(error, description, options)
|
28
|
+
end
|
29
|
+
|
30
|
+
def invalid_request!(description = nil, options = {})
|
31
|
+
error!(:invalid_request, description, options)
|
32
|
+
end
|
33
|
+
|
34
|
+
def invalid_token!(description = nil, options = {})
|
35
|
+
error!(:invalid_token, description, options)
|
36
|
+
end
|
37
|
+
|
38
|
+
def expired_token!(description = nil, options = {})
|
39
|
+
error!(:expired_token, description, options)
|
40
|
+
end
|
41
|
+
|
42
|
+
def insufficient_scope!(description = nil, options = {})
|
43
|
+
error!(:insufficient_scope, description, options)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module Rack
|
2
|
+
module OAuth2
|
3
|
+
module Server
|
4
|
+
class Error
|
5
|
+
module Token
|
6
|
+
|
7
|
+
DEFAULT_DESCRIPTION = {
|
8
|
+
:invalid_request => "The request is missing a required parameter, includes an unsupported parameter or parameter value, repeats a parameter, includes multiple credentials, utilizes more than one mechanism for authenticating the client, or is otherwise malformed.",
|
9
|
+
:invalid_client => "The client identifier provided is invalid, the client failed to authenticate, the client did not include its credentials, provided multiple client credentials, or used unsupported credentials type.",
|
10
|
+
:unauthorized_client => "The authenticated client is not authorized to use the access grant type provided.",
|
11
|
+
:invalid_grant => "The provided access grant is invalid, expired, or revoked (e.g. invalid assertion, expired authorization token, bad end-user password credentials, or mismatching authorization code and redirection URI).",
|
12
|
+
:unsupported_grant_type => "The access grant included - its type or another attribute - is not supported by the authorization server.",
|
13
|
+
:unsupported_response_type => "The requested response type is not supported by the authorization server.",
|
14
|
+
:invalid_scope => "The requested scope is invalid, unknown, malformed, or exceeds the previously granted scope."
|
15
|
+
}
|
16
|
+
|
17
|
+
def error!(error, description = nil, options = {})
|
18
|
+
description ||= DEFAULT_DESCRIPTION[error]
|
19
|
+
exception = if options.delete(:unauthorized)
|
20
|
+
Unauthorized
|
21
|
+
else
|
22
|
+
BadRequest
|
23
|
+
end
|
24
|
+
raise exception.new(error, description, options)
|
25
|
+
end
|
26
|
+
|
27
|
+
def invalid_request!(description = nil, options = {})
|
28
|
+
error!(:invalid_request, description, options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def invalid_client!(description = nil, options = {})
|
32
|
+
error!(:invalid_client, description, options.merge(:unauthorized => via_authorization_header))
|
33
|
+
end
|
34
|
+
|
35
|
+
def unauthorized_client!(description = nil, options = {})
|
36
|
+
error!(:unauthorized_client, description, options)
|
37
|
+
end
|
38
|
+
|
39
|
+
def invalid_grant!(description = nil, options = {})
|
40
|
+
error!(:invalid_grant, description, options)
|
41
|
+
end
|
42
|
+
|
43
|
+
def unsupported_grant_type!(description = nil, options = {})
|
44
|
+
error!(:unsupported_grant_type, description, options)
|
45
|
+
end
|
46
|
+
|
47
|
+
def unsupported_response_type!(description = nil, options = {})
|
48
|
+
error!(:unsupported_response_type, description, options)
|
49
|
+
end
|
50
|
+
|
51
|
+
def invalid_scope!(description = nil, options = {})
|
52
|
+
error!(:invalid_scope, description, options)
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -5,13 +5,13 @@ module Rack
|
|
5
5
|
module Server
|
6
6
|
class Resource < Abstract::Handler
|
7
7
|
|
8
|
-
def initialize(app, realm=nil, &authenticator)
|
8
|
+
def initialize(app, realm = nil, &authenticator)
|
9
9
|
@app = app
|
10
10
|
super(realm, &authenticator)
|
11
11
|
end
|
12
12
|
|
13
13
|
def call(env)
|
14
|
-
request = Request.new(env)
|
14
|
+
request = Request.new(env, realm)
|
15
15
|
if request.oauth2?
|
16
16
|
authenticate!(request)
|
17
17
|
env[ACCESS_TOKEN] = request.access_token
|
@@ -29,9 +29,13 @@ module Rack
|
|
29
29
|
end
|
30
30
|
|
31
31
|
class Request < Rack::Request
|
32
|
+
include Error::Resource
|
32
33
|
|
33
|
-
|
34
|
+
attr_accessor :realm
|
35
|
+
|
36
|
+
def initialize(env, realm)
|
34
37
|
@env = env
|
38
|
+
@realm = realm
|
35
39
|
@auth_header = Rack::Auth::AbstractRequest.new(env)
|
36
40
|
end
|
37
41
|
|
@@ -40,15 +44,14 @@ module Rack
|
|
40
44
|
end
|
41
45
|
|
42
46
|
def access_token
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
when access_token_in_haeder.blank? && access_token_in_payload.present?
|
47
|
-
access_token_in_payload
|
48
|
-
when access_token_in_haeder.present? && access_token_in_payload.present?
|
49
|
-
raise BadRequest.new(:invalid_request, 'Both Authorization header and payload includes oauth_token.', :www_authenticate => true)
|
50
|
-
else
|
47
|
+
tokens = [access_token_in_haeder, access_token_in_payload].compact
|
48
|
+
case Array(tokens).size
|
49
|
+
when 0
|
51
50
|
nil
|
51
|
+
when 1
|
52
|
+
tokens.first
|
53
|
+
else
|
54
|
+
invalid_request!('Both Authorization header and payload includes oauth_token.', :realm => realm)
|
52
55
|
end
|
53
56
|
end
|
54
57
|
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'rack/auth/basic'
|
2
|
+
|
1
3
|
module Rack
|
2
4
|
module OAuth2
|
3
5
|
module Server
|
@@ -11,12 +13,21 @@ module Rack
|
|
11
13
|
end
|
12
14
|
|
13
15
|
class Request < Abstract::Request
|
14
|
-
|
16
|
+
include Error::Token
|
17
|
+
|
18
|
+
attr_accessor :grant_type, :client_secret, :via_authorization_header
|
15
19
|
|
16
20
|
def initialize(env)
|
17
|
-
|
18
|
-
|
19
|
-
|
21
|
+
auth = Rack::Auth::Basic::Request.new(env)
|
22
|
+
if auth.provided? && auth.basic?
|
23
|
+
@client_id, @client_secret = auth.credentials
|
24
|
+
@via_authorization_header = true
|
25
|
+
super
|
26
|
+
else
|
27
|
+
super
|
28
|
+
@client_secret = params['client_secret']
|
29
|
+
end
|
30
|
+
@grant_type = params['grant_type']
|
20
31
|
end
|
21
32
|
|
22
33
|
def required_params
|
@@ -34,7 +45,7 @@ module Rack
|
|
34
45
|
when 'refresh_token'
|
35
46
|
RefreshToken
|
36
47
|
else
|
37
|
-
|
48
|
+
unsupported_grant_type!("'#{params['grant_type']}' isn't supported.")
|
38
49
|
end
|
39
50
|
end
|
40
51
|
|