Add support for access tokens in implicit flow
Squashed commits: [7dbf618] Use Rail's find_or_create_by method
This commit is contained in:
parent
2d762da072
commit
ee9ac06e1a
6 changed files with 73 additions and 43 deletions
|
|
@ -76,7 +76,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController
|
||||||
req = Rack::Request.new(request.env)
|
req = Rack::Request.new(request.env)
|
||||||
req.update_param("client_id", session[:client_id])
|
req.update_param("client_id", session[:client_id])
|
||||||
req.update_param("redirect_uri", session[:redirect_uri])
|
req.update_param("redirect_uri", session[:redirect_uri])
|
||||||
req.update_param("response_type", session[:response_type])
|
req.update_param("response_type", session[:response_type].respond_to?(:map) ? session[:response_type].map(&:to_s).join(" ") : session[:response_type])
|
||||||
req.update_param("scopes", session[:scopes])
|
req.update_param("scopes", 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])
|
||||||
|
|
|
||||||
|
|
@ -2,12 +2,12 @@ class OpenidConnect::Authorization < ActiveRecord::Base
|
||||||
belongs_to :user
|
belongs_to :user
|
||||||
belongs_to :o_auth_application
|
belongs_to :o_auth_application
|
||||||
|
|
||||||
validates :user, presence: true, uniqueness: true
|
validates :user, presence: true
|
||||||
validates :o_auth_application, presence: true, uniqueness: true
|
validates :o_auth_application, presence: true
|
||||||
|
|
||||||
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
|
has_many :id_tokens, dependent: :destroy
|
||||||
|
|
||||||
def generate_refresh_token
|
def generate_refresh_token
|
||||||
self.refresh_token = SecureRandom.hex(32)
|
self.refresh_token = SecureRandom.hex(32)
|
||||||
|
|
@ -15,6 +15,11 @@ class OpenidConnect::Authorization < ActiveRecord::Base
|
||||||
|
|
||||||
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
|
||||||
|
end
|
||||||
|
|
||||||
|
def create_id_token(nonce)
|
||||||
|
id_tokens.create!(nonce: nonce)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_by_client_id_and_user(client_id, user)
|
def self.find_by_client_id_and_user(client_id, user)
|
||||||
|
|
@ -22,15 +27,5 @@ class OpenidConnect::Authorization < ActiveRecord::Base
|
||||||
find_by(o_auth_application: app, user: user)
|
find_by(o_auth_application: app, user: user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def self.find_by_app_and_user(app, user)
|
|
||||||
find_by(o_auth_application: app, user: user)
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: Handle creation error
|
|
||||||
def self.find_or_create(client_id, user)
|
|
||||||
app = OpenidConnect::OAuthApplication.find_by(client_id: client_id)
|
|
||||||
find_by_app_and_user(app, user) || create!(user: user, o_auth_application: app)
|
|
||||||
end
|
|
||||||
|
|
||||||
# TODO: Consider splitting into subclasses by flow type
|
# TODO: Consider splitting into subclasses by flow type
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base
|
||||||
before_validation :setup, on: :create
|
before_validation :setup, on: :create
|
||||||
|
|
||||||
validates :token, presence: true, uniqueness: true
|
validates :token, presence: true, uniqueness: true
|
||||||
validates :authorization, presence: true, uniqueness: true
|
validates :authorization, presence: true
|
||||||
|
|
||||||
scope :valid, ->(time) { where("expires_at >= ?", time) }
|
scope :valid, ->(time) { where("expires_at >= ?", time) }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def available_response_types
|
def available_response_types
|
||||||
["id_token"]
|
["id_token", "id_token token"]
|
||||||
end
|
end
|
||||||
|
|
||||||
def register!(registrar)
|
def register!(registrar)
|
||||||
|
|
|
||||||
|
|
@ -18,14 +18,17 @@ module OpenidConnect
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: Add support for request object and auth code
|
||||||
def approved!(req, res)
|
def approved!(req, res)
|
||||||
auth = OpenidConnect::Authorization.find_or_create(req.client_id, @user)
|
auth = OpenidConnect::Authorization.find_or_create_by(o_auth_application: @o_auth_application, user: @user)
|
||||||
response_types = Array(req.response_type)
|
response_types = Array(req.response_type)
|
||||||
|
if response_types.include?(:token)
|
||||||
|
res.access_token = auth.create_access_token
|
||||||
|
end
|
||||||
if response_types.include?(:id_token)
|
if response_types.include?(:id_token)
|
||||||
id_token = auth.id_tokens.create!(nonce: req.nonce)
|
id_token = auth.create_id_token(req.nonce)
|
||||||
options = %i(code access_token).map{|option| ["res.#{option}", res.respond_to?(option) ? res.option : nil]}.to_h
|
access_token_value = res.respond_to?(:access_token) ? res.access_token : nil
|
||||||
res.id_token = id_token.to_jwt(options)
|
res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value)
|
||||||
# TODO: Add support for request object
|
|
||||||
end
|
end
|
||||||
res.approve!
|
res.approve!
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
context "when already authorized" do
|
context "when already authorized" do
|
||||||
let!(:auth) { OpenidConnect::Authorization.find_or_create(client.client_id, alice) }
|
let!(:auth) { OpenidConnect::Authorization.find_or_create_by(o_auth_application: client, user: alice) }
|
||||||
|
|
||||||
context "when valid parameters are passed" do
|
context "when valid parameters are passed" do
|
||||||
before do
|
before do
|
||||||
|
|
@ -113,41 +113,73 @@ describe OpenidConnect::AuthorizationsController, type: :controller do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create" do
|
describe "#create" do
|
||||||
before do
|
|
||||||
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
|
||||||
scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3
|
|
||||||
end
|
|
||||||
|
|
||||||
context "when authorization is approved" do
|
context "when id_token token" do
|
||||||
before do
|
before do
|
||||||
post :create, approve: "true"
|
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token token",
|
||||||
|
scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return the id token in a fragment" do
|
context "when authorization is approved" do
|
||||||
expect(response.location).to have_content("id_token=")
|
before do
|
||||||
encoded_id_token = response.location[/(?<=id_token=)[^&]+/]
|
post :create, approve: "true"
|
||||||
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key
|
end
|
||||||
expect(decoded_token.nonce).to eq("4180930983")
|
|
||||||
expect(decoded_token.exp).to be > Time.now.utc.to_i
|
|
||||||
end
|
|
||||||
|
|
||||||
it "should return the passed in state" do
|
it "should return the id token in a fragment" do
|
||||||
expect(response.location).to have_content("state=4180930983")
|
encoded_id_token = response.location[/(?<=id_token=)[^&]+/]
|
||||||
|
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key
|
||||||
|
expect(decoded_token.nonce).to eq("4180930983")
|
||||||
|
expect(decoded_token.exp).to be > Time.now.utc.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return a valid access token in a fragment" do
|
||||||
|
encoded_id_token = response.location[/(?<=id_token=)[^&]+/]
|
||||||
|
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key
|
||||||
|
access_token = response.location[/(?<=access_token=)[^&]+/]
|
||||||
|
access_token_check_num = UrlSafeBase64.encode64(OpenSSL::Digest::SHA256.digest(access_token)[0, 128 / 8])
|
||||||
|
expect(decoded_token.at_hash).to eq(access_token_check_num)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
context "when authorization is denied" do
|
context "when id_token" do
|
||||||
before do
|
before do
|
||||||
post :create, approve: "false"
|
get :new, client_id: client.client_id, redirect_uri: "http://localhost:3000/", response_type: "id_token",
|
||||||
|
scope: "openid", nonce: 418_093_098_3, state: 418_093_098_3
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should return an error in the fragment" do
|
context "when authorization is approved" do
|
||||||
expect(response.location).to have_content("error=")
|
before do
|
||||||
|
post :create, approve: "true"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return the id token in a fragment" do
|
||||||
|
expect(response.location).to have_content("id_token=")
|
||||||
|
encoded_id_token = response.location[/(?<=id_token=)[^&]+/]
|
||||||
|
decoded_token = OpenIDConnect::ResponseObject::IdToken.decode encoded_id_token, OpenidConnect::IdTokenConfig.public_key
|
||||||
|
expect(decoded_token.nonce).to eq("4180930983")
|
||||||
|
expect(decoded_token.exp).to be > Time.now.utc.to_i
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return the passed in state" do
|
||||||
|
expect(response.location).to have_content("state=4180930983")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
it "should NOT contain a id token in the fragment" do
|
context "when authorization is denied" do
|
||||||
expect(response.location).to_not have_content("id_token=")
|
before do
|
||||||
|
post :create, approve: "false"
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should return an error in the fragment" do
|
||||||
|
expect(response.location).to have_content("error=")
|
||||||
|
end
|
||||||
|
|
||||||
|
it "should NOT contain a id token in the fragment" do
|
||||||
|
expect(response.location).to_not have_content("id_token=")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue