synapse-rubycas-server 1.1.3alpha

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +15 -0
  2. data/CHANGELOG +353 -0
  3. data/Gemfile +12 -0
  4. data/LICENSE +26 -0
  5. data/README.md +38 -0
  6. data/Rakefile +3 -0
  7. data/bin/rubycas-server +30 -0
  8. data/config/config.example.yml +552 -0
  9. data/config/unicorn.rb +88 -0
  10. data/config.ru +11 -0
  11. data/db/migrate/001_create_initial_structure.rb +47 -0
  12. data/db/migrate/002_add_indexes_for_performance.rb +15 -0
  13. data/lib/casserver/authenticators/active_directory_ldap.rb +17 -0
  14. data/lib/casserver/authenticators/active_resource.rb +113 -0
  15. data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
  16. data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
  17. data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
  18. data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
  19. data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
  20. data/lib/casserver/authenticators/base.rb +70 -0
  21. data/lib/casserver/authenticators/client_certificate.rb +47 -0
  22. data/lib/casserver/authenticators/google.rb +62 -0
  23. data/lib/casserver/authenticators/ldap.rb +131 -0
  24. data/lib/casserver/authenticators/ntlm.rb +88 -0
  25. data/lib/casserver/authenticators/open_id.rb +19 -0
  26. data/lib/casserver/authenticators/sql.rb +158 -0
  27. data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
  28. data/lib/casserver/authenticators/sql_bcrypt.rb +17 -0
  29. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  30. data/lib/casserver/authenticators/sql_md5.rb +19 -0
  31. data/lib/casserver/authenticators/sql_rest_auth.rb +82 -0
  32. data/lib/casserver/authenticators/test.rb +21 -0
  33. data/lib/casserver/base.rb +13 -0
  34. data/lib/casserver/cas.rb +324 -0
  35. data/lib/casserver/core_ext/directory_user.rb +81 -0
  36. data/lib/casserver/core_ext/securerandom.rb +17 -0
  37. data/lib/casserver/core_ext/string.rb +22 -0
  38. data/lib/casserver/core_ext.rb +12 -0
  39. data/lib/casserver/model/consumable.rb +31 -0
  40. data/lib/casserver/model/ticket.rb +19 -0
  41. data/lib/casserver/model.rb +248 -0
  42. data/lib/casserver/server.rb +796 -0
  43. data/lib/casserver/utils.rb +20 -0
  44. data/lib/casserver/views/_login_form.erb +42 -0
  45. data/lib/casserver/views/layout.erb +18 -0
  46. data/lib/casserver/views/login.erb +30 -0
  47. data/lib/casserver/views/proxy.builder +13 -0
  48. data/lib/casserver/views/proxy_validate.builder +31 -0
  49. data/lib/casserver/views/service_validate.builder +24 -0
  50. data/lib/casserver/views/validate.erb +2 -0
  51. data/lib/casserver.rb +19 -0
  52. data/locales/de.yml +27 -0
  53. data/locales/en.yml +26 -0
  54. data/locales/es.yml +26 -0
  55. data/locales/es_ar.yml +26 -0
  56. data/locales/fr.yml +26 -0
  57. data/locales/it.yml +26 -0
  58. data/locales/jp.yml +26 -0
  59. data/locales/pl.yml +26 -0
  60. data/locales/pt.yml +26 -0
  61. data/locales/ru.yml +26 -0
  62. data/locales/zh.yml +26 -0
  63. data/locales/zh_tw.yml +26 -0
  64. data/public/themes/cas.css +126 -0
  65. data/public/themes/notice.png +0 -0
  66. data/public/themes/ok.png +0 -0
  67. data/public/themes/simple/bg.png +0 -0
  68. data/public/themes/simple/favicon.png +0 -0
  69. data/public/themes/simple/login_box_bg.png +0 -0
  70. data/public/themes/simple/logo.png +0 -0
  71. data/public/themes/simple/theme.css +28 -0
  72. data/public/themes/warning.png +0 -0
  73. data/resources/init.d.sh +58 -0
  74. data/spec/casserver/authenticators/active_resource_spec.rb +116 -0
  75. data/spec/casserver/authenticators/ldap_spec.rb +57 -0
  76. data/spec/casserver/cas_spec.rb +148 -0
  77. data/spec/casserver/model_spec.rb +42 -0
  78. data/spec/casserver/utils_spec.rb +24 -0
  79. data/spec/casserver_spec.rb +221 -0
  80. data/spec/config/alt_config.yml +50 -0
  81. data/spec/config/default_config.yml +56 -0
  82. data/spec/core_ext/string_spec.rb +28 -0
  83. data/spec/spec.opts +4 -0
  84. data/spec/spec_helper.rb +126 -0
  85. data/tasks/bundler.rake +4 -0
  86. data/tasks/db/migrate.rake +12 -0
  87. data/tasks/spec.rake +10 -0
  88. metadata +405 -0
