Add ability to get user info from access tokens
This commit is contained in:
parent
a1f3d5f5f9
commit
efdfe318fd
18 changed files with 295 additions and 46 deletions
3
Gemfile
3
Gemfile
|
|
@ -279,6 +279,9 @@ group :test do
|
||||||
gem "database_cleaner" , "1.5.1"
|
gem "database_cleaner" , "1.5.1"
|
||||||
gem "selenium-webdriver", "2.47.1"
|
gem "selenium-webdriver", "2.47.1"
|
||||||
|
|
||||||
|
gem "cucumber-api-steps", "0.13", require: false
|
||||||
|
gem "json_spec", "1.1.4"
|
||||||
|
|
||||||
# General helpers
|
# General helpers
|
||||||
|
|
||||||
gem "factory_girl_rails", "4.5.0"
|
gem "factory_girl_rails", "4.5.0"
|
||||||
|
|
|
||||||
56
Gemfile.lock
56
Gemfile.lock
|
|
@ -57,6 +57,7 @@ GEM
|
||||||
ast (2.2.0)
|
ast (2.2.0)
|
||||||
astrolabe (1.3.1)
|
astrolabe (1.3.1)
|
||||||
parser (~> 2.2)
|
parser (~> 2.2)
|
||||||
|
attr_required (1.0.0)
|
||||||
autoprefixer-rails (6.2.2)
|
autoprefixer-rails (6.2.2)
|
||||||
execjs
|
execjs
|
||||||
json
|
json
|
||||||
|
|
@ -66,6 +67,7 @@ GEM
|
||||||
jquery-rails
|
jquery-rails
|
||||||
railties
|
railties
|
||||||
bcrypt (3.1.10)
|
bcrypt (3.1.10)
|
||||||
|
bindata (2.1.0)
|
||||||
bootstrap-sass (3.3.6)
|
bootstrap-sass (3.3.6)
|
||||||
autoprefixer-rails (>= 5.2.1)
|
autoprefixer-rails (>= 5.2.1)
|
||||||
sass (>= 3.3.4)
|
sass (>= 3.3.4)
|
||||||
|
|
@ -126,6 +128,10 @@ GEM
|
||||||
gherkin (~> 2.12)
|
gherkin (~> 2.12)
|
||||||
multi_json (>= 1.7.5, < 2.0)
|
multi_json (>= 1.7.5, < 2.0)
|
||||||
multi_test (>= 0.1.2)
|
multi_test (>= 0.1.2)
|
||||||
|
cucumber-api-steps (0.13)
|
||||||
|
cucumber (>= 1.2.1)
|
||||||
|
jsonpath (>= 0.1.2)
|
||||||
|
rspec (>= 2.12.0)
|
||||||
cucumber-rails (1.4.2)
|
cucumber-rails (1.4.2)
|
||||||
capybara (>= 1.1.2, < 3)
|
capybara (>= 1.1.2, < 3)
|
||||||
cucumber (>= 1.3.8, < 2)
|
cucumber (>= 1.3.8, < 2)
|
||||||
|
|
@ -390,6 +396,7 @@ GEM
|
||||||
httparty (0.13.7)
|
httparty (0.13.7)
|
||||||
json (~> 1.8)
|
json (~> 1.8)
|
||||||
multi_xml (>= 0.5.2)
|
multi_xml (>= 0.5.2)
|
||||||
|
httpclient (2.7.1)
|
||||||
i18n (0.7.0)
|
i18n (0.7.0)
|
||||||
i18n-inflector (2.6.7)
|
i18n-inflector (2.6.7)
|
||||||
i18n (>= 0.4.1)
|
i18n (>= 0.4.1)
|
||||||
|
|
@ -423,8 +430,19 @@ GEM
|
||||||
multi_json (>= 1.3)
|
multi_json (>= 1.3)
|
||||||
rake
|
rake
|
||||||
json (1.8.3)
|
json (1.8.3)
|
||||||
|
json-jwt (1.5.1)
|
||||||
|
activesupport
|
||||||
|
bindata
|
||||||
|
multi_json (>= 1.3)
|
||||||
|
securecompare
|
||||||
|
url_safe_base64
|
||||||
json-schema (2.5.2)
|
json-schema (2.5.2)
|
||||||
addressable (~> 2.3.8)
|
addressable (~> 2.3.8)
|
||||||
|
json_spec (1.1.4)
|
||||||
|
multi_json (~> 1.0)
|
||||||
|
rspec (>= 2.0, < 4.0)
|
||||||
|
jsonpath (0.5.7)
|
||||||
|
multi_json
|
||||||
jwt (1.5.2)
|
jwt (1.5.2)
|
||||||
kaminari (0.16.3)
|
kaminari (0.16.3)
|
||||||
actionpack (>= 3.0.0)
|
actionpack (>= 3.0.0)
|
||||||
|
|
@ -504,6 +522,17 @@ GEM
|
||||||
open_graph_reader (0.6.1)
|
open_graph_reader (0.6.1)
|
||||||
faraday (~> 0.9.0)
|
faraday (~> 0.9.0)
|
||||||
nokogiri (~> 1.6)
|
nokogiri (~> 1.6)
|
||||||
|
openid_connect (0.9.2)
|
||||||
|
activemodel
|
||||||
|
attr_required (>= 1.0.0)
|
||||||
|
json (>= 1.4.3)
|
||||||
|
json-jwt (>= 1.5.0)
|
||||||
|
rack-oauth2 (>= 1.2.1)
|
||||||
|
swd (>= 1.0.0)
|
||||||
|
tzinfo
|
||||||
|
validate_email
|
||||||
|
validate_url
|
||||||
|
webfinger (>= 1.0.1)
|
||||||
orm_adapter (0.5.0)
|
orm_adapter (0.5.0)
|
||||||
parser (2.2.3.0)
|
parser (2.2.3.0)
|
||||||
ast (>= 1.1, < 3.0)
|
ast (>= 1.1, < 3.0)
|
||||||
|
|
@ -545,6 +574,12 @@ GEM
|
||||||
activesupport
|
activesupport
|
||||||
rack-mobile-detect (0.4.0)
|
rack-mobile-detect (0.4.0)
|
||||||
rack
|
rack
|
||||||
|
rack-oauth2 (1.2.1)
|
||||||
|
activesupport (>= 2.3)
|
||||||
|
attr_required (>= 0.0.5)
|
||||||
|
httpclient (>= 2.4)
|
||||||
|
multi_json (>= 1.3.6)
|
||||||
|
rack (>= 1.1)
|
||||||
rack-piwik (0.3.0)
|
rack-piwik (0.3.0)
|
||||||
rack-pjax (0.8.0)
|
rack-pjax (0.8.0)
|
||||||
nokogiri (~> 1.5)
|
nokogiri (~> 1.5)
|
||||||
|
|
@ -708,6 +743,7 @@ GEM
|
||||||
scss_lint (0.42.2)
|
scss_lint (0.42.2)
|
||||||
rainbow (~> 2.0)
|
rainbow (~> 2.0)
|
||||||
sass (~> 3.4.15)
|
sass (~> 3.4.15)
|
||||||
|
securecompare (1.0.0)
|
||||||
selenium-webdriver (2.47.1)
|
selenium-webdriver (2.47.1)
|
||||||
childprocess (~> 0.5)
|
childprocess (~> 0.5)
|
||||||
multi_json (~> 1.0)
|
multi_json (~> 1.0)
|
||||||
|
|
@ -757,6 +793,12 @@ GEM
|
||||||
activesupport (>= 3.0)
|
activesupport (>= 3.0)
|
||||||
sprockets (>= 2.8, < 4.0)
|
sprockets (>= 2.8, < 4.0)
|
||||||
state_machine (1.2.0)
|
state_machine (1.2.0)
|
||||||
|
swd (1.0.0)
|
||||||
|
activesupport (>= 3)
|
||||||
|
attr_required (>= 0.0.5)
|
||||||
|
httpclient (>= 2.4)
|
||||||
|
i18n
|
||||||
|
json (>= 1.4.3)
|
||||||
sysexits (1.2.0)
|
sysexits (1.2.0)
|
||||||
systemu (2.6.5)
|
systemu (2.6.5)
|
||||||
terminal-table (1.5.2)
|
terminal-table (1.5.2)
|
||||||
|
|
@ -797,11 +839,22 @@ GEM
|
||||||
kgio (~> 2.6)
|
kgio (~> 2.6)
|
||||||
rack
|
rack
|
||||||
raindrops (~> 0.7)
|
raindrops (~> 0.7)
|
||||||
|
url_safe_base64 (0.2.2)
|
||||||
uuid (2.3.8)
|
uuid (2.3.8)
|
||||||
macaddr (~> 1.0)
|
macaddr (~> 1.0)
|
||||||
valid (1.1.0)
|
valid (1.1.0)
|
||||||
|
validate_email (0.1.6)
|
||||||
|
activemodel (>= 3.0)
|
||||||
|
mail (>= 2.2.5)
|
||||||
|
validate_url (1.0.2)
|
||||||
|
activemodel (>= 3.0.0)
|
||||||
|
addressable
|
||||||
warden (1.2.4)
|
warden (1.2.4)
|
||||||
rack (>= 1.0)
|
rack (>= 1.0)
|
||||||
|
webfinger (1.0.1)
|
||||||
|
activesupport
|
||||||
|
httpclient (>= 2.4)
|
||||||
|
multi_json
|
||||||
webmock (1.22.3)
|
webmock (1.22.3)
|
||||||
addressable (>= 2.3.6)
|
addressable (>= 2.3.6)
|
||||||
crack (>= 0.3.2)
|
crack (>= 0.3.2)
|
||||||
|
|
@ -830,6 +883,7 @@ DEPENDENCIES
|
||||||
carrierwave (= 0.10.0)
|
carrierwave (= 0.10.0)
|
||||||
compass-rails (= 2.0.5)
|
compass-rails (= 2.0.5)
|
||||||
configurate (= 0.3.1)
|
configurate (= 0.3.1)
|
||||||
|
cucumber-api-steps (= 0.13)
|
||||||
cucumber-rails (= 1.4.2)
|
cucumber-rails (= 1.4.2)
|
||||||
database_cleaner (= 1.5.1)
|
database_cleaner (= 1.5.1)
|
||||||
devise (= 3.5.3)
|
devise (= 3.5.3)
|
||||||
|
|
@ -867,6 +921,7 @@ DEPENDENCIES
|
||||||
jshintrb (= 0.3.0)
|
jshintrb (= 0.3.0)
|
||||||
json (= 1.8.3)
|
json (= 1.8.3)
|
||||||
json-schema (= 2.5.2)
|
json-schema (= 2.5.2)
|
||||||
|
json_spec (= 1.1.4)
|
||||||
leaflet-rails (= 0.7.4)
|
leaflet-rails (= 0.7.4)
|
||||||
logging-rails (= 0.5.0)
|
logging-rails (= 0.5.0)
|
||||||
markerb (= 1.1.0)
|
markerb (= 1.1.0)
|
||||||
|
|
@ -882,6 +937,7 @@ DEPENDENCIES
|
||||||
omniauth-twitter (= 1.2.1)
|
omniauth-twitter (= 1.2.1)
|
||||||
omniauth-wordpress (= 0.2.2)
|
omniauth-wordpress (= 0.2.2)
|
||||||
open_graph_reader (= 0.6.1)
|
open_graph_reader (= 0.6.1)
|
||||||
|
openid_connect
|
||||||
pg (= 0.18.4)
|
pg (= 0.18.4)
|
||||||
pronto (= 0.5.3)
|
pronto (= 0.5.3)
|
||||||
pronto-haml (= 0.5.0)
|
pronto-haml (= 0.5.0)
|
||||||
|
|
|
||||||
|
|
@ -3,9 +3,17 @@
|
||||||
# the COPYRIGHT file.
|
# the COPYRIGHT file.
|
||||||
|
|
||||||
class UsersController < ApplicationController
|
class UsersController < ApplicationController
|
||||||
before_action :authenticate_user!, :except => [:new, :create, :public, :user_photo]
|
include Openid::Authentication
|
||||||
|
|
||||||
|
before_action :authenticate_user!, except: [:new, :create, :public, :user_photo]
|
||||||
|
before_filter :require_access_token, only: [:show]
|
||||||
respond_to :html
|
respond_to :html
|
||||||
|
|
||||||
|
# TODO: Adjust so that it sends back only required elements, e.g, should not send hashed password (serialized_private_key) back
|
||||||
|
def show
|
||||||
|
render json: current_user
|
||||||
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@aspect = :user_edit
|
@aspect = :user_edit
|
||||||
@user = current_user
|
@user = current_user
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,8 @@ class Token < ActiveRecord::Base
|
||||||
|
|
||||||
validates :token, presence: true, uniqueness: true
|
validates :token, presence: true, uniqueness: true
|
||||||
|
|
||||||
|
scope :valid, ->(time) { where("expires_at >= ?", time) }
|
||||||
|
|
||||||
def setup
|
def setup
|
||||||
self.token = SecureRandom.hex(32)
|
self.token = SecureRandom.hex(32)
|
||||||
self.expires_at = 24.hours.from_now
|
self.expires_at = 24.hours.from_now
|
||||||
|
|
@ -16,4 +18,8 @@ class Token < ActiveRecord::Base
|
||||||
expires_in: (expires_at - Time.now.utc).to_i
|
expires_in: (expires_at - Time.now.utc).to_i
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessible?(_scopes_or_claims_ = nil)
|
||||||
|
true # TODO: For now don't support scopes
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ module Diaspora
|
||||||
# Custom directories with classes and modules you want to be autoloadable.
|
# Custom directories with classes and modules you want to be autoloadable.
|
||||||
config.autoload_paths += %W{#{config.root}/app}
|
config.autoload_paths += %W{#{config.root}/app}
|
||||||
config.autoload_once_paths += %W{#{config.root}/lib}
|
config.autoload_once_paths += %W{#{config.root}/lib}
|
||||||
config.autoload_paths += %W{#{config.root}/lib/openid_connect}
|
config.autoload_paths += %W{#{config.root}/lib/openid}
|
||||||
|
|
||||||
# Only load the plugins named here, in the order given (default is alphabetical).
|
# Only load the plugins named here, in the order given (default is alphabetical).
|
||||||
# :all can be used as a placeholder for all plugins not explicitly named.
|
# :all can be used as a placeholder for all plugins not explicitly named.
|
||||||
|
|
@ -108,5 +108,9 @@ module Diaspora
|
||||||
host: AppConfig.pod_uri.authority
|
host: AppConfig.pod_uri.authority
|
||||||
}
|
}
|
||||||
config.action_mailer.asset_host = AppConfig.pod_uri.to_s
|
config.action_mailer.asset_host = AppConfig.pod_uri.to_s
|
||||||
|
|
||||||
|
config.middleware.use Rack::OAuth2::Server::Resource::Bearer, 'OpenID Connect' do |req|
|
||||||
|
Token.valid(Time.now.utc).find_by(token: req.access_token) || req.invalid_token!
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -246,7 +246,7 @@ Diaspora::Application.routes.draw do
|
||||||
resources :authorizations, only: [:new, :create]
|
resources :authorizations, only: [:new, :create]
|
||||||
match 'connect', to: 'connect#show', via: [:get, :post]
|
match 'connect', to: 'connect#show', via: [:get, :post]
|
||||||
match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post]
|
match '.well-known/:id', to: 'discovery#show' , :via => [:get, :post]
|
||||||
match 'user_info', to: 'user#show', :via => [:get, :post]
|
post 'access_tokens', to: proc { |env| Openid::TokenEndpoint.new.call(env) }
|
||||||
post 'access_tokens', to: proc { |env| TokenEndpoint.new.call(env) }
|
match 'user_info', to: 'users#show', :via => [:get, :post]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
class CreateTokens < ActiveRecord::Migration
|
class CreateTokens < ActiveRecord::Migration
|
||||||
def change
|
def self.up
|
||||||
create_table :tokens do |t|
|
create_table :tokens do |t|
|
||||||
t.belongs_to :o_auth_application
|
t.belongs_to :o_auth_application
|
||||||
t.string :token
|
t.string :token
|
||||||
|
|
|
||||||
31
features/desktop/protected_resource.feature
Normal file
31
features/desktop/protected_resource.feature
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
@javascript
|
||||||
|
# TODO: Add tests for expired access tokens
|
||||||
|
# TODO: Add tests to check for WWW-Authenticate response header field as according to RFC 6750
|
||||||
|
Feature: Access protected resources using bearer access token
|
||||||
|
Background:
|
||||||
|
Given a user with username "bob"
|
||||||
|
And I log in manually as "bob" with password "password"
|
||||||
|
And I send a post request to the token endpoint using "bob"'s credentials
|
||||||
|
|
||||||
|
Scenario: Valid bearer tokens sent via Authorization Request Header Field
|
||||||
|
# TODO: Add tests
|
||||||
|
|
||||||
|
Scenario: Valid bearer tokens sent via Form Encoded Parameter
|
||||||
|
# TODO: Add tests
|
||||||
|
|
||||||
|
Scenario: Valid bearer tokens sent via URI query parameter
|
||||||
|
When I use received valid bearer tokens to access user info via URI query parameter
|
||||||
|
Then I should receive "bob"'s id, username, and email
|
||||||
|
# TODO: I want to confirm that the cache-control header in the response is private as according to RFC 6750
|
||||||
|
# Unfortunately, selenium doesn't allow access to response headers
|
||||||
|
|
||||||
|
Scenario: Invalid bearer tokens sent via URI query parameter
|
||||||
|
When I use invalid bearer tokens to access user info via URI query parameter
|
||||||
|
Then I should receive an "invalid_token" error
|
||||||
|
|
||||||
|
Scenario: Valid bearer tokens sent via URI query parameter but user is logged out
|
||||||
|
When I log out manually
|
||||||
|
And I use received valid bearer tokens to access user info via URI query parameter
|
||||||
|
Then I should see "Sign in" in the content
|
||||||
|
When I log in manually as "bob" with password "password"
|
||||||
|
Then I should receive "bob"'s id, username, and email
|
||||||
37
features/step_definitions/openid_steps.rb
Normal file
37
features/step_definitions/openid_steps.rb
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Password has been hard coded as all test accounts seem to have a password of "password"
|
||||||
|
Given /^I send a post request to the token endpoint using "([^\"]*)"'s credentials$/ do |username|
|
||||||
|
user = User.find_by(username: username)
|
||||||
|
tokenEndpointURL = "/openid/access_tokens"
|
||||||
|
tokenEndpointURLQuery = "?grant_type=password&username=" +
|
||||||
|
user.username +
|
||||||
|
"&password=password&client_id=4&client_secret=azerty"
|
||||||
|
post tokenEndpointURL + tokenEndpointURLQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I use received valid bearer tokens to access user info via URI query parameter$/ do
|
||||||
|
accessTokenJson = JSON.parse(last_response.body)
|
||||||
|
userInfoEndPointURL = "/openid/user_info/"
|
||||||
|
userInfoEndPointURLQuery = "?access_token=" + accessTokenJson["access_token"]
|
||||||
|
visit userInfoEndPointURL + userInfoEndPointURLQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
When /^I use invalid bearer tokens to access user info via URI query parameter$/ do
|
||||||
|
userInfoEndPointURL = "/openid/user_info/"
|
||||||
|
userInfoEndPointURLQuery = "?access_token=" + SecureRandom.hex(32)
|
||||||
|
visit userInfoEndPointURL + userInfoEndPointURLQuery
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username|
|
||||||
|
user = User.find_by_username(username)
|
||||||
|
expect(page).to have_content(user.username)
|
||||||
|
expect(page).to have_content(user.language)
|
||||||
|
expect(page).to have_content(user.email)
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should receive an "([^\"]*)" error$/ do |error_message|
|
||||||
|
expect(page).to have_content(error_message)
|
||||||
|
end
|
||||||
|
|
||||||
|
Then /^I should see "([^\"]*)" in the content$/ do |content|
|
||||||
|
expect(page).to have_content(content)
|
||||||
|
end
|
||||||
|
|
@ -12,6 +12,9 @@ require "capybara/cucumber"
|
||||||
require "capybara/session"
|
require "capybara/session"
|
||||||
require "selenium/webdriver"
|
require "selenium/webdriver"
|
||||||
|
|
||||||
|
require "cucumber/api_steps"
|
||||||
|
require "json_spec/cucumber"
|
||||||
|
|
||||||
# Ensure we know the appservers port
|
# Ensure we know the appservers port
|
||||||
Capybara.server_port = AppConfig.pod_uri.port
|
Capybara.server_port = AppConfig.pod_uri.port
|
||||||
Rails.application.routes.default_url_options[:host] = AppConfig.pod_uri.host
|
Rails.application.routes.default_url_options[:host] = AppConfig.pod_uri.host
|
||||||
|
|
|
||||||
30
lib/openid/authentication.rb
Normal file
30
lib/openid/authentication.rb
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
module Openid
|
||||||
|
module Authentication
|
||||||
|
|
||||||
|
def self.included(klass)
|
||||||
|
klass.send :include, Authentication::Helper
|
||||||
|
end
|
||||||
|
|
||||||
|
module Helper
|
||||||
|
def current_token
|
||||||
|
@current_token
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def require_access_token
|
||||||
|
@current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
|
||||||
|
unless @current_token
|
||||||
|
raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user")
|
||||||
|
end
|
||||||
|
# TODO: This block is useless until we actually start checking for scopes
|
||||||
|
unless @current_token.try(:accessible?, required_scopes)
|
||||||
|
raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
# Scopes should be implemented here
|
||||||
|
def required_scopes
|
||||||
|
nil # as default
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
12
lib/openid/authorization_endpoint.rb
Normal file
12
lib/openid/authorization_endpoint.rb
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
module Openid
|
||||||
|
class AuthorizationEndpoint
|
||||||
|
attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object
|
||||||
|
delegate :call, to: :app
|
||||||
|
|
||||||
|
def initialize(allow_approval = false, approved = false)
|
||||||
|
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
||||||
|
req.unsupported_response_type! # TODO: not supported yet
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
35
lib/openid/token_endpoint.rb
Normal file
35
lib/openid/token_endpoint.rb
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
module Openid
|
||||||
|
class TokenEndpoint
|
||||||
|
attr_accessor :app
|
||||||
|
delegate :call, to: :app
|
||||||
|
|
||||||
|
def initialize
|
||||||
|
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
||||||
|
case req.grant_type
|
||||||
|
when :password
|
||||||
|
o_auth_app = retrieveOrCreateNewClientApplication(req)
|
||||||
|
user = User.find_for_database_authentication(username: req.username)
|
||||||
|
if o_auth_app && user && user.valid_password?(req.password)
|
||||||
|
res.access_token = o_auth_app.tokens.create!.bearer_token
|
||||||
|
else
|
||||||
|
req.invalid_grant!
|
||||||
|
end
|
||||||
|
else
|
||||||
|
res.unsupported_grant_type!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieveOrCreateNewClientApplication(req)
|
||||||
|
retrieveClient(req) || createClient(req)
|
||||||
|
end
|
||||||
|
|
||||||
|
def retrieveClient(req)
|
||||||
|
OAuthApplication.find_by_client_id req.client_id
|
||||||
|
end
|
||||||
|
|
||||||
|
def createClient(req)
|
||||||
|
OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
class AuthorizationEndpoint
|
|
||||||
attr_accessor :app, :account, :client, :redirect_uri, :response_type, :scopes, :_request_, :request_uri, :request_object
|
|
||||||
delegate :call, to: :app
|
|
||||||
|
|
||||||
def initialize(allow_approval = false, approved = false)
|
|
||||||
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
|
|
||||||
req.unsupported_response_type! # Not supported yet
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,22 +0,0 @@
|
||||||
class TokenEndpoint
|
|
||||||
attr_accessor :app
|
|
||||||
delegate :call, to: :app
|
|
||||||
|
|
||||||
def initialize
|
|
||||||
@app = Rack::OAuth2::Server::Token.new do |req, res|
|
|
||||||
case req.grant_type
|
|
||||||
when :password
|
|
||||||
# If the grant type is password, the application does not have to be known
|
|
||||||
# If it does not exist, insert into DB
|
|
||||||
user = User.find_for_database_authentication(username: req.username)
|
|
||||||
o_auth_app = OAuthApplication.find_by_client_id req.client_id
|
|
||||||
o_auth_app ||= OAuthApplication.create!(client_id: req.client_id, client_secret: req.client_secret)
|
|
||||||
if user.valid_password? req.password
|
|
||||||
res.access_token = o_auth_app.tokens.create!.bearer_token
|
|
||||||
end
|
|
||||||
else
|
|
||||||
req.unsupported_grant_type!
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
64
spec/integration/openid/token_endpoint_spec.rb
Normal file
64
spec/integration/openid/token_endpoint_spec.rb
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
require 'spec_helper'
|
||||||
|
|
||||||
|
describe "Token Endpoint", type: :request do
|
||||||
|
describe "password grant type" do
|
||||||
|
context "when the username field is missing" do
|
||||||
|
it "should return an invalid request error" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include("'username' required")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when the password field is missing" do
|
||||||
|
it "should return an invalid request error" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&username=bob\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include("'password' required")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when the username does not match an existing user" do
|
||||||
|
it "should return an invalid request error" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include("invalid_grant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when the password is invalid" do
|
||||||
|
it "should return an invalid request error" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&username=mewasdfrandom\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include("invalid_grant")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when there are duplicate fields" do
|
||||||
|
it "should return an invalid request error" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin6\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include("invalid_grant")
|
||||||
|
# TODO: Apparently Nov's implementation lets this one pass; however, according to the OIDC spec, we are supposed to reject duplicate fields. Is this a security issue?
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when the client is unauthorized" do
|
||||||
|
# TODO: If we support password grant, we should prevent access from unauthorized client applications
|
||||||
|
it "should return an error" do
|
||||||
|
fail
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when many unauthorized requests are made" do
|
||||||
|
# TODO: If we support password grant, we should support a way to prevent brute force attacks (using rate-limitation or generating alerts) as specified by RFC 6749 4.3.2 Access Token Request
|
||||||
|
it "should generate an alert" do
|
||||||
|
fail
|
||||||
|
end
|
||||||
|
end
|
||||||
|
context "when the request is valid" do
|
||||||
|
it "should return an access token" do
|
||||||
|
post "/openid/access_tokens?grant_type=password\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
json = JSON.parse(response.body)
|
||||||
|
expect(json["access_token"].length).to eq(64)
|
||||||
|
expect(json["token_type"]).to eq("bearer")
|
||||||
|
expect(json.keys).to include("expires_in")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
describe "unsupported grant type" do
|
||||||
|
it "should return an unsupported grant type error" do
|
||||||
|
post "/openid/access_tokens?grant_type=me\&username=bob\&password=bluepin7\&client_id=4\&client_secret=azerty"
|
||||||
|
expect(response.body).to include "unsupported_grant_type"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
require 'rspec'
|
|
||||||
|
|
||||||
describe TokenEndpoint do
|
|
||||||
|
|
||||||
it "shoud generate a token" do
|
|
||||||
|
|
||||||
end
|
|
||||||
end
|
|
||||||
Loading…
Reference in a new issue