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