Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Setting verify_ssl_cert was not taking effect / Proxy Support #44

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,5 @@
Gemfile.lock
/gemfiles/*.lock
pkg/*
.rvmrc
.idea
26 changes: 26 additions & 0 deletions lib/generators/proxy_granting_ticket_ious_migration_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
require 'rails/generators'
require 'rails/generators/migration'

class ProxyGrantingTicketIousMigrationGenerator < Rails::Generators::Base
include Rails::Generators::Migration

desc 'Creates a new Proxy Granting Ticket IOUs migration file'

def self.source_root
File.expand_path('../templates', __FILE__)
end

def self.next_migration_number(dirname)
if ActiveRecord::Base.timestamped_migrations
migration_number = Time.now.utc.strftime("%Y%m%d%H%M%S").to_i
migration_number += 1
migration_number.to_s
else
"%.3d" % (current_migration_number(dirname) + 1)
end
end

def create_migration_file
migration_template 'create_rack_cas_proxy_granting_ticket_ious_migration.rb', 'db/migrate/create_rack_cas_proxy_granting_ticket_ious.rb'
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class CreateRackCasProxyGrantingTicketIous < ActiveRecord::Migration
def self.up
create_table :proxy_granting_ticket_ious do |t|
t.string :proxy_granting_ticket_iou, :null => false
t.string :proxy_granting_ticket, :null => false
t.timestamps
end

add_index :proxy_granting_ticket_ious, :proxy_granting_ticket_iou
add_index :proxy_granting_ticket_ious, :proxy_granting_ticket
add_index :proxy_granting_ticket_ious, :updated_at
end

def self.down
drop_table :proxy_granting_ticket_ious
end
end
35 changes: 32 additions & 3 deletions lib/rack-cas/cas_request.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require 'addressable/uri'
require 'nokogiri'

class CASRequest
Expand Down Expand Up @@ -29,10 +30,15 @@ def single_sign_out?

def ticket_validation?
# The CAS protocol specifies that services must support tickets of
# *up to* 32 characters in length (including ST-), and recommendes
# *up to* 32 characters in length (including ST-), and recommends
# that services accept tickets up to 256 characters long.
# http://www.jasig.org/cas/protocol
!!(@request.get? && ticket_param && ticket_param.to_s =~ /\AST\-[^\s]{1,253}\Z/)
#
# It also specifies that although the service ticket MUST start with "ST-",
# the proxy ticket SHOULD start with "PT-". The "ST-" validation has
# been moved to the validate_service_url method in server.rb.
#
# http://jasig.github.io/cas/development/protocol/CAS-Protocol-Specification.html
!!(@request.get? && ticket_param && ticket_param.to_s =~ /\A[^\s]{1,256}\Z/)
end

def path_matches?(strings_or_regexps)
Expand All @@ -45,12 +51,35 @@ def path_matches?(strings_or_regexps)
end
end

def pgt_callback?
!!(@request.get? && RackCAS.config.pgt_callback_url? && \
Addressable::URI.parse(RackCAS.config.pgt_callback_url).path == Addressable::URI.parse(@request.url).path && \
pgt_iou_param && pgt_iou_param.to_s =~ /\A[^\s]{1,256}\Z/ && \
pgt_param && pgt_param.to_s =~ /\A[^\s]{1,256}\Z/)
end

def pgt
pgt_param if pgt_callback?
end

def pgt_iou
pgt_iou_param if pgt_callback?
end

private

def ticket_param
@request.params['ticket']
end

def pgt_iou_param
@request.params['pgtIou']
end

def pgt_param
@request.params['pgtId']
end

def sso_ticket
xml = Nokogiri::XML(@request.params['logoutRequest'])
node = xml.root.children.find { |c| c.name =~ /SessionIndex/i }
Expand Down
4 changes: 2 additions & 2 deletions lib/rack-cas/configuration.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
module RackCAS
class Configuration
SETTINGS = [:fake, :server_url, :session_store, :exclude_path, :exclude_paths, :extra_attributes_filter,
:verify_ssl_cert, :renew, :use_saml_validation, :ignore_intercept_validator, :exclude_request_validator, :protocol]

:verify_ssl_cert, :renew, :use_saml_validation, :ignore_intercept_validator, :exclude_request_validator,
:protocol, :pgt_callback_url]

SETTINGS.each do |setting|
attr_accessor setting
Expand Down
79 changes: 79 additions & 0 deletions lib/rack-cas/proxy_response.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
require 'nokogiri'

module RackCAS
class ProxyResponse
class ProxyFailure < StandardError; end
class RequestInvalidError < ProxyFailure; end
class UnauthorizedServiceError < ProxyFailure; end
class InternalError < ProxyFailure; end

REQUEST_HEADERS = { 'Accept' => '*/*' }