@@ -0,0 +1,221 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ $LOG = Logger.new(File.basename(__FILE__).gsub('.rb','.log'))
5
+
6
+ RSpec.configure do |config|
7
+ config.include Capybara::DSL
8
+ end
9
+
10
+ VALID_USERNAME = 'spec_user'
11
+ VALID_PASSWORD = 'spec_password'
12
+
13
+ ATTACK_USERNAME = '%3E%22%27%3E%3Cscript%3Ealert%2826%29%3C%2Fscript%3E&password=%3E%22%27%3E%3Cscript%3Ealert%2826%29%3C%2Fscript%3E&lt=%3E%22%27%3E%3Cscript%3Ealert%2826%29%3C%2Fscript%3E&service=%3E%22%27%3E%3Cscript%3Ealert%2826%29%3C%2Fscript%3E'
14
+ INVALID_PASSWORD = 'invalid_password'
15
+
16
+ describe 'CASServer' do
17
+ include Rack::Test::Methods
18
+
19
+ before do
20
+ @target_service = 'http://my.app.test'
21
+ end
22
+
23
+ describe "/login" do
24
+ before do
25
+ load_server("default_config")
26
+ reset_spec_database
27
+ end
28
+
29
+ it "logs in successfully with valid username and password without a target service" do
30
+ visit "/login"
31
+
32
+ fill_in 'username', :with => VALID_USERNAME
33
+ fill_in 'password', :with => VALID_PASSWORD
34
+ click_button 'login-submit'
35
+
36
+ page.should have_content("You have successfully logged in")
37
+ Capybara.current_session.driver.response.headers['Set-Cookie'].should match 'path=/'
38
+ end
39
+
40
+ it "fails to log in with invalid password" do
41
+ visit "/login"
42
+ fill_in 'username', :with => VALID_USERNAME
43
+ fill_in 'password', :with => INVALID_PASSWORD
44
+ click_button 'login-submit'
45
+
46
+ page.should have_content("Incorrect username or password")
47
+ end
48
+
49
+ it "logs in successfully with valid username and password and redirects to target service" do
50
+ visit "/login?service="+CGI.escape(@target_service)
51
+
52
+ fill_in 'username', :with => VALID_USERNAME
53
+ fill_in 'password', :with => VALID_PASSWORD
54
+
55
+ click_button 'login-submit'
56
+
57
+ page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/
58
+ end
59
+
60
+ it "preserves target service after invalid login" do
61
+ visit "/login?service="+CGI.escape(@target_service)
62
+
63
+ fill_in 'username', :with => VALID_USERNAME
64
+ fill_in 'password', :with => INVALID_PASSWORD
65
+ click_button 'login-submit'
66
+
67
+ page.should have_content("Incorrect username or password")
68
+ page.should have_xpath('//input[@id="service"]', :value => @target_service)
69
+ end
70
+
71
+ it "uses appropriate localization based on Accept-Language header" do
72
+
73
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'pl'}
74
+ #visit "/login?lang=pl"
75
+ visit "/login"
76
+ page.should have_content("Użytkownik")
77
+
78
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'pt_BR'}
79
+ #visit "/login?lang=pt_BR"
80
+ visit "/login"
81
+ page.should have_content("Usuário")
82
+
83
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'en'}
84
+ #visit "/login?lang=en"
85
+ visit "/login"
86
+ page.should have_content("Username")
87
+ end
88
+
89
+ it "is not vunerable to Cross Site Scripting" do
90
+ visit '/login?service=%22%2F%3E%3cscript%3ealert%2832%29%3c%2fscript%3e'
91
+ page.should_not have_content("alert(32)")
92
+ page.should_not have_xpath("//script")
93
+ #page.should have_xpath("<script>alert(32)</script>")
94
+ end
95
+
96
+ end # describe '/login'
97
+
98
+
99
+ describe '/logout' do
100
+ describe 'user logged in' do
101
+ before do
102
+ load_server("default_config")
103
+ reset_spec_database
104
+ visit "/login"
105
+ fill_in 'username', :with => VALID_USERNAME
106
+ fill_in 'password', :with => VALID_PASSWORD
107
+ click_button 'login-submit'
108
+ page.should have_content("You have successfully logged in")
109
+ end
110
+
111
+ it "logs out user who is looged in" do
112
+ visit "/logout"
113
+ page.should have_content("You have successfully logged out")
114
+ end
115
+
116
+ it "logs out successfully and redirects to target service" do
117
+ visit "/logout?gateway=true&service="+CGI.escape(@target_service)
118
+
119
+ page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?/
120
+ end
121
+ end
122
+
123
+ describe "user not logged in" do
124
+ it "try logs out user which is not logged in" do
125
+ visit "/logout"
126
+ page.should have_content("You have successfully logged out")
127
+ end
128
+ end
129
+
130
+ end # describe '/logout'
131
+
132
+ describe 'Configuration' do
133
+ it "uri_path value changes prefix of routes" do
134
+ load_server("alt_config")
135
+ @target_service = 'http://my.app.test'
136
+
137
+ visit "/test/login"
138
+ page.status_code.should_not == 404
139
+
140
+ visit "/test/logout"
141
+ page.status_code.should_not == 404
142
+ end
143
+ end
144
+
145
+ describe 'validation' do
146
+ let(:allowed_ip) { '127.0.0.1' }
147
+ let(:unallowed_ip) { '10.0.0.1' }
148
+ let(:service) { @target_service }
149
+
150
+ before do
151
+ load_server('default_config') # 127.0.0.0/24 is allowed here
152
+ reset_spec_database
153
+
154
+ ticket = get_ticket_for(service)
155
+
156
+ Rack::Request.any_instance.stub(:ip).and_return(request_ip)
157
+ get "/#{path}?service=#{CGI.escape(service)}&ticket=#{CGI.escape(ticket)}"
158
+ end
159
+
160
+ subject { last_response }
161
+
162
+ describe 'validate' do
163
+ let(:path) { 'validate' }
164
+
165
+ context 'from allowed IP' do
166
+ let(:request_ip) { allowed_ip }
167
+
168
+ it { should be_ok }
169
+ its(:body) { should match 'yes' }
170
+ end
171
+
172
+ context 'from unallowed IP' do
173
+ let(:request_ip) { unallowed_ip }
174
+
175
+ its(:status) { should eql 422 }
176
+ its(:body) { should match 'no' }
177
+ end
178
+ end
179
+
180
+ describe 'serviceValidate' do
181
+ let(:path) { 'serviceValidate' }
182
+
183
+ context 'from allowed IP' do
184
+ let(:request_ip) { allowed_ip }
185
+
186
+ it { should be_ok }
187
+ its(:content_type) { should match 'text/xml' }
188
+ its(:body) { should match /cas:authenticationSuccess/i }
189
+ its(:body) { should match '<test_utf_string>Ютф</test_utf_string>' }
190
+ end
191
+
192
+ context 'from unallowed IP' do
193
+ let(:request_ip) { unallowed_ip }
194
+
195
+ its(:status) { should eql 422 }
196
+ its(:content_type) { should match 'text/xml' }
197
+ its(:body) { should match /cas:authenticationFailure.*INVALID_REQUEST/i }
198
+ end
199
+ end
200
+
201
+ describe 'proxyValidate' do
202
+ let(:path) { 'proxyValidate' }
203
+
204
+ context 'from allowed IP' do
205
+ let(:request_ip) { allowed_ip }
206
+
207
+ it { should be_ok }
208
+ its(:content_type) { should match 'text/xml' }
209
+ its(:body) { should match /cas:authenticationSuccess/i }
210
+ end
211
+
212
+ context 'from unallowed IP' do
213
+ let(:request_ip) { unallowed_ip }
214
+
215
+ its(:status) { should eql 422 }
216
+ its(:content_type) { should match 'text/xml' }
217
+ its(:body) { should match /cas:authenticationFailure.*INVALID_REQUEST/i }
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,50 @@
1
+ server: webrick
2
+ port: 6543
3
+ #ssl_cert: test.pem
4
+ uri_path: /test
5
+ #bind_address: 0.0.0.0
6
+
7
+ # database:
8
+ # adapter: mysql
9
+ # database: casserver
10
+ # username: root
11
+ # password:
12
+ # host: localhost
13
+ # reconnect: true
14
+ database:
15
+ adapter: sqlite3
16
+ database: spec/casserver_spec.db
17
+
18
+ disable_auto_migrations: true
19
+
20
+ quiet: true
21
+
22
+ authenticator:
23
+ class: CASServer::Authenticators::Test
24
+ password: spec_password
25
+
26
+ theme: simple
27
+
28
+ organization: "RSPEC-TEST"
29
+
30
+ infoline: "This is an rspec test."
31
+
32
+ #custom_views: /path/to/custom/views
33
+
34
+ default_locale: en
35
+
36
+ log:
37
+ file: casserver_spec.log
38
+ level: DEBUG
39
+
40
+ #db_log:
41
+ # file: casserver_spec_db.log
42
+
43
+ enable_single_sign_out: true
44
+
45
+ #maximum_unused_login_ticket_lifetime: 300
46
+ #maximum_unused_service_ticket_lifetime: 300
47
+
48
+ #maximum_session_lifetime: 172800
49
+
50
+ #downcase_username: true
@@ -0,0 +1,56 @@
1
+ server: webrick
2
+ port: 6543
3
+ #ssl_cert: test.pem
4
+ #uri_path: /cas
5
+ #bind_address: 0.0.0.0
6
+
7
+ # database:
8
+ # adapter: mysql
9
+ # database: casserver
10
+ # username: root
11
+ # password:
12
+ # host: localhost
13
+ # reconnect: true
14
+ database:
15
+ adapter: sqlite3
16
+ database: spec/casserver_spec.db
17
+
18
+ disable_auto_migrations: true
19
+
20
+ quiet: true
21
+
22
+ authenticator:
23
+ class: CASServer::Authenticators::Test
24
+ password: spec_password
25
+
26
+ theme: simple
27
+
28
+ organization: "RSPEC-TEST"
29
+
30
+ infoline: "This is an rspec test."
31
+
32
+ #custom_views: /path/to/custom/views
33
+
34
+ default_locale: en
35
+
36
+ log:
37
+ file: casserver_spec.log
38
+ level: DEBUG
39
+
40
+ #db_log:
41
+ # file: casserver_spec_db.log
42
+
43
+ enable_single_sign_out: true
44
+
45
+ #maximum_unused_login_ticket_lifetime: 300
46
+ #maximum_unused_service_ticket_lifetime: 300
47
+
48
+ #maximum_session_lifetime: 172800
49
+
50
+ cookie_options:
51
+ path: "/"
52
+
53
+ #downcase_username: true
54
+
55
+ allowed_service_ips:
56
+ - 127.0.0.0/24
@@ -0,0 +1,28 @@
1
+ require 'spec_helper'
2
+ require 'casserver/core_ext'
3
+
4
+ describe CASServer::CoreExt::String do
5
+ describe '.random(length = 29)' do
6
+ context 'when max length is not passed in' do
7
+ it 'should return a random string of length 29' do
8
+ String.random.length.should == 29
9
+ end
10
+ end
11
+
12
+ context 'when max length is passed in' do
13
+ it 'should return a random string of the desired length' do
14
+ String.random(30).length.should == 30
15
+ end
16
+ end
17
+
18
+ it 'should include the letter r in the random string' do
19
+ String.random.should include 'r'
20
+ end
21
+
22
+ it 'should return a random string' do
23
+ random_string = String.random
24
+ another_random_string = String.random
25
+ random_string.should_not == another_random_string
26
+ end
27
+ end
28
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,4 @@
1
+ --colour
2
+ --format nested
3
+ --loadby mtime
4
+ --reverse
@@ -0,0 +1,126 @@
1
+ require 'rubygems'
2
+ require 'rack/test'
3
+ require 'rspec'
4
+ #require 'spec/autorun'
5
+ #require 'spec/interop/test'
6
+ require 'logger'
7
+ require 'ostruct'
8
+ require 'webmock/rspec'
9
+
10
+ require 'capybara'
11
+ require 'capybara/dsl'
12
+ require 'casserver/authenticators/base'
13
+ require 'casserver/core_ext.rb'
14
+
15
+ CASServer::Authenticators.autoload :LDAP, 'casserver/authenticators/ldap.rb'
16
+ CASServer::Authenticators.autoload :ActiveDirectoryLDAP, 'casserver/authenticators/active_directory_ldap.rb'
17
+ CASServer::Authenticators.autoload :SQL, 'casserver/authenticators/sql.rb'
18
+ CASServer::Authenticators.autoload :SQLEncrypted, 'lib/casserver/authenticators/sql_encrypted.rb'
19
+ CASServer::Authenticators.autoload :Google, 'casserver/authenticators/google.rb'
20
+ CASServer::Authenticators.autoload :ActiveResource, 'casserver/authenticators/active_resource.rb'
21
+ CASServer::Authenticators.autoload :Test, 'casserver/authenticators/test.rb'
22
+
23
+ # require builder because it doesn't pull in the version
24
+ # info automatically...
25
+ begin
26
+ require 'builder'
27
+ require 'builder/version'
28
+ rescue LoadError
29
+ puts "builder not found, testing ActiveRecord 2.3?"
30
+ end
31
+
32
+ if Dir.getwd =~ /\/spec$/
33
+ # Avoid potential weirdness by changing the working directory to the CASServer root
34
+ FileUtils.cd('..')
35
+ end
36
+
37
+ def silence_warnings
38
+ old_verbose, $VERBOSE = $VERBOSE, nil
39
+ yield
40
+ ensure
41
+ $VERBOSE = old_verbose
42
+ end
43
+
44
+ # Ugly monkeypatch to allow us to test for correct redirection to
45
+ # external services.
46
+ #
47
+ # This will likely break in the future when Capybara or RackTest are upgraded.
48
+ class Capybara::RackTest::Browser
49
+ def current_url
50
+ if @redirected_to_external_url
51
+ @redirected_to_external_url
52
+ else
53
+ request.url rescue ""
54
+ end
55
+ end
56
+
57
+ def follow_redirects!
58
+ if last_response.redirect? && last_response['Location'] =~ /^http[s]?:/
59
+ @redirected_to_external_url = last_response['Location']
60
+ else
61
+ 5.times do
62
+ follow_redirect! if last_response.redirect?
63
+ end
64
+ raise Capybara::InfiniteRedirectError, "redirected more than 5 times, check for infinite redirects." if last_response.redirect?
65
+ end
66
+ end
67
+ end
68
+
69
+ # This called in specs' `before` block.
70
+ # Due to the way Sinatra applications are loaded,
71
+ # we're forced to delay loading of the server code
72
+ # until the start of each test so that certain
73
+ # configuraiton options can be changed (e.g. `uri_path`)
74
+ def load_server(config_file = 'default_config')
75
+ ENV['CONFIG_FILE'] = File.join(File.dirname(__FILE__),'config',"#{config_file}.yml")
76
+
77
+ silence_warnings do
78
+ load File.dirname(__FILE__) + '/../lib/casserver/server.rb'
79
+ end
80
+
81
+ # set test environment
82
+ CASServer::Server.set :environment, :test
83
+ CASServer::Server.set :run, false
84
+ CASServer::Server.set :raise_errors, true
85
+ CASServer::Server.set :logging, false
86
+
87
+ CASServer::Server.enable(:raise_errors)
88
+ CASServer::Server.disable(:show_exceptions)
89
+
90
+ #Capybara.current_driver = :selenium
91
+ Capybara.app = CASServer::Server
92
+
93
+ def app
94
+ CASServer::Server
95
+ end
96
+ end
97
+
98
+ # Deletes the sqlite3 database specified in the app's config
99
+ # and runs the db:migrate rake tasks to rebuild the database schema.
100
+ def reset_spec_database
101
+ raise "Cannot reset the spec database because config[:database][:database] is not defined." unless
102
+ CASServer::Server.config[:database] && CASServer::Server.config[:database][:database]
103
+
104
+ FileUtils.rm_f(CASServer::Server.config[:database][:database])
105
+
106
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
107
+ ActiveRecord::Base.logger.level = Logger::ERROR
108
+ ActiveRecord::Migration.verbose = false
109
+ ActiveRecord::Migrator.migrate("db/migrate")
110
+ end
111
+
112
+ def get_ticket_for(service, username = 'spec_user', password = 'spec_password')
113
+ visit "/login?service=#{CGI.escape(service)}"
114
+ fill_in 'username', :with => username
115
+ fill_in 'password', :with => password
116
+ click_button 'login-submit'
117
+
118
+ page.current_url.match(/ticket=(.*)$/)[1]
119
+ end
120
+ def gem_available?(name)
121
+ if Gem::Specification.methods.include?(:find_all_by_name)
122
+ not Gem::Specification.find_all_by_name(name).empty?
123
+ else
124
+ Gem.available?(name)
125
+ end
126
+ end
@@ -0,0 +1,4 @@
1
+ require 'bundler'
2
+ namespace :bundler do
3
+ Bundler::GemHelper.install_tasks(:name => 'rubycas-server')
4
+ end
@@ -0,0 +1,12 @@
1
+ namespace :db do
2
+ desc "bring your CAS server database schema up to date (options CONFIG=/path/to/config.yml)"
3
+ task :migrate do |t|
4
+ $:.unshift File.dirname(__FILE__) + "/../../lib"
5
+
6
+ require 'casserver/server'
7
+
8
+ CASServer::Model::Ticket.logger = Logger.new(STDOUT)
9
+ ActiveRecord::Migration.verbose = true
10
+ ActiveRecord::Migrator.migrate("db/migrate")
11
+ end
12
+ end
data/tasks/spec.rake ADDED
@@ -0,0 +1,10 @@
1
+ begin
2
+ require 'rspec/core/rake_task'
3
+ desc 'Run RSpecs to confirm that all functionality is working as expected'
4
+ RSpec::Core::RakeTask.new('spec') do |t|
5
+ t.rspec_opts = ['--colour', '--format nested']
6
+ t.pattern = 'spec/**/*_spec.rb'
7
+ end
8
+ rescue LoadError
9
+ puts "Hiding spec tasks because RSpec is not available"
10
+ end