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
7 changes: 6 additions & 1 deletion lib/doorkeeper/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,11 @@ def extended(base)
warn(I18n.translate('doorkeeper.errors.messages.credential_flow_not_configured'))
nil
end)
option :resource_owner_from_assertion,
default: (lambda do |routes|
warn(I18n.translate('doorkeeper.errors.messages.assertion_flow_not_configured'))
nil
end)
option :skip_authorization, default: ->(routes) {}
option :access_token_expires_in, default: 7200
option :authorization_code_expires_in, default: 600
Expand All @@ -169,7 +174,7 @@ def extended(base)
option :realm, default: 'Doorkeeper'
option :wildcard_redirect_uri, default: false
option :grant_flows,
default: %w(authorization_code implicit password client_credentials)
default: %w(authorization_code implicit password client_credentials assertion)

def refresh_token_enabled?
!!@refresh_token_enabled
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
1 change: 1 addition & 0 deletions 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 Down
23 changes: 23 additions & 0 deletions lib/doorkeeper/request/assertion.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
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
@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
3 changes: 2 additions & 1 deletion spec/dummy/db/schema.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
#
# 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

create_table 'oauth_access_grants', force: true do |t|
t.integer 'resource_owner_id', null: false
Expand Down Expand Up @@ -60,6 +60,7 @@
t.datetime 'created_at', null: false
t.datetime 'updated_at', null: false
t.string 'password'
t.string 'assertion'
end

end
3 changes: 2 additions & 1 deletion spec/lib/config_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,8 @@
'authorization_code',
'implicit',
'password',
'client_credentials'
'client_credentials',
'assertion'
]
end

Expand Down
76 changes: 76 additions & 0 deletions spec/requests/flows/assertion_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# 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).


should_have_json 'error', 'invalid_resource_owner'
should_have_json 'error_description', translated_error_message(:invalid_resource_owner)

Choose a reason for hiding this comment

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

Line is too long. [93/80]

expect(response.status).to eq(401)
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.


should_have_json 'error', 'invalid_resource_owner'
should_have_json 'error_description', translated_error_message(:invalid_resource_owner)

Choose a reason for hiding this comment

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

Line is too long. [93/80]

expect(response.status).to eq(401)
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.


should_have_json 'error', 'invalid_resource_owner'
should_have_json 'error_description', translated_error_message(:invalid_resource_owner)

Choose a reason for hiding this comment

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

Line is too long. [93/80]

expect(response.status).to eq(401)
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