synapses-cas 0.1.0

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 (81) hide show
  1. data/CHANGELOG +3 -0
  2. data/Gemfile +3 -0
  3. data/LICENSE +27 -0
  4. data/README.md +20 -0
  5. data/Rakefile +2 -0
  6. data/bin/synapses-cas +30 -0
  7. data/config.ru +17 -0
  8. data/config/config.example.yml +592 -0
  9. data/config/unicorn.rb +88 -0
  10. data/db/migrate/001_create_initial_structure.rb +47 -0
  11. data/lib/casserver.rb +11 -0
  12. data/lib/casserver/authenticators/active_directory_ldap.rb +19 -0
  13. data/lib/casserver/authenticators/active_resource.rb +127 -0
  14. data/lib/casserver/authenticators/authlogic_crypto_providers/aes256.rb +43 -0
  15. data/lib/casserver/authenticators/authlogic_crypto_providers/bcrypt.rb +92 -0
  16. data/lib/casserver/authenticators/authlogic_crypto_providers/md5.rb +34 -0
  17. data/lib/casserver/authenticators/authlogic_crypto_providers/sha1.rb +59 -0
  18. data/lib/casserver/authenticators/authlogic_crypto_providers/sha512.rb +50 -0
  19. data/lib/casserver/authenticators/base.rb +67 -0
  20. data/lib/casserver/authenticators/client_certificate.rb +47 -0
  21. data/lib/casserver/authenticators/google.rb +58 -0
  22. data/lib/casserver/authenticators/ldap.rb +147 -0
  23. data/lib/casserver/authenticators/ntlm.rb +88 -0
  24. data/lib/casserver/authenticators/open_id.rb +22 -0
  25. data/lib/casserver/authenticators/sql.rb +133 -0
  26. data/lib/casserver/authenticators/sql_authlogic.rb +93 -0
  27. data/lib/casserver/authenticators/sql_encrypted.rb +75 -0
  28. data/lib/casserver/authenticators/sql_md5.rb +19 -0
  29. data/lib/casserver/authenticators/sql_rest_auth.rb +82 -0
  30. data/lib/casserver/authenticators/test.rb +22 -0
  31. data/lib/casserver/cas.rb +323 -0
  32. data/lib/casserver/localization.rb +13 -0
  33. data/lib/casserver/model.rb +270 -0
  34. data/lib/casserver/server.rb +758 -0
  35. data/lib/casserver/utils.rb +32 -0
  36. data/lib/casserver/views/_login_form.erb +42 -0
  37. data/lib/casserver/views/layout.erb +18 -0
  38. data/lib/casserver/views/login.erb +30 -0
  39. data/lib/casserver/views/proxy.builder +12 -0
  40. data/lib/casserver/views/proxy_validate.builder +25 -0
  41. data/lib/casserver/views/service_validate.builder +18 -0
  42. data/lib/casserver/views/validate.erb +2 -0
  43. data/locales/de.yml +27 -0
  44. data/locales/en.yml +26 -0
  45. data/locales/es.yml +26 -0
  46. data/locales/es_ar.yml +26 -0
  47. data/locales/fr.yml +26 -0
  48. data/locales/jp.yml +26 -0
  49. data/locales/pl.yml +26 -0
  50. data/locales/pt.yml +26 -0
  51. data/locales/ru.yml +26 -0
  52. data/locales/zh.yml +26 -0
  53. data/locales/zh_tw.yml +26 -0
  54. data/public/themes/cas.css +126 -0
  55. data/public/themes/notice.png +0 -0
  56. data/public/themes/ok.png +0 -0
  57. data/public/themes/simple/bg.png +0 -0
  58. data/public/themes/simple/favicon.png +0 -0
  59. data/public/themes/simple/login_box_bg.png +0 -0
  60. data/public/themes/simple/logo.png +0 -0
  61. data/public/themes/simple/theme.css +28 -0
  62. data/public/themes/urbacon/bg.png +0 -0
  63. data/public/themes/urbacon/login_box_bg.png +0 -0
  64. data/public/themes/urbacon/logo.png +0 -0
  65. data/public/themes/urbacon/theme.css +33 -0
  66. data/public/themes/warning.png +0 -0
  67. data/resources/init.d.sh +58 -0
  68. data/setup.rb +1585 -0
  69. data/spec/alt_config.yml +50 -0
  70. data/spec/authenticators/active_resource_spec.rb +109 -0
  71. data/spec/authenticators/ldap_spec.rb +53 -0
  72. data/spec/casserver_spec.rb +156 -0
  73. data/spec/default_config.yml +50 -0
  74. data/spec/model_spec.rb +42 -0
  75. data/spec/spec.opts +4 -0
  76. data/spec/spec_helper.rb +89 -0
  77. data/spec/utils_spec.rb +53 -0
  78. data/tasks/bundler.rake +4 -0
  79. data/tasks/db/migrate.rake +12 -0
  80. data/tasks/spec.rake +10 -0
  81. metadata +380 -0
@@ -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,109 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ require 'casserver/authenticators/active_resource'
5
+
6
+ describe CASServer::Authenticators::Helpers::Identity do
7
+
8
+ it { should be_an ActiveResource::Base }
9
+
10
+ it "class should respond to :authenticate" do
11
+ subject.class.should respond_to :authenticate
12
+ end
13
+
14
+ it "class should have a method_name accessor" do
15
+ CASServer::Authenticators::Helpers::Identity.method_name.should == :authenticate
16
+ end
17
+
18
+ it "class should have a method_name accessor" do
19
+ CASServer::Authenticators::Helpers::Identity.method_type.should == :post
20
+ end
21
+
22
+ it "class method_type accessor should validate type" do
23
+ expect {
24
+ CASServer::Authenticators::Helpers::Identity.method_type = :foo
25
+ }.to raise_error(ArgumentError)
26
+ end
27
+
28
+ end
29
+
30
+ describe CASServer::Authenticators::ActiveResource do
31
+
32
+ describe "#setup" do
33
+
34
+ it "should configure the identity object" do
35
+ CASServer::Authenticators::Helpers::Identity.should_receive(:user=).with('httpuser').once
36
+ CASServer::Authenticators::ActiveResource.setup :site => 'http://api.example.org', :user => 'httpuser'
37
+ end
38
+
39
+ it "should configure the method_type" do
40
+ CASServer::Authenticators::Helpers::Identity.should_receive(:method_type=).with('get').once
41
+ CASServer::Authenticators::ActiveResource.setup :site => 'http://api.example.org', :method_type => 'get'
42
+ end
43
+
44
+ it "should raise if site option is missing" do
45
+ expect {
46
+ CASServer::Authenticators::ActiveResource.setup({}).should
47
+ }.to raise_error(CASServer::AuthenticatorError, /site option/)
48
+ end
49
+ end
50
+
51
+ describe "#validate" do
52
+
53
+ let(:credentials) { {:username => 'validusername',
54
+ :password => 'validpassword',
55
+ :service => 'test.service'} }
56
+
57
+ let(:auth) { CASServer::Authenticators::ActiveResource.new }
58
+
59
+ def mock_authenticate identity = nil
60
+ identity = CASServer::Authenticators::Helpers::Identity.new if identity.nil?
61
+ CASServer::Authenticators::Helpers::Identity.stub!(:authenticate).and_return(identity)
62
+ end
63
+
64
+ def sample_identity attrs = {}
65
+ identity = CASServer::Authenticators::Helpers::Identity.new
66
+ attrs.each { |k,v| identity.send "#{k}=", v }
67
+ identity
68
+ end
69
+
70
+ it "should call Identity#autenticate with the given params" do
71
+ CASServer::Authenticators::Helpers::Identity.should_receive(:authenticate).with(credentials).once
72
+ auth.validate(credentials)
73
+ end
74
+
75
+ it "should return identity object attributes as extra attributes" do
76
+ auth.configure({}.with_indifferent_access)
77
+ identity = sample_identity({:email => 'foo@example.org'})
78
+ mock_authenticate identity
79
+ auth.validate(credentials).should be_true
80
+ auth.extra_attributes.should == identity.attributes
81
+ end
82
+
83
+ it "should return false when http raises" do
84
+ CASServer::Authenticators::Helpers::Identity.stub!(:authenticate).and_raise(ActiveResource::ForbiddenAccess.new({}))
85
+ auth.validate(credentials).should be_false
86
+ end
87
+
88
+ it "should apply extra_attribute filter" do
89
+ auth.configure({ :extra_attributes => 'age'}.with_indifferent_access)
90
+ mock_authenticate sample_identity({ :email => 'foo@example.org', :age => 28 })
91
+ auth.validate(credentials).should be_true
92
+ auth.extra_attributes.should == { "age" => "28" }
93
+ end
94
+
95
+ it "should only extract not filtered attributes" do
96
+ auth.configure({ :filter_attributes => 'age'}.with_indifferent_access)
97
+ mock_authenticate sample_identity({ :email => 'foo@example.org', :age => 28 })
98
+ auth.validate(credentials).should be_true
99
+ auth.extra_attributes.should == { "email" => 'foo@example.org' }
100
+ end
101
+
102
+ it "should filter password if filter attributes is not given" do
103
+ auth.configure({}.with_indifferent_access)
104
+ mock_authenticate sample_identity({ :email => 'foo@example.org', :password => 'secret' })
105
+ auth.validate(credentials).should be_true
106
+ auth.extra_attributes.should == { "email" => 'foo@example.org' }
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,53 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + '/../spec_helper'
3
+
4
+ require 'casserver/authenticators/ldap'
5
+
6
+ describe CASServer::Authenticators::LDAP do
7
+ before do
8
+ @ldap_entry = mock(Net::LDAP::Entry.new)
9
+ @ldap_entry.stub!(:[]).and_return("Test")
10
+
11
+ @ldap = mock(Net::LDAP)
12
+ @ldap.stub!(:host=)
13
+ @ldap.stub!(:port=)
14
+ @ldap.stub!(:encryption)
15
+ @ldap.stub!(:bind_as).and_return(true)
16
+ @ldap.stub!(:authenticate).and_return(true)
17
+ @ldap.stub!(:search).and_return([@ldap_entry])
18
+
19
+ Net::LDAP.stub!(:new).and_return(@ldap)
20
+ end
21
+
22
+ describe '#validate' do
23
+
24
+ it 'validate with preauthentication and with extra attributes' do
25
+ auth = CASServer::Authenticators::LDAP.new
26
+
27
+ auth_config = HashWithIndifferentAccess.new(
28
+ :ldap => {
29
+ :host => "ad.example.net",
30
+ :port => 389,
31
+ :base => "dc=example,dc=net",
32
+ :filter => "(objectClass=person)",
33
+ :auth_user => "authenticator",
34
+ :auth_password => "itsasecret"
35
+ },
36
+ :extra_attributes => [:full_name, :address]
37
+ )
38
+
39
+ auth.configure(auth_config.merge('auth_index' => 0))
40
+ auth.validate(
41
+ :username => 'validusername',
42
+ :password => 'validpassword',
43
+ :service => 'test.service',
44
+ :request => {}
45
+ ).should == true
46
+
47
+ auth.extra_attributes.should == {:full_name => 'Test', :address => 'Test'}
48
+ end
49
+
50
+ end
51
+ end
52
+
53
+
@@ -0,0 +1,156 @@
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
+
18
+ before do
19
+ @target_service = 'http://my.app.test'
20
+ end
21
+
22
+ describe "/login" do
23
+ before do
24
+ load_server(File.dirname(__FILE__) + "/default_config.yml")
25
+ reset_spec_database
26
+ end
27
+
28
+ it "logs in successfully with valid username and password without a target service" do
29
+ visit "/login"
30
+
31
+ fill_in 'username', :with => VALID_USERNAME
32
+ fill_in 'password', :with => VALID_PASSWORD
33
+ click_button 'login-submit'
34
+
35
+ page.should have_content("You have successfully logged in")
36
+ end
37
+
38
+ it "fails to log in with invalid password" do
39
+ visit "/login"
40
+ fill_in 'username', :with => VALID_USERNAME
41
+ fill_in 'password', :with => INVALID_PASSWORD
42
+ click_button 'login-submit'
43
+
44
+ page.should have_content("Incorrect username or password")
45
+ end
46
+
47
+ it "logs in successfully with valid username and password and redirects to target service" do
48
+ visit "/login?service="+CGI.escape(@target_service)
49
+
50
+ fill_in 'username', :with => VALID_USERNAME
51
+ fill_in 'password', :with => VALID_PASSWORD
52
+
53
+ click_button 'login-submit'
54
+
55
+ page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/
56
+ end
57
+
58
+ it "preserves target service after invalid login" do
59
+ visit "/login?service="+CGI.escape(@target_service)
60
+
61
+ fill_in 'username', :with => VALID_USERNAME
62
+ fill_in 'password', :with => INVALID_PASSWORD
63
+ click_button 'login-submit'
64
+
65
+ page.should have_content("Incorrect username or password")
66
+ page.should have_xpath('//input[@id="service"]', :value => @target_service)
67
+ end
68
+
69
+ it "uses appropriate localization based on Accept-Language header" do
70
+
71
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'pl'}
72
+ #visit "/login?lang=pl"
73
+ visit "/login"
74
+ page.should have_content("Użytkownik")
75
+
76
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'pt_BR'}
77
+ #visit "/login?lang=pt_BR"
78
+ visit "/login"
79
+ page.should have_content("Usuário")
80
+
81
+ page.driver.options[:headers] = {'HTTP_ACCEPT_LANGUAGE' => 'en'}
82
+ #visit "/login?lang=en"
83
+ visit "/login"
84
+ page.should have_content("Username")
85
+ end
86
+
87
+ it "is not vunerable to Cross Site Scripting" do
88
+ visit '/login?service=%22%2F%3E%3cscript%3ealert%2832%29%3c%2fscript%3e'
89
+ page.should_not have_content("alert(32)")
90
+ page.should_not have_xpath("//script")
91
+ #page.should have_xpath("<script>alert(32)</script>")
92
+ end
93
+
94
+ end # describe '/login'
95
+
96
+
97
+ describe '/logout' do
98
+
99
+ before do
100
+ load_server(File.dirname(__FILE__) + "/default_config.yml")
101
+ reset_spec_database
102
+ end
103
+
104
+ it "logs out successfully" do
105
+ visit "/logout"
106
+
107
+ page.should have_content("You have successfully logged out")
108
+ end
109
+
110
+ it "logs out successfully and redirects to target service" do
111
+ visit "/logout?gateway=true&service="+CGI.escape(@target_service)
112
+
113
+ page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?/
114
+ end
115
+
116
+ end # describe '/logout'
117
+
118
+ describe 'Configuration' do
119
+ it "uri_path value changes prefix of routes" do
120
+ load_server(File.dirname(__FILE__) + "/alt_config.yml")
121
+ @target_service = 'http://my.app.test'
122
+
123
+ visit "/test/login"
124
+ page.status_code.should_not == 404
125
+
126
+ visit "/test/logout"
127
+ page.status_code.should_not == 404
128
+ end
129
+ end
130
+
131
+ describe "proxyValidate" do
132
+ before do
133
+ load_server(File.dirname(__FILE__) + "/default_config.yml")
134
+ reset_spec_database
135
+
136
+ visit "/login?service="+CGI.escape(@target_service)
137
+
138
+ fill_in 'username', :with => VALID_USERNAME
139
+ fill_in 'password', :with => VALID_PASSWORD
140
+
141
+ click_button 'login-submit'
142
+
143
+ page.current_url.should =~ /^#{Regexp.escape(@target_service)}\/?\?ticket=ST\-[1-9rA-Z]+/
144
+ @ticket = page.current_url.match(/ticket=(.*)$/)[1]
145
+ end
146
+
147
+ it "should have extra attributes in proper format" do
148
+ visit "/serviceValidate?service=#{CGI.escape(@target_service)}&ticket=#{@ticket}"
149
+
150
+ encoded_utf_string = "&#1070;&#1090;&#1092;" # actual string is "Ютф"
151
+ page.body.should match("<test_utf_string>#{encoded_utf_string}</test_utf_string>")
152
+ page.body.should match("<test_numeric>123.45</test_numeric>")
153
+ page.body.should match("<test_utf_string>&#1070;&#1090;&#1092;</test_utf_string>")
154
+ end
155
+ end
156
+ end
@@ -0,0 +1,50 @@
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
+ #downcase_username: true
@@ -0,0 +1,42 @@
1
+ # encoding: UTF-8
2
+ require File.dirname(__FILE__) + '/spec_helper'
3
+
4
+ module CASServer
5
+ end
6
+ require 'casserver/model'
7
+
8
+ describe CASServer::Model::LoginTicket, '.cleanup(max_lifetime, max_unconsumed_lifetime)' do
9
+ let(:max_lifetime) { -1 }
10
+ let(:max_unconsumed_lifetime) { -2 }
11
+
12
+ before do
13
+ load_server(File.dirname(__FILE__) + "/default_config.yml")
14
+ reset_spec_database
15
+
16
+ CASServer::Model::LoginTicket.create :ticket => 'test', :client_hostname => 'test.local'
17
+ end
18
+
19
+ it 'should destroy all tickets created before the max lifetime' do
20
+ expect {
21
+ CASServer::Model::LoginTicket.cleanup(max_lifetime, max_unconsumed_lifetime)
22
+ }.to change(CASServer::Model::LoginTicket, :count).by(-1)
23
+ end
24
+
25
+ it 'should destroy all unconsumed tickets not exceeding the max lifetime' do
26
+ expect {
27
+ CASServer::Model::LoginTicket.cleanup(max_lifetime, max_unconsumed_lifetime)
28
+ }.to change(CASServer::Model::LoginTicket, :count).by(-1)
29
+ end
30
+ end
31
+
32
+ describe CASServer::Model::LoginTicket, '#to_s' do
33
+ let(:ticket) { 'test' }
34
+
35
+ before do
36
+ @login_ticket = CASServer::Model::LoginTicket.new :ticket => ticket
37
+ end
38
+
39
+ it 'should delegate #to_s to #ticket' do
40
+ @login_ticket.to_s.should == ticket
41
+ end
42
+ end