def initialize(url)
@url = URL.parse(url)
end

def proxy_ticket
if success?
xml.xpath('/cas:serviceResponse/cas:proxySuccess/cas:proxyTicket').text
else
case failure_code
when 'INVALID_REQUEST'
raise RequestInvalidError, failure_message
when 'UNAUTHORIZED_SERVICE'
raise UnauthorizedServiceError, failure_message
when 'INTERNAL_ERROR'
raise InternalError, failure_message
else
raise ProxyFailure, failure_message
end
end
end

protected

def success?
@success ||= !!xml.at('/cas:serviceResponse/cas:proxySuccess')
end

def proxy_failure
@proxy_failure ||= xml.at('/cas:serviceResponse/cas:proxyFailure')
end

def failure_message
if proxy_failure
proxy_failure.text.strip
end
end

def failure_code
if proxy_failure
proxy_failure['code']
end
end

def response
require 'net/http'
return @response unless @response.nil?

http = Net::HTTP.new(@url.host, @url.inferred_port)
if @url.scheme == 'https'
http.use_ssl = true
http.verify_mode = RackCAS.config.verify_ssl_cert? ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
end

http.start do |conn|
@response = conn.get(@url.request_uri, REQUEST_HEADERS)
end

@response
end

def xml
return @xml unless @xml.nil?

@xml = Nokogiri::XML(response.body)
end

end
end
24 changes: 24 additions & 0 deletions lib/rack-cas/proxy_ticket_generator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
require 'rack-cas/url'
require 'rack-cas/proxy_response'

module RackCAS
class ProxyTicketGenerator

def self.generate(service_url, pgt)
response = ProxyResponse.new proxy_url(service_url, pgt)
response.proxy_ticket
end

private

def self.proxy_url(service_url, pgt)
service_url = URL.parse(service_url).remove_param('ticket').to_s
server_url = RackCAS::URL.parse(RackCAS.config.server_url)
server_url.dup.tap do |url|
url.append_path('proxy')
url.add_params(targetService: service_url, pgt: pgt)
end
end

end
end
23 changes: 18 additions & 5 deletions lib/rack-cas/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,17 @@ def logout_url(params = {})
end
end

def validate_service(service_url, ticket)
def validate_service(service_url, ticket, pgt_url = RackCAS.config.pgt_callback_url)
pgt_iou = nil
unless RackCAS.config.use_saml_validation?
response = ServiceValidationResponse.new validate_service_url(service_url, ticket)
response = ServiceValidationResponse.new validate_service_url(service_url, ticket, pgt_url)
if !!pgt_url
pgt_iou = response.proxy_granting_ticket_iou
end
else
response = SAMLValidationResponse.new saml_validate_url(service_url), ticket
end
[response.user, response.extra_attributes]
[response.user, response.extra_attributes, pgt_iou]
end

protected
Expand All @@ -39,9 +43,17 @@ def saml_validate_url(service_url)
@url.dup.append_path(path_for_protocol('samlValidate')).add_params(TARGET: service_url)
end

def validate_service_url(service_url, ticket)
def validate_service_url(service_url, ticket, pgt_url = RackCAS.config.pgt_callback_url)
service_url = URL.parse(service_url).remove_param('ticket').to_s
@url.dup.append_path(path_for_protocol('serviceValidate')).add_params(service: service_url, ticket: ticket)
@url.dup.tap do |url|
if ticket =~ /\AST\-[^\s]{1,253}\Z/
url.append_path(path_for_protocol('serviceValidate'))
else
url.append_path(path_for_protocol('proxyValidate'))
end
url.add_params(service: service_url, ticket: ticket)
url.add_params(pgtUrl: pgt_url) if pgt_url
end
end

def path_for_protocol(path)
Expand All @@ -51,5 +63,6 @@ def path_for_protocol(path)
path
end
end

