synapse-rubycas-server 1.1.3alpha

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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