diff --git a/config/config.example.yml b/config/config.example.yml index f49c5211..772ec4ec 100644 --- a/config/config.example.yml +++ b/config/config.example.yml @@ -534,10 +534,14 @@ log: #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 +# you can either limit specific service URIs (validating the service parameter), +# or add the individual IPs and IP ranges in CIDR notation below. Leaving these +# settings blank will allow any server to authenticate users via the CAS server # and potentially harvest sensitive user information. +#allowed_service_uris: +# - https://myservice.com/login/cas + #allowed_service_ips: # - 127.0.0.1 # - 192.168.0.0/24 diff --git a/lib/casserver/server.rb b/lib/casserver/server.rb index 14a04bda..c03f0d98 100644 --- a/lib/casserver/server.rb +++ b/lib/casserver/server.rb @@ -322,7 +322,11 @@ def self.init_database! begin if @service - if @renew + if !service_allowed?(@service) + $LOG.warn("Disallowed service '#{@service}'.") + @message = {:type => 'mistake', + :message => t.error.disallowed_service} + elsif @renew $LOG.info("Authentication renew explicitly requested. Proceeding with CAS login for service #{@service.inspect}.") elsif tgt && !tgt_error $LOG.debug("Valid ticket granting ticket detected.") @@ -409,6 +413,13 @@ def self.init_database! @username.downcase! end + if !@service.blank? && !service_allowed?(@service) + @message = {:type => 'mistake', + :message => t.error.disallowed_service} + status 400 + return render @template_engine, :login + end + if error = validate_login_ticket(@lt) @message = {:type => 'mistake', :message => error} # generate another login ticket to allow for re-submitting the form @@ -765,6 +776,12 @@ def compile_template(engine, data, options, views) super engine, data, options, views end + def service_allowed?(service) + allowed_uris = Array(settings.config[:allowed_service_uris]) + + allowed_uris.empty? || allowed_uris.include?(service) + end + def ip_allowed?(ip) require 'ipaddr' diff --git a/locales/en.yml b/locales/en.yml index 99eb6094..889d07f1 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -9,6 +9,7 @@ error: invalid_target_service: "The target service your browser supplied appears to be invalid. Please contact your system administrator for help." unable_to_authenticate: "The client and server are unable to negotiate authentication. Please try logging in again later." no_service_parameter_given: "The server cannot fulfill this gateway request because no service parameter was given." + disallowed_service: "The target service is not allowed to authenticate against this CAS server. Please contact your system administrator for help." notice: logged_in_as: "You are currently logged in as '%1'. If this is not you, please log in below." diff --git a/spec/casserver_spec.rb b/spec/casserver_spec.rb index b49c1a03..ba81a740 100644 --- a/spec/casserver_spec.rb +++ b/spec/casserver_spec.rb @@ -36,6 +36,18 @@ page.should have_content("You have successfully logged in") end + it "auto-forwards when you're already logged in" do + visit "/login" + + fill_in 'username', :with => VALID_USERNAME + fill_in 'password', :with => VALID_PASSWORD + click_button 'login-submit' + + visit "/login?service="+CGI.escape(@target_service) + + page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/ + end + it "fails to log in with invalid password" do visit "/login" fill_in 'username', :with => VALID_USERNAME @@ -92,6 +104,38 @@ #page.should have_xpath("") end + describe 'service_uri validation' do + let(:service) { 'http://imposter.com/' } + + it "doesn't redirect back to untrusted services" do + visit "/login?service="+CGI.escape(service) + + page.should have_content("The target service is not allowed") + + fill_in 'username', :with => VALID_USERNAME + fill_in 'password', :with => VALID_PASSWORD + + click_button 'login-submit' + + page.should have_content("The target service is not allowed") + end + + it "doesn't redirect back when already logged in" do + visit "/login" + + fill_in 'username', :with => VALID_USERNAME + fill_in 'password', :with => VALID_PASSWORD + + click_button 'login-submit' + + visit "/login?service="+CGI.escape(service) + + page.should have_content("The target service is not allowed") + end + + end + + end # describe '/login' diff --git a/spec/config/default_config.yml b/spec/config/default_config.yml index 90a83914..ede1abc9 100644 --- a/spec/config/default_config.yml +++ b/spec/config/default_config.yml @@ -50,4 +50,7 @@ enable_single_sign_out: true #downcase_username: true allowed_service_ips: - - 127.0.0.0/24 \ No newline at end of file + - 127.0.0.0/24 + +allowed_service_uris: + - http://my.app.test \ No newline at end of file