end
end
6 changes: 6 additions & 0 deletions lib/rack-cas/service_validation_response.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ def extra_attributes
attrs
end

def proxy_granting_ticket_iou
if success?
@proxy_granting_ticket_iou ||= xml.at('//serviceResponse/authenticationSuccess/proxyGrantingTicket').text
end
end

protected

def success?
Expand Down
13 changes: 13 additions & 0 deletions lib/rack-cas/session_store/active_record.rb
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
module RackCAS
module ActiveRecordStore
class ProxyGrantingTicketIou < ActiveRecord::Base
end

class Session < ActiveRecord::Base
end

def self.create_proxy_granting_ticket(pgt_iou, pgt)
ProxyGrantingTicketIou.create!(proxy_granting_ticket_iou: pgt_iou, proxy_granting_ticket: pgt)
end

def self.proxy_granting_ticket_for(pgt_iou)
proxy_granting_ticket_iou = ProxyGrantingTicketIou.where(proxy_granting_ticket_iou: pgt_iou).first || nil
proxy_granting_ticket_iou.proxy_granting_ticket if proxy_granting_ticket_iou
end

def self.destroy_session_by_cas_ticket(cas_ticket)
affected = Session.delete_all(cas_ticket: cas_ticket)
affected == 1
Expand All @@ -11,6 +23,7 @@ def self.destroy_session_by_cas_ticket(cas_ticket)
def self.prune(after = nil)
after ||= Time.now - 2592000 # 30 days ago
Session.where('updated_at < ?', after).delete_all
ProxyGrantingTicketIou.where('updated_at < ?', after).delete_all
end

private
Expand Down
22 changes: 22 additions & 0 deletions lib/rack-cas/session_store/mongoid.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
module RackCAS
module MongoidStore

class ProxyGrantingTicketIou
include Mongoid::Document
include Mongoid::Timestamps

field :_id, type: String
field :proxy_granting_ticket_iou, type: String
field :proxy_granting_ticket, type: String
end

class Session
include Mongoid::Document
include Mongoid::Timestamps
Expand All @@ -15,6 +25,17 @@ class Session
field :cas_ticket, type: String
end

def self.create_proxy_granting_ticket(pgt_iou, pgt)
ProxyGrantingTicketIou.create!(proxy_granting_ticket_iou: pgt_iou, proxy_granting_ticket: pgt)
end

def self.proxy_granting_ticket_for(pgt_iou = nil)
if pgt_iou
proxy_granting_ticket_iou = ProxyGrantingTicketIou.where(proxy_granting_ticket_iou: pgt_iou).first || nil
proxy_granting_ticket_iou.proxy_granting_ticket if proxy_granting_ticket_iou
end
end

def self.destroy_session_by_cas_ticket(cas_ticket)
affected = Session.where(cas_ticket: cas_ticket).delete
affected == 1
Expand All @@ -23,6 +44,7 @@ def self.destroy_session_by_cas_ticket(cas_ticket)
def self.prune(after = nil)
after ||= Time.now - 2592000 # 30 days ago
Session.where(:updated_at.lte => after).delete
ProxyGrantingTicketIou.where(:updated_at.lte => after).delete
end

private
Expand Down
10 changes: 5 additions & 5 deletions lib/rack-cas/url.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def self.parse(uri)
# Addressable to replace + spaces with %20 spaces. Standardizing on %20
# should prevent service lookup issues due to encoding differences.
super.tap do |u|
u.query_values = u.query_values
u.query_values = u.query_values(Array)
end
end

Expand All @@ -19,9 +19,9 @@ def append_path(path)

def add_params(params)
self.tap do |u|
u.query_values = (u.query_values || {}).tap do |qv|
u.query_values = (u.query_values(Array) || []).tap do |qv|
params.each do |key, value|
qv[key] = value
qv << [key, value]
end
end
end
Expand All @@ -34,9 +34,9 @@ def remove_param(param)
# params can be an array or a hash
def remove_params(params)
self.tap do |u|
u.query_values = (u.query_values || {}).tap do |qv|
u.query_values = (u.query_values(Array) || []).tap do |qv|
params.each do |key, value|
qv.delete key
qv.reject! { |param| param.first == key }
end
end
if u.query_values.empty?
Expand Down
Loading