Add support for scopes
Remove scopes from tokens Squashed commits: [83db38f] Add redirect uris to supported metadata
This commit is contained in:
parent
de4f68c289
commit
3cbe75469b
22 changed files with 165 additions and 171 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
class Api::V0::BaseController < ApplicationController
|
class Api::V0::BaseController < ApplicationController
|
||||||
include OpenidConnect::ProtectedResourceEndpoint
|
include OpenidConnect::ProtectedResourceEndpoint
|
||||||
|
|
||||||
before_filter :require_access_token
|
def user
|
||||||
|
current_token ? current_token.authorization.user : nil
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
class Api::V0::UsersController < Api::V0::BaseController
|
class Api::V0::UsersController < Api::V0::BaseController
|
||||||
|
before_filter do
|
||||||
|
require_access_token OpenidConnect::Scope.find_by(name: "read")
|
||||||
|
end
|
||||||
|
|
||||||
def show
|
def show
|
||||||
render json: user
|
render json: user
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def user
|
|
||||||
current_token.authorization.user
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController
|
||||||
session[:client_id] = @o_auth_application.client_id
|
session[:client_id] = @o_auth_application.client_id
|
||||||
session[:response_type] = @response_type
|
session[:response_type] = @response_type
|
||||||
session[:redirect_uri] = @redirect_uri
|
session[:redirect_uri] = @redirect_uri
|
||||||
session[:scopes] = @scopes.map(&:name)
|
session[:scopes] = @scopes.map(&:name).join(" ")
|
||||||
session[:request_object] = @request_object
|
session[:request_object] = @request_object
|
||||||
session[:nonce] = params[:nonce]
|
session[:nonce] = params[:nonce]
|
||||||
end
|
end
|
||||||
|
|
@ -84,7 +84,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController
|
||||||
req.update_param("response_type", session[:response_type].respond_to?(:map) ?
|
req.update_param("response_type", session[:response_type].respond_to?(:map) ?
|
||||||
session[:response_type].map(&:to_s).join(" ") :
|
session[:response_type].map(&:to_s).join(" ") :
|
||||||
session[:response_type])
|
session[:response_type])
|
||||||
req.update_param("scopes", session[:scopes])
|
req.update_param("scope", session[:scopes])
|
||||||
req.update_param("request_object", session[:request_object])
|
req.update_param("request_object", session[:request_object])
|
||||||
req.update_param("nonce", session[:nonce])
|
req.update_param("nonce", session[:nonce])
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ class OpenidConnect::Authorization < ActiveRecord::Base
|
||||||
validates :o_auth_application, presence: true
|
validates :o_auth_application, presence: true
|
||||||
validates :user, uniqueness: {scope: :o_auth_application}
|
validates :user, uniqueness: {scope: :o_auth_application}
|
||||||
|
|
||||||
|
has_many :authorization_scopes
|
||||||
has_many :scopes, through: :authorization_scopes
|
has_many :scopes, through: :authorization_scopes
|
||||||
has_many :o_auth_access_tokens, dependent: :destroy
|
has_many :o_auth_access_tokens, dependent: :destroy
|
||||||
has_many :id_tokens, dependent: :destroy
|
has_many :id_tokens, dependent: :destroy
|
||||||
|
|
@ -16,6 +17,12 @@ class OpenidConnect::Authorization < ActiveRecord::Base
|
||||||
self.refresh_token = SecureRandom.hex(32)
|
self.refresh_token = SecureRandom.hex(32)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def accessible?(required_scopes=nil)
|
||||||
|
Array(required_scopes).all? do |required_scope|
|
||||||
|
scopes.include? required_scope
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def create_access_token
|
def create_access_token
|
||||||
o_auth_access_tokens.create!.bearer_token
|
o_auth_access_tokens.create!.bearer_token
|
||||||
# TODO: Add support for request object
|
# TODO: Add support for request object
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
class OpenidConnect::OAuthAccessToken < ActiveRecord::Base
|
class OpenidConnect::OAuthAccessToken < ActiveRecord::Base
|
||||||
belongs_to :authorization
|
belongs_to :authorization
|
||||||
has_many :scopes, through: :scope_tokens
|
|
||||||
|
|
||||||
before_validation :setup, on: :create
|
before_validation :setup, on: :create
|
||||||
|
|
||||||
|
|
@ -20,8 +19,4 @@ class OpenidConnect::OAuthAccessToken < 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
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base
|
||||||
|
|
||||||
def supported_metadata
|
def supported_metadata
|
||||||
%i(client_name response_types grant_types application_type
|
%i(client_name response_types grant_types application_type
|
||||||
contacts logo_uri client_uri policy_uri tos_uri)
|
contacts logo_uri client_uri policy_uri tos_uri redirect_uris)
|
||||||
end
|
end
|
||||||
|
|
||||||
def registrar_attributes(registrar)
|
def registrar_attributes(registrar)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
class OpenidConnect::Scope < ActiveRecord::Base
|
class OpenidConnect::Scope < ActiveRecord::Base
|
||||||
has_many :o_auth_access_token, through: :scope_tokens
|
|
||||||
has_many :authorizations, through: :authorization_scopes
|
has_many :authorizations, through: :authorization_scopes
|
||||||
|
|
||||||
validates :name, presence: true, uniqueness: true
|
validates :name, presence: true, uniqueness: true
|
||||||
|
|
||||||
# TODO: Incomplete class
|
# TODO: Add constants so scopes can be referenced as OpenidConnect::Scope::Read
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
class OpenidConnect::ScopeToken < ActiveRecord::Base
|
|
||||||
belongs_to :scope
|
|
||||||
belongs_to :o_auth_access_token
|
|
||||||
|
|
||||||
validates :scope, presence: true
|
|
||||||
validates :token, presence: true
|
|
||||||
end
|
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
class CreateAuthorizationScopesJoinTable < ActiveRecord::Migration
|
||||||
|
def change
|
||||||
|
create_table :authorization_scopes, id: false do |t|
|
||||||
|
t.belongs_to :authorization, index: true
|
||||||
|
t.belongs_to :scope, index: true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
class CreateAuthorizationsScopesJoinTable < ActiveRecord::Migration
|
|
||||||
def change
|
|
||||||
create_table :authorizations_scopes, id: false do |t|
|
|
||||||
t.belongs_to :authorization, index: true
|
|
||||||
t.belongs_to :scope, index: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
class CreateScopesTokensJoinTable < ActiveRecord::Migration
|
|
||||||
def change
|
|
||||||
create_table :scopes_tokens, id: false do |t|
|
|
||||||
t.belongs_to :scope, index: true
|
|
||||||
t.belongs_to :o_auth_access_token, index: true
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
24
db/schema.rb
24
db/schema.rb
|
|
@ -55,6 +55,14 @@ ActiveRecord::Schema.define(version: 20150724152052) do
|
||||||
add_index "aspects", ["user_id", "contacts_visible"], name: "index_aspects_on_user_id_and_contacts_visible", using: :btree
|
add_index "aspects", ["user_id", "contacts_visible"], name: "index_aspects_on_user_id_and_contacts_visible", using: :btree
|
||||||
add_index "aspects", ["user_id"], name: "index_aspects_on_user_id", using: :btree
|
add_index "aspects", ["user_id"], name: "index_aspects_on_user_id", using: :btree
|
||||||
|
|
||||||
|
create_table "authorization_scopes", id: false, force: :cascade do |t|
|
||||||
|
t.integer "authorization_id", limit: 4
|
||||||
|
t.integer "scope_id", limit: 4
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index "authorization_scopes", ["authorization_id"], name: "index_authorization_scopes_on_authorization_id", using: :btree
|
||||||
|
add_index "authorization_scopes", ["scope_id"], name: "index_authorization_scopes_on_scope_id", using: :btree
|
||||||
|
|
||||||
create_table "authorizations", force: :cascade do |t|
|
create_table "authorizations", force: :cascade do |t|
|
||||||
t.integer "user_id", limit: 4
|
t.integer "user_id", limit: 4
|
||||||
t.integer "o_auth_application_id", limit: 4
|
t.integer "o_auth_application_id", limit: 4
|
||||||
|
|
@ -66,14 +74,6 @@ ActiveRecord::Schema.define(version: 20150724152052) do
|
||||||
add_index "authorizations", ["o_auth_application_id"], name: "index_authorizations_on_o_auth_application_id", using: :btree
|
add_index "authorizations", ["o_auth_application_id"], name: "index_authorizations_on_o_auth_application_id", using: :btree
|
||||||
add_index "authorizations", ["user_id"], name: "index_authorizations_on_user_id", using: :btree
|
add_index "authorizations", ["user_id"], name: "index_authorizations_on_user_id", using: :btree
|
||||||
|
|
||||||
create_table "authorizations_scopes", id: false, force: :cascade do |t|
|
|
||||||
t.integer "authorization_id", limit: 4
|
|
||||||
t.integer "scope_id", limit: 4
|
|
||||||
end
|
|
||||||
|
|
||||||
add_index "authorizations_scopes", ["authorization_id"], name: "index_authorizations_scopes_on_authorization_id", using: :btree
|
|
||||||
add_index "authorizations_scopes", ["scope_id"], name: "index_authorizations_scopes_on_scope_id", using: :btree
|
|
||||||
|
|
||||||
create_table "blocks", force: :cascade do |t|
|
create_table "blocks", force: :cascade do |t|
|
||||||
t.integer "user_id", limit: 4
|
t.integer "user_id", limit: 4
|
||||||
t.integer "person_id", limit: 4
|
t.integer "person_id", limit: 4
|
||||||
|
|
@ -523,14 +523,6 @@ ActiveRecord::Schema.define(version: 20150724152052) do
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
end
|
end
|
||||||
|
|
||||||
create_table "scopes_tokens", id: false, force: :cascade do |t|
|
|
||||||
t.integer "scope_id", limit: 4
|
|
||||||
t.integer "o_auth_access_token_id", limit: 4
|
|
||||||
end
|
|
||||||
|
|
||||||
add_index "scopes_tokens", ["o_auth_access_token_id"], name: "index_scopes_tokens_on_o_auth_access_token_id", using: :btree
|
|
||||||
add_index "scopes_tokens", ["scope_id"], name: "index_scopes_tokens_on_scope_id", using: :btree
|
|
||||||
|
|
||||||
create_table "services", force: :cascade do |t|
|
create_table "services", force: :cascade do |t|
|
||||||
t.string "type", limit: 127, null: false
|
t.string "type", limit: 127, null: false
|
||||||
t.integer "user_id", limit: 4, null: false
|
t.integer "user_id", limit: 4, null: false
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
Feature: Access protected resources using password flow
|
Feature: Access protected resources using password flow
|
||||||
Background:
|
Background:
|
||||||
Given a user with username "kent"
|
Given a user with username "kent"
|
||||||
|
And all scopes exist
|
||||||
|
|
||||||
Scenario: Invalid credentials to token endpoint
|
Scenario: Invalid credentials to token endpoint
|
||||||
When I register a new client
|
When I register a new client
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
Feature: Access protected resources using implicit flow
|
Feature: Access protected resources using implicit flow
|
||||||
Background:
|
Background:
|
||||||
Given a user with username "kent"
|
Given a user with username "kent"
|
||||||
And the OpenID scope exists
|
And all scopes exist
|
||||||
|
|
||||||
Scenario: Invalid client id to auth endpoint
|
Scenario: Invalid client id to auth endpoint
|
||||||
When I register a new client
|
When I register a new client
|
||||||
|
|
|
||||||
|
|
@ -1,24 +1,18 @@
|
||||||
o_auth_query_params = %i(
|
o_auth_query_params = %i(
|
||||||
redirect_uri=http://localhost:3000
|
redirect_uri=http://localhost:3000
|
||||||
response_type=id_token token
|
response_type=id_token%20token
|
||||||
scope=openid
|
scope=openid%20read
|
||||||
nonce=hello
|
nonce=hello
|
||||||
state=hi
|
state=hi
|
||||||
).join("&")
|
).join("&")
|
||||||
|
|
||||||
Given(/^the OpenID scope exists$/) do
|
|
||||||
OpenidConnect::Scope.create(name: "openid")
|
|
||||||
end
|
|
||||||
|
|
||||||
Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do
|
Given /^I send a post request from that client to the implicit flow authorization endpoint$/ do
|
||||||
client_json = JSON.parse(last_response.body)
|
client_json = JSON.parse(last_response.body)
|
||||||
auth_endpoint_url = "/openid_connect/authorizations/new"
|
visit new_openid_connect_authorization_path + "?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}"
|
||||||
visit "#{auth_endpoint_url}?client_id=#{client_json["o_auth_application"]["client_id"]}&#{o_auth_query_params}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do
|
Given /^I send a post request from that client to the implicit flow authorization endpoint using a invalid client id/ do
|
||||||
auth_endpoint_url = "/openid_connect/authorizations/new"
|
visit new_openid_connect_authorization_path + "?client_id=randomid&#{o_auth_query_params}"
|
||||||
visit "#{auth_endpoint_url}?client_id=randomid&#{o_auth_query_params}"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
When /^I give my consent and authorize the client$/ do
|
When /^I give my consent and authorize the client$/ do
|
||||||
|
|
@ -38,8 +32,7 @@ end
|
||||||
|
|
||||||
When /^I parse the bearer tokens and use it to access user info$/ do
|
When /^I parse the bearer tokens and use it to access user info$/ do
|
||||||
access_token = current_url[/(?<=access_token=)[^&]+/]
|
access_token = current_url[/(?<=access_token=)[^&]+/]
|
||||||
user_info_endpoint_url = "/api/v0/user/"
|
get api_v0_user_path, access_token: access_token
|
||||||
get user_info_endpoint_url, access_token: access_token
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Then /^I should see an "([^\"]*)" error$/ do |error_message|
|
Then /^I should see an "([^\"]*)" error$/ do |error_message|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,37 @@
|
||||||
|
Given(/^all scopes exist$/) do
|
||||||
|
OpenidConnect::Scope.find_or_create_by(name: "openid")
|
||||||
|
OpenidConnect::Scope.find_or_create_by(name: "read")
|
||||||
|
end
|
||||||
|
|
||||||
When /^I register a new client$/ do
|
When /^I register a new client$/ do
|
||||||
client_registration_url = "/openid_connect/clients"
|
post openid_connect_clients_path, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client"
|
||||||
post client_registration_url, redirect_uris: ["http://localhost:3000"], client_name: "diaspora client"
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username|
|
Given /^I send a post request from that client to the password flow token endpoint using "([^\"]*)"'s credentials$/ do |username|
|
||||||
client_json = JSON.parse(last_response.body)
|
client_json = JSON.parse(last_response.body)
|
||||||
user = User.find_by(username: username)
|
user = User.find_by(username: username)
|
||||||
token_endpoint_url = "/openid_connect/access_tokens"
|
post openid_connect_access_tokens_path, grant_type: "password", username: user.username,
|
||||||
post token_endpoint_url, grant_type: "password", username: user.username,
|
|
||||||
password: "password", # Password has been hard coded as all test accounts seem to have a password of "password"
|
password: "password", # Password has been hard coded as all test accounts seem to have a password of "password"
|
||||||
client_id: client_json["o_auth_application"]["client_id"],
|
client_id: client_json["o_auth_application"]["client_id"],
|
||||||
client_secret: client_json["o_auth_application"]["client_secret"]
|
client_secret: client_json["o_auth_application"]["client_secret"],
|
||||||
|
scope: "read"
|
||||||
end
|
end
|
||||||
|
|
||||||
Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do
|
Given /^I send a post request from that client to the password flow token endpoint using invalid credentials$/ do
|
||||||
client_json = JSON.parse(last_response.body)
|
client_json = JSON.parse(last_response.body)
|
||||||
token_endpoint_url = "/openid_connect/access_tokens"
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "wrongpassword",
|
||||||
post token_endpoint_url, grant_type: "password", username: "bob", password: "wrongpassword",
|
|
||||||
client_id: client_json["o_auth_application"]["client_id"],
|
client_id: client_json["o_auth_application"]["client_id"],
|
||||||
client_secret: client_json["o_auth_application"]["client_secret"]
|
client_secret: client_json["o_auth_application"]["client_secret"],
|
||||||
|
scope: "read"
|
||||||
end
|
end
|
||||||
|
|
||||||
When /^I use received valid bearer tokens to access user info$/ do
|
When /^I use received valid bearer tokens to access user info$/ do
|
||||||
access_token_json = JSON.parse(last_response.body)
|
access_token_json = JSON.parse(last_response.body)
|
||||||
user_info_endpoint_url = "/api/v0/user/"
|
get api_v0_user_path, access_token: access_token_json["access_token"]
|
||||||
get user_info_endpoint_url, access_token: access_token_json["access_token"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
When /^I use invalid bearer tokens to access user info$/ do
|
When /^I use invalid bearer tokens to access user info$/ do
|
||||||
user_info_endpoint_url = "/api/v0/user/"
|
get api_v0_user_path, access_token: SecureRandom.hex(32)
|
||||||
get user_info_endpoint_url, access_token: SecureRandom.hex(32)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username|
|
Then /^I should receive "([^\"]*)"'s id, username, and email$/ do |username|
|
||||||
|
|
|
||||||
|
|
@ -45,8 +45,8 @@ module OpenidConnect
|
||||||
end
|
end
|
||||||
|
|
||||||
def build_scopes(req)
|
def build_scopes(req)
|
||||||
@scopes = req.scope.map {|scope|
|
@scopes = req.scope.map {|scope_name|
|
||||||
OpenidConnect::Scope.where(name: scope).first.tap do |scope|
|
OpenidConnect::Scope.where(name: scope_name).first.tap do |scope|
|
||||||
req.invalid_scope! "Unknown scope: #{scope}" unless scope
|
req.invalid_scope! "Unknown scope: #{scope}" unless scope
|
||||||
end
|
end
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,6 +21,7 @@ module OpenidConnect
|
||||||
# TODO: Add support for request object and auth code
|
# TODO: Add support for request object and auth code
|
||||||
def approved!(req, res)
|
def approved!(req, res)
|
||||||
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user)
|
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user)
|
||||||
|
auth.scopes << @scopes
|
||||||
response_types = Array(req.response_type)
|
response_types = Array(req.response_type)
|
||||||
if response_types.include?(:token)
|
if response_types.include?(:token)
|
||||||
res.access_token = auth.create_access_token
|
res.access_token = auth.create_access_token
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,14 @@ module OpenidConnect
|
||||||
module ProtectedResourceEndpoint
|
module ProtectedResourceEndpoint
|
||||||
attr_reader :current_token
|
attr_reader :current_token
|
||||||
|
|
||||||
def require_access_token
|
def require_access_token(*required_scopes)
|
||||||
@current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
|
@current_token = request.env[Rack::OAuth2::Server::Resource::ACCESS_TOKEN]
|
||||||
unless @current_token
|
unless @current_token && @current_token.authorization
|
||||||
raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user")
|
raise Rack::OAuth2::Server::Resource::Bearer::Unauthorized.new("Unauthorized user")
|
||||||
end
|
end
|
||||||
# TODO: This block is useless until we actually start checking for scopes
|
unless @current_token.authorization.try(:accessible?, required_scopes)
|
||||||
unless @current_token.try(:accessible?, required_scopes)
|
|
||||||
raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope)
|
raise Rack::OAuth2::Server::Resource::Bearer::Forbidden.new(:insufficient_scope)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: Scopes should be implemented here
|
|
||||||
def required_scopes
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,13 @@ module OpenidConnect
|
||||||
user = User.find_for_database_authentication(username: req.username)
|
user = User.find_for_database_authentication(username: req.username)
|
||||||
if user
|
if user
|
||||||
if user.valid_password?(req.password)
|
if user.valid_password?(req.password)
|
||||||
|
scope_list = req.scope.map { |scope_name|
|
||||||
|
OpenidConnect::Scope.find_by(name: scope_name).tap do |scope|
|
||||||
|
req.invalid_scope! "Unknown scope: #{scope}" unless scope
|
||||||
|
end
|
||||||
|
} # TODO: Check client scope permissions
|
||||||
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user)
|
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: o_auth_app, user: user)
|
||||||
|
auth.scopes << scope_list
|
||||||
res.access_token = auth.create_access_token
|
res.access_token = auth.create_access_token
|
||||||
else
|
else
|
||||||
req.invalid_grant!
|
req.invalid_grant!
|
||||||
|
|
@ -40,6 +46,8 @@ module OpenidConnect
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_refresh_flow(req, res)
|
def handle_refresh_flow(req, res)
|
||||||
|
# Handle as if scope request was omitted even if provided.
|
||||||
|
# See https://tools.ietf.org/html/rfc6749#section-6 for handling
|
||||||
auth = OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token
|
auth = OpenidConnect::Authorization.find_by_refresh_token req.client_id, req.refresh_token
|
||||||
if auth
|
if auth
|
||||||
res.access_token = auth.create_access_token
|
res.access_token = auth.create_access_token
|
||||||
|
|
|
||||||
|
|
@ -1,75 +1,89 @@
|
||||||
require "spec_helper"
|
require "spec_helper"
|
||||||
|
|
||||||
describe OpenidConnect::ProtectedResourceEndpoint, type: :request do
|
describe OpenidConnect::ProtectedResourceEndpoint, type: :request do
|
||||||
describe "getting the user info" do
|
let!(:client) do
|
||||||
let!(:client) do
|
OpenidConnect::OAuthApplication.create!(
|
||||||
OpenidConnect::OAuthApplication.create!(
|
client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"])
|
||||||
client_name: "Diaspora Test Client", redirect_uris: ["http://localhost:3000/"])
|
end
|
||||||
end
|
let(:auth_with_read) do
|
||||||
let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) }
|
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob)
|
||||||
let!(:access_token) { auth.create_access_token.to_s }
|
auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read")]
|
||||||
let!(:invalid_token) { SecureRandom.hex(32).to_s }
|
auth
|
||||||
# TODO: Add tests for expired access tokens
|
end
|
||||||
|
let!(:access_token_with_read) { auth_with_read.create_access_token.to_s }
|
||||||
|
let(:auth_with_read_and_write) do
|
||||||
|
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob)
|
||||||
|
auth.scopes << [OpenidConnect::Scope.find_or_create_by(name: "read"), OpenidConnect::Scope.find_or_create_by(name: "write")]
|
||||||
|
auth
|
||||||
|
end
|
||||||
|
let!(:access_token_with_read_and_write) { auth_with_read_and_write.create_access_token.to_s }
|
||||||
|
let(:invalid_token) { SecureRandom.hex(32).to_s }
|
||||||
|
|
||||||
context "when access token is valid" do
|
# TODO: Add tests for expired access tokens
|
||||||
it "shows the user's username and email" do
|
|
||||||
get "/api/v0/user/", access_token: access_token
|
context "when read scope access token is provided for read required endpoint" do
|
||||||
|
describe "user info endpoint" do
|
||||||
|
before do
|
||||||
|
get api_v0_user_path, access_token: access_token_with_read
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the info" do
|
||||||
json_body = JSON.parse(response.body)
|
json_body = JSON.parse(response.body)
|
||||||
expect(json_body["username"]).to eq(bob.username)
|
expect(json_body["username"]).to eq(bob.username)
|
||||||
expect(json_body["email"]).to eq(bob.email)
|
expect(json_body["email"]).to eq(bob.email)
|
||||||
end
|
end
|
||||||
it "should include private in the cache-control header" do
|
|
||||||
get "/api/v0/user/", access_token: access_token
|
it "includes private in the cache-control header" do
|
||||||
expect(response.headers["Cache-Control"]).to include("private")
|
expect(response.headers["Cache-Control"]).to include("private")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "when no access token is provided" do
|
context "when no access token is provided" do
|
||||||
it "should respond with a 401 Unauthorized response" do
|
it "should respond with a 401 Unauthorized response" do
|
||||||
get "/api/v0/user/"
|
get api_v0_user_path
|
||||||
expect(response.status).to be(401)
|
expect(response.status).to be(401)
|
||||||
end
|
end
|
||||||
it "should have an auth-scheme value of Bearer" do
|
it "should have an auth-scheme value of Bearer" do
|
||||||
get "/api/v0/user/"
|
get api_v0_user_path
|
||||||
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when an invalid access token is provided" do
|
||||||
|
before do
|
||||||
|
get api_v0_user_path, access_token: invalid_token
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when an invalid access token is provided" do
|
it "should respond with a 401 Unauthorized response" do
|
||||||
before do
|
expect(response.status).to be(401)
|
||||||
get "/api/v0/user/", access_token: invalid_token
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should respond with a 401 Unauthorized response" do
|
|
||||||
expect(response.status).to be(401)
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should have an auth-scheme value of Bearer" do
|
|
||||||
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should contain an invalid_token error" do
|
|
||||||
expect(response.body).to include("invalid_token")
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when authorization has been destroyed" do
|
it "should have an auth-scheme value of Bearer" do
|
||||||
before do
|
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
||||||
auth.destroy
|
end
|
||||||
get "/api/v0/user/", access_token: access_token
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should respond with a 401 Unauthorized response" do
|
it "should contain an invalid_token error" do
|
||||||
expect(response.status).to be(401)
|
expect(response.body).to include("invalid_token")
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
it "should have an auth-scheme value of Bearer" do
|
context "when authorization has been destroyed" do
|
||||||
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
before do
|
||||||
end
|
auth_with_read.destroy
|
||||||
|
get api_v0_user_path, access_token: access_token_with_read
|
||||||
|
end
|
||||||
|
|
||||||
it "should contain an invalid_token error" do
|
it "should respond with a 401 Unauthorized response" do
|
||||||
expect(response.body).to include("invalid_token")
|
expect(response.status).to be(401)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
it "should have an auth-scheme value of Bearer" do
|
||||||
|
expect(response.headers["WWW-Authenticate"]).to include("Bearer")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should contain an invalid_token error" do
|
||||||
|
expect(response.body).to include("invalid_token")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,53 +5,57 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
OpenidConnect::OAuthApplication.create!(
|
OpenidConnect::OAuthApplication.create!(
|
||||||
redirect_uris: ["http://localhost"], client_name: "diaspora client")
|
redirect_uris: ["http://localhost"], client_name: "diaspora client")
|
||||||
end
|
end
|
||||||
let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) }
|
let(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: bob) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
OpenidConnect::Scope.find_or_create_by(name: "read")
|
||||||
|
end
|
||||||
|
|
||||||
describe "the password grant type" do
|
describe "the password grant type" do
|
||||||
context "when the username field is missing" do
|
context "when the username field is missing" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", password: "bluepin7",
|
post openid_connect_access_tokens_path, grant_type: "password", password: "bluepin7",
|
||||||
client_id: client.client_id, client_secret: client.client_secret
|
client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "'username' required"
|
expect(response.body).to include "'username' required"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the password field is missing" do
|
context "when the password field is missing" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob",
|
||||||
client_id: client.client_id, client_secret: client.client_secret
|
client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "'password' required"
|
expect(response.body).to include "'password' required"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the username does not match an existing user" do
|
context "when the username does not match an existing user" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "randomnoexist",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "randomnoexist",
|
||||||
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret
|
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "invalid_grant"
|
expect(response.body).to include "invalid_grant"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the password is invalid" do
|
context "when the password is invalid" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob",
|
||||||
password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret
|
password: "wrongpassword", client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "invalid_grant"
|
expect(response.body).to include "invalid_grant"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the client_secret doesn't match" do
|
context "when the client_secret doesn't match" do
|
||||||
it "should return an invalid client error" do
|
it "should return an invalid client error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob",
|
||||||
password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret"
|
password: "bluepin7", client_id: client.client_id, client_secret: "client.client_secret", scope: "read"
|
||||||
expect(response.body).to include "invalid_client"
|
expect(response.body).to include "invalid_client"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the request is valid" do
|
context "when the request is valid" do
|
||||||
it "should return an access token" do
|
it "should return an access token" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob",
|
||||||
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret
|
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
expect(json.keys).to include "expires_in"
|
expect(json.keys).to include "expires_in"
|
||||||
expect(json["access_token"].length).to eq(64)
|
expect(json["access_token"].length).to eq(64)
|
||||||
|
|
@ -61,16 +65,16 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
|
|
||||||
context "when there are duplicate fields" do
|
context "when there are duplicate fields" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob", password: "bluepin7",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob", password: "bluepin7",
|
||||||
username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret
|
username: "bob", password: "bluepin6", client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "invalid_grant"
|
expect(response.body).to include "invalid_grant"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the client is unregistered" do
|
context "when the client is unregistered" do
|
||||||
it "should return an error" do
|
it "should return an error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "password", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "password", username: "bob",
|
||||||
password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret
|
password: "bluepin7", client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "invalid_client"
|
expect(response.body).to include "invalid_client"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -80,8 +84,8 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
|
|
||||||
describe "an unsupported grant type" do
|
describe "an unsupported grant type" do
|
||||||
it "should return an unsupported grant type error" do
|
it "should return an unsupported grant type error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "noexistgrant", username: "bob",
|
post openid_connect_access_tokens_path, grant_type: "noexistgrant", username: "bob",
|
||||||
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret
|
password: "bluepin7", client_id: client.client_id, client_secret: client.client_secret, scope: "read"
|
||||||
expect(response.body).to include "unsupported_grant_type"
|
expect(response.body).to include "unsupported_grant_type"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
@ -89,7 +93,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
describe "the refresh token flow" do
|
describe "the refresh token flow" do
|
||||||
context "when the refresh token is valid" do
|
context "when the refresh token is valid" do
|
||||||
it "should return an access token" do
|
it "should return an access token" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "refresh_token",
|
post openid_connect_access_tokens_path, grant_type: "refresh_token",
|
||||||
client_id: client.client_id, client_secret: client.client_secret, refresh_token: auth.refresh_token
|
client_id: client.client_id, client_secret: client.client_secret, refresh_token: auth.refresh_token
|
||||||
json = JSON.parse(response.body)
|
json = JSON.parse(response.body)
|
||||||
expect(response.body).to include "expires_in"
|
expect(response.body).to include "expires_in"
|
||||||
|
|
@ -100,15 +104,15 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
|
|
||||||
context "when the refresh token is not valid" do
|
context "when the refresh token is not valid" do
|
||||||
it "should return an invalid grant error" do
|
it "should return an invalid grant error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "refresh_token",
|
post openid_connect_access_tokens_path, grant_type: "refresh_token",
|
||||||
client_id: client.client_id, client_secret: client.client_secret, refresh_token: " "
|
client_id: client.client_id, client_secret: client.client_secret, refresh_token: "123456"
|
||||||
expect(response.body).to include "invalid_grant"
|
expect(response.body).to include "invalid_grant"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when the client is unregistered" do
|
context "when the client is unregistered" do
|
||||||
it "should return an error" do
|
it "should return an error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token,
|
post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token,
|
||||||
client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret
|
client_id: SecureRandom.hex(16).to_s, client_secret: client.client_secret
|
||||||
expect(response.body).to include "invalid_client"
|
expect(response.body).to include "invalid_client"
|
||||||
end
|
end
|
||||||
|
|
@ -116,7 +120,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
|
|
||||||
context "when the refresh_token field is missing" do
|
context "when the refresh_token field is missing" do
|
||||||
it "should return an invalid request error" do
|
it "should return an invalid request error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "refresh_token",
|
post openid_connect_access_tokens_path, grant_type: "refresh_token",
|
||||||
client_id: client.client_id, client_secret: client.client_secret
|
client_id: client.client_id, client_secret: client.client_secret
|
||||||
expect(response.body).to include "'refresh_token' required"
|
expect(response.body).to include "'refresh_token' required"
|
||||||
end
|
end
|
||||||
|
|
@ -124,7 +128,7 @@ describe OpenidConnect::TokenEndpoint, type: :request do
|
||||||
|
|
||||||
context "when the client_secret doesn't match" do
|
context "when the client_secret doesn't match" do
|
||||||
it "should return an invalid client error" do
|
it "should return an invalid client error" do
|
||||||
post "/openid_connect/access_tokens", grant_type: "refresh_token", refresh_token: auth.refresh_token,
|
post openid_connect_access_tokens_path, grant_type: "refresh_token", refresh_token: auth.refresh_token,
|
||||||
client_id: client.client_id, client_secret: "client.client_secret"
|
client_id: client.client_id, client_secret: "client.client_secret"
|
||||||
expect(response.body).to include "invalid_client"
|
expect(response.body).to include "invalid_client"
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue