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
data/config/unicorn.rb ADDED
@@ -0,0 +1,88 @@
1
+ # Sample configuration file for Unicorn (not Rack)
2
+ #
3
+ # See http://unicorn.bogomips.org/Unicorn/Configurator.html for complete
4
+ # documentation.
5
+ SINATRA_ROOT = `pwd`.strip
6
+
7
+ # Use at least one worker per core if you're on a dedicated server,
8
+ # more will usually help for _short_ waits on databases/caches.
9
+ worker_processes 3
10
+
11
+ # Help ensure your application will always spawn in the symlinked
12
+ # "current" directory that Capistrano sets up.
13
+ working_directory SINATRA_ROOT # available in 0.94.0+
14
+
15
+ # listen on both a Unix domain socket and a TCP port,
16
+ # we use a shorter backlog for quicker failover when busy
17
+ # listen "/tmp/.sock", :backlog => 64
18
+ listen 18889, :tcp_nopush => true
19
+
20
+ # nuke workers after 30 seconds instead of 60 seconds (the default)
21
+ timeout 30
22
+
23
+ # feel free to point this anywhere accessible on the filesystem
24
+
25
+ pid "#{SINATRA_ROOT}/tmp/pids/unicorn.pid"
26
+
27
+ # relative_path "/test_platform"
28
+ # some applications/frameworks log to stderr or stdout, so prevent
29
+ # them from going to /dev/null when daemonized here:
30
+ stderr_path "#{SINATRA_ROOT}/log/unicorn.stderr.log"
31
+ stdout_path "#{SINATRA_ROOT}/log/unicorn.stdout.log"
32
+
33
+ # combine REE with "preload_app true" for memory savings
34
+ # http://rubyenterpriseedition.com/faq.html#adapt_apps_for_cow
35
+ preload_app false
36
+ GC.respond_to?(:copy_on_write_friendly=) and
37
+ GC.copy_on_write_friendly = true
38
+
39
+ before_fork do |server, worker|
40
+ # the following is highly recomended for Rails + "preload_app true"
41
+ # as there's no need for the master process to hold a connection
42
+ # defined?(ActiveRecord::Base) and
43
+ # ActiveRecord::Base.connection.disconnect!
44
+
45
+ # The following is only recommended for memory/DB-constrained
46
+ # installations. It is not needed if your system can house
47
+ # twice as many worker_processes as you have configured.
48
+ #
49
+ # # This allows a new master process to incrementally
50
+ # # phase out the old master process with SIGTTOU to avoid a
51
+ # # thundering herd (especially in the "preload_app false" case)
52
+ # # when doing a transparent upgrade. The last worker spawned
53
+ # # will then kill off the old master process with a SIGQUIT.
54
+ old_pid = "#{server.config[:pid]}.oldbin"
55
+
56
+ puts 'pid:'
57
+ puts '-------------------'
58
+ puts server.pid
59
+ puts old_pid
60
+ puts '---------------------'
61
+
62
+ if old_pid != server.pid
63
+ begin
64
+ sig = (worker.nr + 1) >= server.worker_processes ? :QUIT : :TTOU
65
+ Process.kill(sig, File.read(old_pid).to_i)
66
+ rescue Errno::ENOENT, Errno::ESRCH
67
+ end
68
+ end
69
+ #
70
+ # # *optionally* throttle the master from forking too quickly by sleeping
71
+ sleep 1
72
+ end
73
+
74
+ after_fork do |server, worker|
75
+ # per-process listener ports for debugging/admin/migrations
76
+ # addr = "127.0.0.1:#{9293 + worker.nr}"
77
+ # server.listen(addr, :tries => -1, :delay => 5, :tcp_nopush => true)
78
+
79
+ # the following is *required* for Rails + "preload_app true",
80
+ # defined?(ActiveRecord::Base) and
81
+ # ActiveRecord::Base.establish_connection
82
+
83
+ # if preload_app is true, then you may also want to check and
84
+ # restart any other shared sockets/descriptors such as Memcached,
85
+ # and Redis. TokyoCabinet file handles are safe to reuse
86
+ # between any number of forked children (assuming your kernel
87
+ # correctly implements pread()/pwrite() system calls)
88
+ end
@@ -0,0 +1,47 @@
1
+ class CreateInitialStructure < ActiveRecord::Migration
2
+ def self.up
3
+ # Oracle table names cannot exceed 30 chars...
4
+ # See http://code.google.com/p/rubycas-server/issues/detail?id=15
5
+ create_table 'casserver_lt', :force => true do |t|
6
+ t.string 'ticket', :null => false
7
+ t.timestamp 'created_on', :null => false
8
+ t.datetime 'consumed', :null => true
9
+ t.string 'client_hostname', :null => false
10
+ end
11
+
12
+ create_table 'casserver_st', :force => true do |t|
13
+ t.string 'ticket', :null => false
14
+ t.text 'service', :null => false
15
+ t.timestamp 'created_on', :null => false
16
+ t.datetime 'consumed', :null => true
17
+ t.string 'client_hostname', :null => false
18
+ t.string 'username', :null => false
19
+ t.string 'type', :null => false
20
+ t.integer 'granted_by_pgt_id', :null => true
21
+ t.integer 'granted_by_tgt_id', :null => true
22
+ end
23
+
24
+ create_table 'casserver_tgt', :force => true do |t|
25
+ t.string 'ticket', :null => false
26
+ t.timestamp 'created_on', :null => false
27
+ t.string 'client_hostname', :null => false
28
+ t.string 'username', :null => false
29
+ t.text 'extra_attributes', :null => true
30
+ end
31
+
32
+ create_table 'casserver_pgt', :force => true do |t|
33
+ t.string 'ticket', :null => false
34
+ t.timestamp 'created_on', :null => false
35
+ t.string 'client_hostname', :null => false
36
+ t.string 'iou', :null => false
37
+ t.integer 'service_ticket_id', :null => false
38
+ end
39
+ end # self.up
40
+
41
+ def self.down
42
+ drop_table 'casserver_pgt'
43
+ drop_table 'casserver_tgt'
44
+ drop_table 'casserver_st'
45
+ drop_table 'casserver_lt'
46
+ end # self.down
47
+ end
data/lib/casserver.rb ADDED
@@ -0,0 +1,11 @@
1
+ module CASServer; end
2
+
3
+ require 'active_record'
4
+ require 'active_support'
5
+ require 'sinatra/base'
6
+ require 'builder' # for XML views
7
+ require 'logger'
8
+ $LOG = Logger.new(STDOUT)
9
+
10
+ require 'casserver/server'
11
+
@@ -0,0 +1,19 @@
1
+ require 'casserver/authenticators/ldap'
2
+
3
+ # Slightly modified version of the LDAP authenticator for Microsoft's ActiveDirectory.
4
+ # The only difference is that the default_username_attribute for AD is 'sAMAccountName'
5
+ # rather than 'uid'.
6
+ class CASServer::Authenticators::ActiveDirectoryLDAP < CASServer::Authenticators::LDAP
7
+ protected
8
+ def default_username_attribute
9
+ "sAMAccountName"
10
+ end
11
+
12
+ def extract_extra_attributes(ldap_entry)
13
+ super(ldap_entry)
14
+ if @extra_attributes["objectGUID"]
15
+ @extra_attributes["guid"] = @extra_attributes["objectGUID"].to_s.unpack("H*").to_s
16
+ end
17
+ ldap_entry
18
+ end
19
+ end
@@ -0,0 +1,127 @@
1
+ require 'casserver/authenticators/base'
2
+
3
+ begin
4
+ require 'active_resource'
5
+ rescue LoadError
6
+ require 'rubygems'
7
+ begin
8
+ gem 'activeresource', '~> 3.0.0'
9
+ rescue Gem::LoadError
10
+ $stderr.puts
11
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
12
+ $stderr.puts
13
+ $stderr.puts "To use the ActiveResource authenticator, you must first install the 'activeresource' gem."
14
+ $stderr.puts
15
+ $stderr.puts "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"
16
+ exit 1
17
+ end
18
+ require 'active_resource'
19
+ end
20
+
21
+ module CASServer
22
+ module Authenticators
23
+
24
+ module Helpers
25
+ class Identity < ActiveResource::Base
26
+
27
+ # define method_name accessor
28
+ cattr_accessor(:method_name)
29
+ self.method_name = :authenticate
30
+
31
+ def self.method_type
32
+ @@method_type ||= :post
33
+ end
34
+
35
+ def self.method_type= type
36
+ methods = [:get, :post, :put, :delete]
37
+ raise ArgumentError, "Method type should be one of #{methods.map { |m| m.to_s.upcase }.join(', ')}" unless methods.include? type.to_sym
38
+ @@method_type = type
39
+ end
40
+
41
+ # Autenticate an identity using the given method
42
+ # @param [Hash] credentials
43
+ def self.authenticate(credentials = {})
44
+ response = send(method_type, method_name, credentials)
45
+ new.from_authentication_data(response)
46
+ end
47
+
48
+ # Used to load object attributes from the given response
49
+ def from_authentication_data response
50
+ load_attributes_from_response(response)
51
+ end
52
+ end
53
+ end
54
+
55
+ class ActiveResource < Base
56
+
57
+ # This is called at server startup.
58
+ # Any class-wide initializiation for the authenticator should be done here.
59
+ # (e.g. establish database connection).
60
+ # You can leave this empty if you don't need to set up anything.
61
+ def self.setup(options)
62
+ raise AuthenticatorError, 'You must define at least site option' unless options[:site]
63
+ # apply options to active resource object
64
+ options.each do |method, arg|
65
+ Helpers::Identity.send "#{method}=", arg if Helpers::Identity.respond_to? "#{method}="
66
+ end
67
+ $LOG.info "ActiveResource configuration loaded"
68
+ end
69
+
70
+ # Override this to implement your authentication credential validation.
71
+ # This is called each time the user tries to log in. The credentials hash
72
+ # holds the credentials as entered by the user (generally under :username
73
+ # and :password keys; :service and :request are also included by default)
74
+ #
75
+ # Note that the standard credentials can be read in to instance variables
76
+ # by calling #read_standard_credentials.
77
+ def validate(credentials)
78
+ begin
79
+ $LOG.debug("Starting Active Resource authentication")
80
+ result = Helpers::Identity.authenticate(credentials.except(:request))
81
+ extract_extra_attributes(result) if result
82
+ !!result
83
+ rescue ::ActiveResource::ConnectionError => e
84
+ if e.response.blank? # band-aid for ARes 2.3.x -- craps out if to_s is called without a response
85
+ e = e.class.to_s
86
+ end
87
+ $LOG.warn("Error during authentication: #{e}")
88
+ false
89
+ end
90
+ end
91
+
92
+ private
93
+
94
+ def extract_extra_attributes(resource)
95
+ @extra_attributes = {}
96
+ $LOG.debug("Parsing extra attributes")
97
+ if @options[:extra_attributes]
98
+ extra_attributes_to_extract.each do |attr|
99
+ @extra_attributes[attr] = resource.send(attr).to_s
100
+ end
101
+ else
102
+ @extra_attributes = resource.attributes
103
+ end
104
+ # do filtering
105
+ extra_attributes_to_filter.each do |attr|
106
+ @extra_attributes.delete(attr)
107
+ end
108
+ end
109
+
110
+ # extract attributes to filter from the given configuration
111
+ def extra_attributes_to_filter
112
+ # default value if not set
113
+ return ['password'] unless @options[:filter_attributes]
114
+ # parse option value
115
+ if @options[:filter_attributes].kind_of? Array
116
+ attrs = @options[:filter_attributes]
117
+ elsif @options[:filter_attributes].kind_of? String
118
+ attrs = @options[:filter_attributes].split(',').collect { |col| col.strip }
119
+ else
120
+ $LOG.error("Can't figure out attribute list from #{@options[:filter_attributes].inspect}. This must be an Array of column names or a comma-separated list.")
121
+ attrs = []
122
+ end
123
+ attrs
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,43 @@
1
+ require "openssl"
2
+
3
+ module Authlogic
4
+ module CryptoProviders
5
+ # This encryption method is reversible if you have the supplied key. So in order to use this encryption method you must supply it with a key first.
6
+ # In an initializer, or before your application initializes, you should do the following:
7
+ #
8
+ # Authlogic::CryptoProviders::AES256.key = "my really long and unique key, preferrably a bunch of random characters"
9
+ #
10
+ # My final comment is that this is a strong encryption method, but its main weakness is that its reversible. If you do not need to reverse the hash
11
+ # then you should consider Sha512 or BCrypt instead.
12
+ #
13
+ # Keep your key in a safe place, some even say the key should be stored on a separate server.
14
+ # This won't hurt performance because the only time it will try and access the key on the separate server is during initialization, which only
15
+ # happens once. The reasoning behind this is if someone does compromise your server they won't have the key also. Basically, you don't want to
16
+ # store the key with the lock.
17
+ class AES256
18
+ class << self
19
+ attr_writer :key
20
+
21
+ def encrypt(*tokens)
22
+ aes.encrypt
23
+ aes.key = @key
24
+ [aes.update(tokens.join) + aes.final].pack("m").chomp
25
+ end
26
+
27
+ def matches?(crypted, *tokens)
28
+ aes.decrypt
29
+ aes.key = @key
30
+ (aes.update(crypted.unpack("m").first) + aes.final) == tokens.join
31
+ rescue OpenSSL::CipherError
32
+ false
33
+ end
34
+
35
+ private
36
+ def aes
37
+ raise ArgumentError.new("You must provide a key like #{name}.key = my_key before using the #{name}") if @key.blank?
38
+ @aes ||= OpenSSL::Cipher::Cipher.new("AES-256-ECB")
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,92 @@
1
+ begin
2
+ require "bcrypt"
3
+ rescue LoadError
4
+ end
5
+
6
+ module Authlogic
7
+ module CryptoProviders
8
+ # For most apps Sha512 is plenty secure, but if you are building an app that stores nuclear launch codes you might want to consier BCrypt. This is an extremely
9
+ # secure hashing algorithm, mainly because it is slow. A brute force attack on a BCrypt encrypted password would take much longer than a brute force attack on a
10
+ # password encrypted with a Sha algorithm. Keep in mind you are sacrificing performance by using this, generating a password takes exponentially longer than any
11
+ # of the Sha algorithms. I did some benchmarking to save you some time with your decision:
12
+ #
13
+ # require "bcrypt"
14
+ # require "digest"
15
+ # require "benchmark"
16
+ #
17
+ # Benchmark.bm(18) do |x|
18
+ # x.report("BCrypt (cost = 10:") { 100.times { BCrypt::Password.create("mypass", :cost => 10) } }
19
+ # x.report("BCrypt (cost = 2:") { 100.times { BCrypt::Password.create("mypass", :cost => 2) } }
20
+ # x.report("Sha512:") { 100.times { Digest::SHA512.hexdigest("mypass") } }
21
+ # x.report("Sha1:") { 100.times { Digest::SHA1.hexdigest("mypass") } }
22
+ # end
23
+ #
24
+ # user system total real
25
+ # BCrypt (cost = 10): 10.780000 0.060000 10.840000 ( 11.100289)
26
+ # BCrypt (cost = 2): 0.180000 0.000000 0.180000 ( 0.181914)
27
+ # Sha512: 0.000000 0.000000 0.000000 ( 0.000829)
28
+ # Sha1: 0.000000 0.000000 0.000000 ( 0.000395)
29
+ #
30
+ # You can play around with the cost to get that perfect balance between performance and security.
31
+ #
32
+ # Decided BCrypt is for you? Just insall the bcrypt gem:
33
+ #
34
+ # gem install bcrypt-ruby
35
+ #
36
+ # Tell acts_as_authentic to use it:
37
+ #
38
+ # acts_as_authentic do |c|
39
+ # c.crypto_provider = Authlogic::CryptoProviders::BCrypt
40
+ # end
41
+ #
42
+ # You are good to go!
43
+ class BCrypt
44
+ class << self
45
+ # This is the :cost option for the BCrpyt library. The higher the cost the more secure it is and the longer is take the generate a hash. By default this is 10.
46
+ # Set this to whatever you want, play around with it to get that perfect balance between security and performance.
47
+ def cost
48
+ @cost ||= 10
49
+ end
50
+ attr_writer :cost
51
+
52
+ # Creates a BCrypt hash for the password passed.
53
+ def encrypt(*tokens)
54
+ ::BCrypt::Password.create(join_tokens(tokens), :cost => cost)
55
+ end
56
+
57
+ # Does the hash match the tokens? Uses the same tokens that were used to encrypt.
58
+ def matches?(hash, *tokens)
59
+ $LOG.debug hash
60
+ $LOG.debug tokens.inspect
61
+
62
+ hash = new_from_hash(hash)
63
+ return false if hash.blank?
64
+ hash == join_tokens(tokens)
65
+ end
66
+
67
+ # This method is used as a flag to tell Authlogic to "resave" the password upon a successful login, using the new cost
68
+ def cost_matches?(hash)
69
+ hash = new_from_hash(hash)
70
+ if hash.blank?
71
+ false
72
+ else
73
+ hash.cost == cost
74
+ end
75
+ end
76
+
77
+ private
78
+ def join_tokens(tokens)
79
+ tokens.flatten.join
80
+ end
81
+
82
+ def new_from_hash(hash)
83
+ begin
84
+ ::BCrypt::Password.new(hash)
85
+ rescue ::BCrypt::Errors::InvalidHash
86
+ return nil
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,34 @@
1
+ require "digest/md5"
2
+
3
+ module Authlogic
4
+ module CryptoProviders
5
+ # This class was made for the users transitioning from md5 based systems.
6
+ # I highly discourage using this crypto provider as it superbly inferior
7
+ # to your other options.
8
+ #
9
+ # Please use any other provider offered by Authlogic.
10
+ class MD5
11
+ class << self
12
+ attr_accessor :join_token
13
+
14
+ # The number of times to loop through the encryption.
15
+ def stretches
16
+ @stretches ||= 1
17
+ end
18
+ attr_writer :stretches
19
+
20
+ # Turns your raw password into a MD5 hash.
21
+ def encrypt(*tokens)
22
+ digest = tokens.flatten.join(join_token)
23
+ stretches.times { digest = Digest::MD5.hexdigest(digest) }
24
+ digest
25
+ end
26
+
27
+ # Does the crypted password match the tokens? Uses the same tokens that were used to encrypt.
28
+ def matches?(crypted, *tokens)
29
+ encrypt(*tokens) == crypted
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end