Skip to content
Merged
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
9 changes: 9 additions & 0 deletions config/config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -538,3 +538,12 @@ log:
# convert this to "jsmith".

#downcase_username: true

# If you'd like to limit the service hosts that can use CAS for authentication,
# add the individual IPs and IP ranges in CIDR notation below. Leaving this
# setting blank will allow any server to authenticate users via the CAS server
# and potentially harvest sensitive user information.

#allowed_service_ips:
# - 127.0.0.1
# - 192.168.0.0/24
142 changes: 83 additions & 59 deletions lib/casserver/server.rb
Original file line number Diff line number Diff line change
Expand Up @@ -601,60 +601,70 @@ def self.init_database!
# 2.4

# 2.4.1
get "#{uri_path}/validate" do
CASServer::Utils::log_controller_action(self.class, params)

# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@renew = params['renew']

st, @error = validate_service_ticket(@service, @ticket)
@success = st && !@error

@username = st.username if @success

get "#{uri_path}/validate" do
CASServer::Utils::log_controller_action(self.class, params)

if ip_allowed?(request.ip)
# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@renew = params['renew']

st, @error = validate_service_ticket(@service, @ticket)
@success = st && !@error

@username = st.username if @success
else
@success = false
@error = Error.new(:INVALID_REQUEST, 'The IP address of this service has not been allowed')
end

status response_status_from_error(@error) if @error
render @template_engine, :validate, :layout => false
end

render @template_engine, :validate, :layout => false
end


# 2.5

# 2.5.1
get "#{uri_path}/serviceValidate" do
CASServer::Utils::log_controller_action(self.class, params)
CASServer::Utils::log_controller_action(self.class, params)

# force xml content type
content_type 'text/xml', :charset => 'utf-8'

# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@pgt_url = params['pgtUrl']
@renew = params['renew']

st, @error = validate_service_ticket(@service, @ticket)
@success = st && !@error

if @success
@username = st.username
if @pgt_url
pgt = generate_proxy_granting_ticket(@pgt_url, st)
@pgtiou = pgt.iou if pgt
if ip_allowed?(request.ip)
# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@pgt_url = params['pgtUrl']
@renew = params['renew']

st, @error = validate_service_ticket(@service, @ticket)
@success = st && !@error

if @success
@username = st.username
if @pgt_url
pgt = generate_proxy_granting_ticket(@pgt_url, st)
@pgtiou = pgt.iou if pgt
end
@extra_attributes = st.granted_by_tgt.extra_attributes || {}
end
@extra_attributes = st.granted_by_tgt.extra_attributes || {}
else
@success = false
@error = Error.new(:INVALID_REQUEST, 'The IP address of this service has not been allowed')
end

status response_status_from_error(@error) if @error

render :builder, :proxy_validate
end
render :builder, :proxy_validate
end


# 2.6

# 2.6.1
Expand All @@ -664,32 +674,38 @@ def self.init_database!
# force xml content type
content_type 'text/xml', :charset => 'utf-8'

# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@pgt_url = params['pgtUrl']
@renew = params['renew']
if ip_allowed?(request.ip)

@proxies = []
# required
@service = clean_service_url(params['service'])
@ticket = params['ticket']
# optional
@pgt_url = params['pgtUrl']
@renew = params['renew']

t, @error = validate_proxy_ticket(@service, @ticket)
@success = t && !@error
@proxies = []

@extra_attributes = {}
if @success
@username = t.username
t, @error = validate_proxy_ticket(@service, @ticket)
@success = t && !@error

if t.kind_of? CASServer::Model::ProxyTicket
@proxies << t.granted_by_pgt.service_ticket.service
end
@extra_attributes = {}
if @success
@username = t.username

if @pgt_url
pgt = generate_proxy_granting_ticket(@pgt_url, t)
@pgtiou = pgt.iou if pgt
end
if t.kind_of? CASServer::Model::ProxyTicket
@proxies << t.granted_by_pgt.service_ticket.service
end

@extra_attributes = t.granted_by_tgt.extra_attributes || {}
if @pgt_url
pgt = generate_proxy_granting_ticket(@pgt_url, t)
@pgtiou = pgt.iou if pgt
end

