Add support for allowed_service_ips whitelist.
allowed_service_ips can be set in config.yml to limit service validations to a certain set of IPs or IP ranges. This prevents just any site from being able to grab potentially sensitive personal information.
This commit is contained in:
Родитель
3c0c0dba92
Коммит
1be41b92d9
|
@ -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
|
|
@ -601,60 +601,70 @@ module CASServer
|
|||
# 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']
|
||||
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
|
||||
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 @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
|
||||
|
@ -664,32 +674,38 @@ module CASServer
|
|||
# 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
|
||||
@extra_attributes = {}
|
||||
if @success
|
||||
@username = t.username
|
||||
|
||||
if t.kind_of? CASServer::Model::ProxyTicket
|
||||
@proxies << t.granted_by_pgt.service_ticket.service
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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 || {}
|
||||
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
|
||||
|
@ -751,5 +767,13 @@ module CASServer
|
|||
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
|
|
@ -141,27 +141,80 @@ describe 'CASServer' do
|
|||
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
|
||||
|
||||
click_button 'login-submit'
|
||||
|
||||
page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/
|
||||
@ticket = page.current_url.match(/ticket=(.*)$/)[1]
|
||||
Rack::Request.any_instance.stub(:ip).and_return(request_ip)
|
||||
get "/#{path}?service=#{CGI.escape(service)}&ticket=#{CGI.escape(ticket)}"
|
||||
end
|
||||
|
||||
it "should have extra attributes in proper format" do
|
||||
get "/serviceValidate?service=#{CGI.escape(@target_service)}&ticket=#{@ticket}"
|
||||
subject { last_response }
|
||||
|
||||
last_response.content_type.should match 'text/xml'
|
||||
last_response.body.should match "<test_utf_string>Ютф</test_utf_string>"
|
||||
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
|
||||
|
||||
describe 'serviceValidate' do
|
||||
let(:path) { 'serviceValidate' }
|
||||
|
||||
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
|
|
@ -48,3 +48,6 @@ enable_single_sign_out: true
|
|||
#maximum_session_lifetime: 172800
|
||||
|
||||
#downcase_username: true
|
||||
|
||||
allowed_service_ips:
|
||||
- 127.0.0.0/24
|
|
@ -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
|
Загрузка…
Ссылка в новой задаче