Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added Assertion Flow #249

Closed
wants to merge 14 commits into from
Closed
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,22 @@ If you are not using devise, you may want to check other ways of
authentication
[here](https://github.com/doorkeeper-gem/doorkeeper/wiki/Authenticating-using-Clearance-or-DIY).

### Authenticating with Assertion

You may want to configure Doorkeeper to handle authentication via assertion. This will let you define your own way of authenticating
resource owners via 3rd Party applications (e.g. Facebook).

```ruby
Doorkeeper.configure do
resource_owner_from_assertion do
facebook = URI.parse('https://graph.facebook.com/me?access_token=' + params[:assertion])
response = Net::HTTP.get_response(facebook)
user_data = JSON.parse(response.body)
User.find_by_facebook_id(user_data['id'])
end
end
```

## Protecting resources with OAuth (a.k.a your API endpoint)

To protect your API with OAuth, doorkeeper only requires you to call
Expand Down
1 change: 1 addition & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ en:

#configuration error messages
credential_flow_not_configured: 'Resource Owner Password Credentials flow failed due to Doorkeeper.configure.resource_owner_from_credentials being unconfigured.'
assertion_flow_not_configured: 'Resource Owner Assertion flow failed due to Doorkeeper.configure.resource_owner_from_assertion being unconfigured.'
resource_owner_authenticator_not_configured: 'Resource Owner find failed due to Doorkeeper.configure.resource_owner_authenticator being unconfiged.'

# Access grant errors
Expand Down
5 changes: 5 additions & 0 deletions lib/doorkeeper/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,11 @@ def extended(base)
warn(I18n.translate('doorkeeper.errors.messages.credential_flow_not_configured'))
nil
}
option :resource_owner_from_assertion,
:default => lambda{|routes|
warn(I18n.translate('doorkeeper.errors.messages.assertion_flow_not_configured'))
nil
}
option :skip_authorization, :default => lambda{|routes|}
option :access_token_expires_in, :default => 7200
option :authorization_code_expires_in,:default => 600
Expand Down
5 changes: 5 additions & 0 deletions lib/doorkeeper/helpers/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ def self.included(base)
:authenticate_admin!,
:current_resource_owner,
:resource_owner_from_credentials,
:resource_owner_from_assertion,
:skip_authorization?
end

Expand All @@ -22,6 +23,10 @@ def resource_owner_from_credentials
instance_eval(&Doorkeeper.configuration.resource_owner_from_credentials)
end

def resource_owner_from_assertion
instance_eval(&Doorkeeper.configuration.resource_owner_from_assertion)
end

def authenticate_admin!
instance_eval(&Doorkeeper.configuration.authenticate_admin)
end
Expand Down
3 changes: 2 additions & 1 deletion lib/doorkeeper/request.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'doorkeeper/request/authorization_code'
require 'doorkeeper/request/client_credentials'
require 'doorkeeper/request/assertion'
require 'doorkeeper/request/code'
require 'doorkeeper/request/password'
require 'doorkeeper/request/refresh_token'
Expand All @@ -16,7 +17,7 @@ def authorization_strategy(strategy)
end

def token_strategy(strategy)
get_strategy strategy, %w[password client_credentials authorization_code refresh_token]
get_strategy strategy, %w[password client_credentials authorization_code refresh_token assertion]

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [103/80]

rescue NameError
raise Errors::InvalidTokenStrategy
end
Expand Down
25 changes: 25 additions & 0 deletions lib/doorkeeper/request/assertion.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
module Doorkeeper
module Request
class Assertion
def self.build(server)
new(server.credentials, server.resource_owner_from_assertion, server)
end

attr_accessor :credentials, :resource_owner, :server

def initialize(credentials, resource_owner, server)
@credentials, @resource_owner, @server = credentials, resource_owner, server

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [84/80]

end

def request
# TODO: For now OAuth::PasswordAccessTokenRequest is reused for the Assertion Flow. In need of
# OAuth::AssertionAccessTokenRequest in future
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How exactly would an AssertionAccessTokenRequest be different than a PasswordAccessTokenRequest?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There shouldn't be any differences in the PasswordAccessTokenRequest and the potential AssertionAccessTokenRequest to be honest, but I think by using the PasswordAccessTokenRequest for the assertion flow, the name didn't suite as much anymore. What do you think? I could also just remove the TODO comment

@request ||= OAuth::PasswordAccessTokenRequest.new(Doorkeeper.configuration, credentials, resource_owner, server.parameters)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line is too long. [132/80]

end

def authorize
request.authorize
end
end
end
end
4 changes: 4 additions & 0 deletions lib/doorkeeper/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,10 @@ def resource_owner
context.send :resource_owner_from_credentials
end

def resource_owner_from_assertion
context.send :resource_owner_from_assertion
end