@extra_attributes = t.granted_by_tgt.extra_attributes || {}
end
else
@success = false
@error = Error.new(:INVALID_REQUEST, 'The IP address of this service has not been allowed')
end

status response_status_from_error(@error) if @error
Expand Down Expand Up @@ -751,5 +767,13 @@ def compile_template(engine, data, options, views)
raise unless @custom_views
super engine, data, options, views
end

def ip_allowed?(ip)
require 'ipaddr'

allowed_ips = Array(settings.config[:allowed_service_ips])

allowed_ips.empty? || allowed_ips.any? { |i| IPAddr.new(i) === ip }
end
end
end
end
79 changes: 66 additions & 13 deletions spec/casserver_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -141,27 +141,80 @@
end
end

describe "proxyValidate" do
describe 'validation' do
let(:allowed_ip) { '127.0.0.1' }
let(:unallowed_ip) { '10.0.0.1' }
let(:service) { @target_service }

before do
load_server("default_config")
load_server('default_config') # 127.0.0.0/24 is allowed here
reset_spec_database

visit "/login?service="+CGI.escape(@target_service)
ticket = get_ticket_for(service)

fill_in 'username', :with => VALID_USERNAME
fill_in 'password', :with => VALID_PASSWORD
Rack::Request.any_instance.stub(:ip).and_return(request_ip)
get "/#{path}?service=#{CGI.escape(service)}&ticket=#{CGI.escape(ticket)}"
end

click_button 'login-submit'
subject { last_response }

page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/
@ticket = page.current_url.match(/ticket=(.*)$/)[1]
describe 'validate' do
let(:path) { 'validate' }

context 'from allowed IP' do
let(:request_ip) { allowed_ip }

it { should be_ok }
its(:body) { should match 'yes' }
end

context 'from unallowed IP' do
let(:request_ip) { unallowed_ip }

its(:status) { should eql 422 }
its(:body) { should match 'no' }
end
end

it "should have extra attributes in proper format" do
get "/serviceValidate?service=#{CGI.escape(@target_service)}&ticket=#{@ticket}"
describe 'serviceValidate' do
let(:path) { 'serviceValidate' }

last_response.content_type.should match 'text/xml'
last_response.body.should match "<test_utf_string>Ютф</test_utf_string>"
context 'from allowed IP' do
let(:request_ip) { allowed_ip }

it { should be_ok }
its(:content_type) { should match 'text/xml' }
its(:body) { should match /cas:authenticationSuccess/i }
its(:body) { should match '<test_utf_string>Ютф</test_utf_string>' }
end

context 'from unallowed IP' do
let(:request_ip) { unallowed_ip }

its(:status) { should eql 422 }
its(:content_type) { should match 'text/xml' }
its(:body) { should match /cas:authenticationFailure.*INVALID_REQUEST/i }
end
end

describe 'proxyValidate' do
let(:path) { 'proxyValidate' }

context 'from allowed IP' do
let(:request_ip) { allowed_ip }

it { should be_ok }
its(:content_type) { should match 'text/xml' }
its(:body) { should match /cas:authenticationSuccess/i }
end

context 'from unallowed IP' do
let(:request_ip) { unallowed_ip }

its(:status) { should eql 422 }
its(:content_type) { should match 'text/xml' }
its(:body) { should match /cas:authenticationFailure.*INVALID_REQUEST/i }
end
end
end
end
end
3 changes: 3 additions & 0 deletions spec/config/default_config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,6 @@ enable_single_sign_out: true
#maximum_session_lifetime: 172800

#downcase_username: true

allowed_service_ips:
- 127.0.0.0/24
9 changes: 9 additions & 0 deletions spec/spec_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,12 @@ def reset_spec_database
ActiveRecord::Migration.verbose = false
ActiveRecord::Migrator.migrate("db/migrate")
end

def get_ticket_for(service, username = 'spec_user', password = 'spec_password')
visit "/login?service=#{CGI.escape(service)}"
fill_in 'username', :with => username
fill_in 'password', :with => password
click_button 'login-submit'

page.current_url.match(/ticket=(.*)$/)[1]
end