Implement authorization endpoint (part 1)

The user can now authenticate with the authorization
server's authorization endpoint and receive a fake
id token.
This commit is contained in:
theworldbright 2015-07-10 02:09:45 +09:00 committed by theworldbright
parent 059933f076
commit 3cfbcbce8f
8 changed files with 210 additions and 54 deletions

View file

@ -1,47 +1,54 @@
class AuthorizationsController < ApplicationController
class OpenidConnect::AuthorizationsController < ApplicationController
rescue_from Rack::OAuth2::Server::Authorize::BadRequest do |e|
@error = e
logger.info e.backtrace[0,10].join("\n")
render :error, status: e.status
print e.backtrace[0,10].join("\n")
render json: {error: :error, status: e.status} #error_message: e.message
end
before_action :authenticate_user!
def new
call_authorization_endpoint
request_authorization_consent_form
end
def create
call_authorization_endpoint :is_create, params[:approve]
process_authorization_consent(params[:approve])
end
private
def call_authorization_endpoint(is_create = false, approved = false)
endpoint = AuthorizationEndpoint.new is_create, approved
rack_response = *endpoint.call(request.env)
@client, @response_type, @redirect_uri, @scopes, @_request_, @request_uri, @request_object = *[
endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint._request_, endpoint.request_uri, endpoint.request_object
def request_authorization_consent_form
endpoint = OpenidConnect::Authorization::EndpointStartPoint.new(current_user)
endpoint.call(request.env)
@client, @response_type, @redirect_uri, @scopes, @request_object = *[
endpoint.client, endpoint.response_type, endpoint.redirect_uri, endpoint.scopes, endpoint.request_object
]
if (
!is_create &&
(max_age = @request_object.try(:id_token).try(:max_age)) &&
current_account.last_logged_in_at < max_age.seconds.ago
)
flash[:notice] = 'Exceeded Max Age, Login Again'
unauthenticate!
end
respond_as_rack_app *rack_response
end
def respond_as_rack_app(status, header, response)
["WWW-Authenticate"].each do |key|
headers[key] = header[key] if header[key].present?
end
if response.redirect?
redirect_to header['Location']
else
saveRequestParameters
render :new
end
def process_authorization_consent(approvedString)
endpoint = OpenidConnect::Authorization::EndpointConfirmationPoint.new(current_user, to_boolean(approvedString))
restoreRequestParameters(endpoint)
status, header, response = *endpoint.call(request.env)
redirect_to header['Location']
end
def saveRequestParameters
session[:client_id], session[:response_type], session[:redirect_uri], session[:scopes], session[:request_object] =
@client.client_id, @response_type, @redirect_uri, @scopes.collect { |scope| scope.name }, @request_object
end
def restoreRequestParameters(endpoint)
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])
endpoint.scopes, endpoint.request_object =
session[:scopes].collect {|scope| Scope.find_by_name(scope)}, session[:request_object]
end
def to_boolean(str)
str.downcase == "true"
end
end

View file

@ -6,6 +6,8 @@ class OAuthApplication < ActiveRecord::Base
validates :client_id, presence: true, uniqueness: true
validates :client_secret, presence: true
serialize :redirect_uris, JSON
before_validation :setup, on: :create
def setup
self.client_id = SecureRandom.hex(16)
@ -13,6 +15,10 @@ class OAuthApplication < ActiveRecord::Base
end
class << self
def available_response_types
["id_token"]
end
def register!(registrarHash)
registrarHash.validate!
buildClientApplication(registrarHash)

View file

@ -0,0 +1,9 @@
<%= form_tag openid_connect_authorizations_path, class: action do %>
<% if action == :approve %>
<%= submit_tag "Approve" %>
<%= hidden_field_tag :approve, true %>
<% else %>
<%= submit_tag "Deny" %>
<%= hidden_field_tag :approve, false %>
<% end %>
<% end %>

View file

@ -0,0 +1,15 @@
<h2><%= @client.name %></h2>
<p>You will be redirected to <%= @redirect_uri %> with an id token if approved or an error if denied</p>
<ul>
<% @scopes.each do |scope| %>
<li><%= scope.name %></li>
<% end %>
<% if @request_object %>
<li>Request Objects (Currently not supported)</li>
<ul>
<pre><%= JSON.pretty_generate @request_object.as_json %></pre>
</ul>
<% end %>
</ul>
<%= render 'openid_connect/authorizations/form', action: :approve %>
<%= render 'openid_connect/authorizations/form', action: :deny %>

