Skip to content
This repository was archived by the owner on Aug 25, 2020. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 46 additions & 9 deletions lib/rack/oauth2/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -188,8 +188,22 @@ def get_issuer(identifier)
# user = User.find_by_username(username)
# user if user && user.authenticated?(password)
# end
Options = Struct.new(:access_token_path, :authenticator, :authorization_types,
:authorize_path, :database, :host, :param_authentication, :path, :realm,
#
# Assertion handler is a hash of blocks keyed by assertion_type. Blocks receive
# three parameters: the client, the assertion, and the scope. If authenticated,
# it returns an identity. Otherwise it can return nil or false. For example:
# oauth.assertion_handler['facebook.com'] = lambda do |client, assertion, scope|
# facebook = URI.parse('https://graph.facebook.com/me?access_token=' + assertion)
# response = Net::HTTP.get_response(facebook)
#
# user_data = JSON.parse(response.body)
# user = User.from_facebook_data(user_data)
# end
# Assertion handlers are optional; if one is not present for a given assertion
# type, no error will result.
#
Options = Struct.new(:access_token_path, :authenticator, :assertion_handler, :authorization_types,
:authorize_path, :database, :host, :param_authentication, :cookie_authentication, :path, :realm,
:expires_in,:logger, :collection_prefix)

# Global options. This is what we set during configuration (e.g. Rails'
Expand All @@ -204,10 +218,12 @@ def initialize(app, options = nil, &authenticator)
@app = app
@options = options || Server.options
@options.authenticator ||= authenticator
@options.assertion_handler ||= {}
@options.access_token_path ||= "/oauth/access_token"
@options.authorize_path ||= "/oauth/authorize"
@options.authorization_types ||= %w{code token}
@options.param_authentication ||= false
@options.cookie_authentication ||= false
@options.collection_prefix ||= "oauth2"
end

Expand All @@ -225,17 +241,32 @@ def call(env)
# Flow starts here.
return request_authorization(request, logger) if request.path == options.authorize_path
# 4. Obtaining an Access Token
return respond_with_access_token(request, logger) if request.path == options.access_token_path
if request.path == options.access_token_path
if env['CONTENT_TYPE'] =~ /^application\/json/ && request.post?
env.update({
'rack.request.form_hash' => ActiveSupport::JSON.decode(env['rack.input'].read),
'rack.request.form_input' => env['rack.input']
})
end
return respond_with_access_token(request, logger)
end

# 5. Accessing a Protected Resource
if request.authorization
# 5.1.1. The Authorization Request Header Field
token = request.credentials if request.oauth?
elsif options.param_authentication && !request.GET["oauth_verifier"] # Ignore OAuth 1.0 callbacks
# 5.1.2. URI Query Parameter
# 5.1.3. Form-Encoded Body Parameter
token = request.GET["oauth_token"] || request.POST["oauth_token"]
token ||= request.GET['access_token'] || request.POST['access_token']
else
if options.param_authentication
# 5.1.2. URI Query Parameter
# 5.1.3. Form-Encoded Body Parameter
token = request.GET["oauth_token"] || request.POST["oauth_token"]
token ||= request.GET['access_token'] || request.POST['access_token']
end

if !token && options.cookie_authentication
# 5.1.4. Cookie Value
token ||= request.cookies['oauth_token'] || request.cookies['access_token']
end
end

if token
Expand Down Expand Up @@ -422,10 +453,16 @@ def respond_with_access_token(request, logger)
assertion_type, assertion = request.POST.values_at("assertion_type", "assertion")
raise InvalidGrantError, "Missing assertion_type/assertion" unless assertion_type && assertion
# TODO: Add other supported assertion types (i.e. SAML) here
raise InvalidGrantError, "Unsupported assertion_type" if assertion_type != "urn:ietf:params:oauth:grant-type:jwt-bearer"
if assertion_type == "urn:ietf:params:oauth:grant-type:jwt-bearer"
identity = process_jwt_assertion(assertion)
access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
elsif options.assertion_handler[assertion_type]
args = [client, assertion, requested_scope]
identity = options.assertion_handler[assertion_type].call(*args)
raise InvalidGrantError, "Unknown assertion for #{assertion_type}" unless identity
access_token = AccessToken.get_token_for(identity, client, requested_scope, options.expires_in)
else
raise InvalidGrantError, "Unsupported assertion_type" if assertion_type != "urn:ietf:params:oauth:grant-type:jwt-bearer"
end
else
raise UnsupportedGrantType
Expand Down
68 changes: 65 additions & 3 deletions test/oauth/access_grant_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,19 @@ def request_with_username_password(username, password, scope = nil)
params[:password] = password if password
post "/oauth/access_token", params
end

def request_with_username_password_json(username, password, scope = nil)
body_content = {
:client_id => client.id.to_s,
:client_secret => client.secret,
:grant_type => 'password',
:username => username,
:password => password
}
body_content[:scope] = scope if scope
# Pass params as a string and it becomes the body input
post_json "/oauth/access_token", body_content.to_json
end

def request_with_assertion(assertion_type, assertion)
basic_authorize client.id, client.secret
Expand All @@ -102,7 +115,13 @@ def request_with_assertion(assertion_type, assertion)
params[:assertion] = assertion if assertion
post "/oauth/access_token", params
end


# Post with the header indicating content type and accepted response types for JSON
def post_json(*args)
header 'Accept', 'application/json'
header 'Content-Type', 'application/json'
post *args
end

# 4. Obtaining an Access Token

Expand Down Expand Up @@ -277,6 +296,41 @@ def request_with_assertion(assertion_type, assertion)
setup { request_with_assertion "urn:some:assertion:type", nil }
should_return_error :invalid_grant
end

context "assertion_type with callback" do
setup do
config.assertion_handler['special_assertion_type'] = lambda do |client, assertion, scope|
@client = client
@assertion = assertion
@scope = scope
if assertion == 'myassertion'
"Spiderman"
else
nil
end
end
request_with_assertion 'special_assertion_type', 'myassertion'
end

context "valid credentials" do
setup { request_with_assertion 'special_assertion_type', 'myassertion' }

should_respond_with_access_token "read write"
should "receive client" do
assert_equal client, @client
end
should "receieve assertion" do
assert_equal 'myassertion', @assertion
end
end

context "invalid credentials" do
setup { request_with_assertion 'special_assertion_type', 'dunno' }
should_return_error :invalid_grant
end

teardown { config.assertion_handler['special_assertion_type'] = nil }
end

context "unsupported assertion_type" do
setup { request_with_assertion "urn:some:assertion:type", "myassertion" }
Expand Down Expand Up @@ -409,8 +463,16 @@ def request_with_assertion(assertion_type, assertion)
end

context "using username/password" do
setup { request_with_username_password "cowbell", "more", "read" }
should_respond_with_access_token "read"
context "as basic auth" do
setup { request_with_username_password "cowbell", "more", "read" }
should_respond_with_access_token "read"
end

context "using username/password as JSON post" do
setup { request_with_username_password_json "cowbell", "more", "read" }
should_respond_with_access_token "read"
end
end


end
47 changes: 47 additions & 0 deletions test/oauth/access_token_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,53 @@ def with_expired_token
end
end
end

# 5.1.4. Cookie Parameter

context "cookie parameter" do
context "default mode" do
setup {
set_cookie "oauth_token=#{@token}"
get "/private"
}
should_fail_authentication
end

context "enabled" do
setup do
config.cookie_authentication = true
end

context "no token" do
setup {
clear_cookies
get "/private"
}
should_fail_authentication
end

context "valid token" do
setup {
set_cookie "oauth_token=#{@token}"
get "/private"
}
should_return_resource "Shhhh"
end

context "invalid token" do
setup {
set_cookie "oauth_token=dingdong"
get "/private"
}
should_fail_authentication :invalid_token
end

teardown do
config.cookie_authentication = false
end
end
end

end

context "POST" do
Expand Down