def credentials
methods = Doorkeeper.configuration.client_credentials_methods
@credentials ||= OAuth::Client::Credentials.from_request(context.request, *methods)
Expand Down
8 changes: 5 additions & 3 deletions spec/dummy/app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,20 +9,22 @@ class User

field :name, :type => String
field :password, :type => String
field :assertion, :type => String
end
when :mongo_mapper
class User
include MongoMapper::Document
timestamps!

key :name, String
key :password, String
key :name, String
key :password, String
key :assertion, String
end
end

class User
if ::Rails.version.to_i < 4
attr_accessible :name, :password
attr_accessible :name, :password, :assertion
end

def self.authenticate!(name, password)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AddAssertionToUsers < ActiveRecord::Migration
def change
add_column :users, :assertion, :string
end
end
7 changes: 4 additions & 3 deletions spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@
#
# It's strongly recommended to check this file into your version control system.

ActiveRecord::Schema.define(:version => 20130902175349) do
ActiveRecord::Schema.define(:version => 20140422215603) do

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Separate every 3 digits in the integer portion of a number with underscores(_).


create_table "oauth_access_grants", :force => true do |t|

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.integer "resource_owner_id", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.integer "application_id", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "token", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.integer "expires_in", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.text "redirect_uri", :null => false
t.string "redirect_uri", :limit => 2048, :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.datetime "created_at", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.datetime "revoked_at"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "scopes"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

Expand All @@ -45,7 +45,7 @@
t.string "name", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "uid", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "secret", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.text "redirect_uri", :null => false
t.string "redirect_uri", :limit => 2048, :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.datetime "created_at", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.datetime "updated_at", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.integer "owner_id"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

Expand All @@ -60,6 +60,7 @@
t.datetime "created_at", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.datetime "updated_at", :null => false

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use the new Ruby 1.9 hash syntax.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "password"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

t.string "assertion"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put one space between the method name and the first argument.
Prefer single-quoted strings when you don't need string interpolation or special symbols.

end

end
64 changes: 64 additions & 0 deletions spec/requests/flows/assertion_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# coding: utf-8

require 'spec_helper_integration'

feature 'Resource Owner Assertion Flow inproperly set up' do
background do
client_exists
create_resource_owner
end

context 'with valid user assertion' do
scenario "should not issue new token" do
expect {
post assertion_endpoint_url(:client => @client, :resource_owner => @resource_owner)
}.to_not change { Doorkeeper::AccessToken.count }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does it hit ErrorResponse? We should assert that (if there's no config, this should not work).

end
end
end

feature 'Resource Owner Assertion Flow' do
background do
config_is_set(:resource_owner_from_assertion) { User.where(:assertion => params[:assertion]).first }
client_exists
create_resource_owner
end

context 'with valid user assertion' do
scenario "should issue new token" do
expect {
post assertion_endpoint_url(:client => @client, :resource_owner => @resource_owner)
}.to change { Doorkeeper::AccessToken.count }.by(1)

token = Doorkeeper::AccessToken.first

should_have_json 'access_token', token.token
end

scenario "should issue a refresh token if enabled" do
config_is_set(:refresh_token_enabled, true)

post assertion_endpoint_url(:client => @client, :resource_owner => @resource_owner)

token = Doorkeeper::AccessToken.first

should_have_json 'refresh_token', token.refresh_token
end

end

context "with invalid user assertion" do
scenario "should not issue new token with bad assertion" do
expect {
post assertion_endpoint_url( :client => @client, :assertion => 'i_dont_exist' )
}.to_not change { Doorkeeper::AccessToken.count }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should assert it returns an ErrorResponse.

end

scenario "should not issue new token without assertion" do
expect {
post assertion_endpoint_url( :client => @client )
}.to_not change { Doorkeeper::AccessToken.count }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should assert it returns an ErrorResponse.

end

end
end
2 changes: 1 addition & 1 deletion spec/support/helpers/model_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ def client_exists(client_attributes = {})
end

def create_resource_owner
@resource_owner = User.create!(:name => "Joe", :password => "sekret")
@resource_owner = User.create!(:name => "Joe", :password => "sekret", :assertion => "assertion")
end

def authorization_code_exists(options = {})
Expand Down
12 changes: 12 additions & 0 deletions spec/support/helpers/url_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,18 @@ def refresh_token_endpoint_url(options = {})
"/oauth/token?#{build_query(parameters)}"
end

def assertion_endpoint_url(options = {})
parameters = {
:code => options[:code],
:client_id => options[:client_id] || options[:client].uid,
:client_secret => options[:client_secret] || options[:client].secret,
:redirect_uri => options[:redirect_uri] || options[:client].redirect_uri,
:grant_type => options[:grant_type] || "assertion",
:assertion => options[:assertion] || (options[:resource_owner] ? options[:resource_owner].assertion : nil)
}
"/oauth/token?#{build_query(parameters)}"
end

def revocation_token_endpoint_url
"/oauth/revoke"
end
Expand Down