View file

@ -7,11 +7,7 @@ module OpenidConnect
def initialize(current_user)
@user = current_user
@app = Rack::OAuth2::Server::Authorize.new do |req, res|
buildClient(req)
buildRedirectURI(req, res)
verifyNonce(req, res)
buildScopes(req)
buildRequestObject(req)
buildAttributes(req, res)
if OAuthApplication.available_response_types.include? Array(req.response_type).collect(&:to_s).join(' ')
handleResponseType(req, res)
else
@ -19,32 +15,23 @@ module OpenidConnect
end
end
end
def buildAttributes(req, res)
buildClient(req)
buildRedirectURI(req, res)
end
def handleResponseType(req, res)
# Implemented by subclass
end
private
def buildClient(req)
@client = OAuthApplication.find_by_client_id(req.client_id) || req.bad_request!
end
def buildRedirectURI(req, res)
res.redirect_uri = @redirect_uri = req.verify_redirect_uri!(@client.redirect_uris)
end
def verifyNonce(req, res)
if res.protocol_params_location == :fragment && req.nonce.blank?
req.invalid_request! 'nonce required'
end
end
def buildScopes(req)
@scopes = req.scope.inject([]) do |_scopes_, scope|
_scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}"
end
end
def buildRequestObject(req)
@request_object = if (@_request_ = req.request).present?
OpenIDConnect::RequestObject.decode req.request, nil # @client.secret
elsif (@request_uri = req.request_uri).present?
OpenIDConnect::RequestObject.fetch req.request_uri, nil # @client.secret
end
end
def handleResponseType(req, res)
# Implemented by subclass
end
end
end
end

View file

@ -0,0 +1,35 @@
module OpenidConnect
module Authorization
class EndpointConfirmationPoint < Endpoint
def initialize(current_user, approved = false)
super(current_user)
@approved = approved
end
def buildAttributes(req, res)
super(req, res)
# TODO: buildResponseType(req)
end
def handleResponseType(req, res)
handleApproval(@approved, req, res)
end
def handleApproval(approved, req, res)
if approved
approved!(req, res)
else
req.access_denied!
end
end
def approved!(req, res)
response_types = Array(req.response_type)
if response_types.include?(:id_token)
res.id_token = SecureRandom.hex(16) # TODO: Replace with real ID token
end
res.approve!
end
end
end
end

View file

@ -0,0 +1,28 @@
module OpenidConnect
module Authorization
class EndpointStartPoint < Endpoint
def initialize(current_user)
super(current_user)
end
def handleResponseType(req, res)
@response_type = req.response_type
end
def buildAttributes(req, res)
super(req, res)
verifyNonce(req, res)
buildScopes(req)
# TODO: buildRequestObject(req)
end
def verifyNonce(req, res)
if res.protocol_params_location == :fragment && req.nonce.blank?
req.invalid_request! "nonce required"
end
end
def buildScopes(req)
@scopes = req.scope.inject([]) do |_scopes_, scope|
_scopes_ << Scope.find_by_name(scope) or req.invalid_scope! "Unknown scope: #{scope}"
end
end
end
end
end

View file

@ -0,0 +1,69 @@
require 'spec_helper'
describe OpenidConnect::AuthorizationsController, type: :controller do
let!(:client) { OAuthApplication.create!(redirect_uris: ["http://localhost:3000/"]) }
before do
sign_in :user, alice
allow(@controller).to receive(:current_user).and_return(alice)
Scope.create!(name:"openid")
end
describe "#new" do
render_views
context "when valid parameters are passed" do
it "should return a form page" do
get :new,
{
client_id: client.client_id,
redirect_uri: "http://localhost:3000/",
response_type: "id_token",
scope: "openid",
nonce: SecureRandom.hex(16),
state: SecureRandom.hex(16)
}
expect(response.body).to match("Approve")
expect(response.body).to match("Deny")
end
end
# TODO: Implement tests for missing/invalid parameters
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: SecureRandom.hex(16),
state: SecureRandom.hex(16)
}
end
context "when authorization is approved" do
it "should return the id token in a fragment" do
post :create,
{
approve: "true"
}
expect(response.location).to have_content("#id_token=")
end
end
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