From ee9ac06e1a6c1d6cdccaee15d758e0b90e899df7 Mon Sep 17 00:00:00 2001 From: theworldbright <kent@kentshikama.com> Date: Thu, 16 Jul 2015 01:53:10 +0900 Subject: [PATCH] Add support for access tokens in implicit flow Squashed commits: [7dbf618] Use Rail's find_or_create_by method --- .../authorizations_controller.rb | 2 +- app/models/openid_connect/authorization.rb | 21 ++--- .../openid_connect/o_auth_access_token.rb | 2 +- .../openid_connect/o_auth_application.rb | 2 +- .../endpoint_confirmation_point.rb | 13 ++-- .../authorizations_controller_spec.rb | 76 +++++++++++++------ 6 files changed, 73 insertions(+), 43 deletions(-) diff --git a/app/controllers/openid_connect/authorizations_controller.rb b/app/controllers/openid_connect/authorizations_controller.rb index 3d4a55eb94..febf6247a5 100644 --- a/app/controllers/openid_connect/authorizations_controller.rb +++ b/app/controllers/openid_connect/authorizations_controller.rb @@ -76,7 +76,7 @@ class OpenidConnect::AuthorizationsController < ApplicationController req = Rack::Request.new(request.env) req.update_param("client_id", session[:client_id]) 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("request_object", session[:request_object]) req.update_param("nonce", session[:nonce]) diff --git a/app/models/openid_connect/authorization.rb b/app/models/openid_connect/authorization.rb index 6793c59ce4..c91aa2cc66 100644 --- a/app/models/openid_connect/authorization.rb +++ b/app/models/openid_connect/authorization.rb @@ -2,12 +2,12 @@ class OpenidConnect::Authorization < ActiveRecord::Base belongs_to :user belongs_to :o_auth_application - validates :user, presence: true, uniqueness: true - validates :o_auth_application, presence: true, uniqueness: true + validates :user, presence: true + validates :o_auth_application, presence: true has_many :scopes, through: :authorization_scopes has_many :o_auth_access_tokens, dependent: :destroy - has_many :id_tokens + has_many :id_tokens, dependent: :destroy def generate_refresh_token self.refresh_token = SecureRandom.hex(32) @@ -15,21 +15,16 @@ class OpenidConnect::Authorization < ActiveRecord::Base def create_access_token o_auth_access_tokens.create!.bearer_token + # TODO: Add support for request object end - def self.find_by_client_id_and_user(client_id, user) - app = OpenidConnect::OAuthApplication.find_by(client_id: client_id) - find_by(o_auth_application: app, user: user) - end - - def self.find_by_app_and_user(app, user) - find_by(o_auth_application: app, user: user) + def create_id_token(nonce) + id_tokens.create!(nonce: nonce) end - # TODO: Handle creation error - def self.find_or_create(client_id, user) + def self.find_by_client_id_and_user(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) + find_by(o_auth_application: app, user: user) end # TODO: Consider splitting into subclasses by flow type diff --git a/app/models/openid_connect/o_auth_access_token.rb b/app/models/openid_connect/o_auth_access_token.rb index de46e837f9..04f423f4de 100644 --- a/app/models/openid_connect/o_auth_access_token.rb +++ b/app/models/openid_connect/o_auth_access_token.rb @@ -5,7 +5,7 @@ class OpenidConnect::OAuthAccessToken < ActiveRecord::Base before_validation :setup, on: :create validates :token, presence: true, uniqueness: true - validates :authorization, presence: true, uniqueness: true + validates :authorization, presence: true scope :valid, ->(time) { where("expires_at >= ?", time) } diff --git a/app/models/openid_connect/o_auth_application.rb b/app/models/openid_connect/o_auth_application.rb index 2ba85ba0b4..482f0ad249 100644 --- a/app/models/openid_connect/o_auth_application.rb +++ b/app/models/openid_connect/o_auth_application.rb @@ -16,7 +16,7 @@ class OpenidConnect::OAuthApplication < ActiveRecord::Base class << self def available_response_types - ["id_token"] + ["id_token", "id_token token"] end def register!(registrar) diff --git a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb index 7b91d0123c..cdb4d94742 100644 --- a/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb +++ b/lib/openid_connect/authorization_point/endpoint_confirmation_point.rb @@ -18,14 +18,17 @@ module OpenidConnect end end + # TODO: Add support for request object and auth code 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) + if response_types.include?(:token) + res.access_token = auth.create_access_token + end if response_types.include?(:id_token) - id_token = auth.id_tokens.create!(nonce: req.nonce) - options = %i(code access_token).map{|option| ["res.#{option}", res.respond_to?(option) ? res.option : nil]}.to_h - res.id_token = id_token.to_jwt(options) - # TODO: Add support for request object + id_token = auth.create_id_token(req.nonce) + access_token_value = res.respond_to?(:access_token) ? res.access_token : nil + res.id_token = id_token.to_jwt(code: nil, access_token: access_token_value) end res.approve! end diff --git a/spec/controllers/openid_connect/authorizations_controller_spec.rb b/spec/controllers/openid_connect/authorizations_controller_spec.rb index 08b65bc173..bb597254db 100644 --- a/spec/controllers/openid_connect/authorizations_controller_spec.rb +++ b/spec/controllers/openid_connect/authorizations_controller_spec.rb @@ -89,7 +89,7 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end end 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 before do @@ -113,41 +113,73 @@ describe OpenidConnect::AuthorizationsController, type: :controller do end 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 - 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 - 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 + context "when authorization is approved" do + before do + post :create, approve: "true" + end + + it "should return the id 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 + 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") + 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 - context "when authorization is denied" do + context "when id_token" 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 - it "should return an error in the fragment" do - expect(response.location).to have_content("error=") + context "when authorization is approved" do + 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 - it "should NOT contain a id token in the fragment" do - expect(response.location).to_not have_content("id_token=") + context "when authorization is denied" do + 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 -- GitLab