casino 3.0.4 → 4.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (149) hide show
  1. checksums.yaml +7 -0
  2. data/.travis.yml +12 -3
  3. data/app/api/casino/api.rb +7 -0
  4. data/app/api/casino/api/entity/auth_token_ticket.rb +5 -0
  5. data/app/api/casino/api/resource/auth_token_tickets.rb +12 -0
  6. data/app/assets/javascripts/casino/{application.js → application.js.erb} +1 -1
  7. data/app/authenticators/casino/static_authenticator.rb +8 -2
  8. data/app/builders/casino/proxy_response_builder.rb +24 -0
  9. data/app/builders/casino/ticket_validation_response_builder.rb +9 -5
  10. data/app/controllers/casino/application_controller.rb +0 -22
  11. data/app/controllers/casino/auth_tokens_controller.rb +34 -0
  12. data/app/controllers/casino/controller_concern/ticket_validator.rb +30 -0
  13. data/app/controllers/casino/proxy_tickets_controller.rb +42 -2
  14. data/app/controllers/casino/service_tickets_controller.rb +15 -2
  15. data/app/controllers/casino/sessions_controller.rb +59 -8
  16. data/app/controllers/casino/two_factor_authenticators_controller.rb +28 -3
  17. data/app/helpers/casino/sessions_helper.rb +75 -0
  18. data/app/helpers/casino/two_factor_authenticators_helper.rb +12 -0
  19. data/app/models/casino/auth_token_ticket.rb +15 -0
  20. data/app/models/casino/login_ticket.rb +7 -4
  21. data/app/models/casino/model_concern/consumable_ticket.rb +20 -0
  22. data/app/models/casino/model_concern/ticket.rb +28 -0
  23. data/app/models/casino/proxy_granting_ticket.rb +12 -0
  24. data/app/models/casino/proxy_ticket.rb +4 -0
  25. data/app/models/casino/service_ticket.rb +5 -4
  26. data/app/models/casino/ticket_granting_ticket.rb +5 -1
  27. data/app/models/casino/two_factor_authenticator.rb +2 -0
  28. data/app/processors/casino/authentication_processor.rb +73 -0
  29. data/app/processors/casino/browser_processor.rb +12 -0
  30. data/app/processors/casino/proxy_granting_ticket_processor.rb +37 -0
  31. data/app/processors/casino/service_ticket_processor.rb +81 -0
  32. data/app/processors/casino/ticket_granting_ticket_processor.rb +56 -0
  33. data/app/processors/casino/two_factor_authenticator_processor.rb +18 -0
  34. data/app/services/casino/auth_token_validation_service.rb +66 -0
  35. data/app/views/casino/sessions/index.html.erb +2 -2
  36. data/app/views/casino/sessions/new.html.erb +1 -1
  37. data/app/views/casino/sessions/validate_otp.html.erb +1 -1
  38. data/app/views/casino/two_factor_authenticators/new.html.erb +6 -3
  39. data/app/views/layouts/application.html.erb +0 -1
  40. data/casino.gemspec +4 -2
  41. data/config/locales/en.yml +35 -0
  42. data/config/locales/zh-CN.yml +88 -0
  43. data/config/locales/zh-TW.yml +88 -0
  44. data/config/routes.rb +3 -10
  45. data/db/migrate/20140831205255_create_auth_token_tickets.rb +10 -0
  46. data/lib/casino.rb +4 -1
  47. data/lib/casino/tasks/cleanup.rake +13 -1
  48. data/lib/casino/version.rb +1 -1
  49. data/spec/controllers/auth_tokens_controller_spec.rb +75 -0
  50. data/spec/controllers/proxy_tickets_controller_spec.rb +120 -14
  51. data/spec/controllers/service_and_proxy_tickets_controller_spec.rb +224 -0
  52. data/spec/controllers/service_tickets_controller_spec.rb +62 -16
  53. data/spec/controllers/sessions_controller_spec.rb +622 -36
  54. data/spec/controllers/two_factor_authenticators_controller_spec.rb +217 -18
  55. data/spec/dummy/config/cas.yml +3 -0
  56. data/spec/dummy/config/environments/development.rb +0 -4
  57. data/spec/dummy/db/migrate/{20130910094259_create_base_models.casino.rb → 20140831214845_create_core_schema.casino.rb} +55 -32
  58. data/spec/dummy/db/migrate/20140831214846_rename_base_models.casino.rb +102 -0
  59. data/spec/dummy/db/migrate/20140831214847_cleanup_indexes.casino.rb +28 -0
  60. data/spec/dummy/db/migrate/20140831214848_fix_long_index_names.casino.rb +13 -0
  61. data/spec/dummy/db/migrate/20140831214849_change_service_to_text.casino.rb +7 -0
  62. data/spec/dummy/db/migrate/20140831214850_change_user_agent_to_text.casino.rb +6 -0
  63. data/spec/dummy/db/migrate/20140831214851_fix_length_of_text_fields.casino.rb +8 -0
  64. data/spec/dummy/db/migrate/20140831214852_create_auth_token_tickets.casino.rb +11 -0
  65. data/spec/dummy/db/schema.rb +79 -70
  66. data/spec/features/login_spec.rb +0 -9
  67. data/spec/model/auth_token_ticket_spec.rb +23 -0
  68. data/spec/services/auth_token_validation_service_spec.rb +83 -0
  69. data/spec/support/sign_in.rb +4 -0
  70. metadata +139 -210
  71. data/app/controllers/casino/api/v1/tickets_controller.rb +0 -55
  72. data/app/helpers/service_tickets_helper.rb +0 -2
  73. data/app/listeners/casino/legacy_validator_listener.rb +0 -11
  74. data/app/listeners/casino/listener.rb +0 -16
  75. data/app/listeners/casino/login_credential_acceptor_listener.rb +0 -38
  76. data/app/listeners/casino/login_credential_requestor_listener.rb +0 -21
  77. data/app/listeners/casino/logout_listener.rb +0 -12
  78. data/app/listeners/casino/other_sessions_destroyer_listener.rb +0 -7
  79. data/app/listeners/casino/proxy_ticket_provider_listener.rb +0 -11
  80. data/app/listeners/casino/second_factor_authentication_acceptor_listener.rb +0 -26
  81. data/app/listeners/casino/session_destroyer_listener.rb +0 -11
  82. data/app/listeners/casino/session_overview_listener.rb +0 -11
  83. data/app/listeners/casino/ticket_validator_listener.rb +0 -11
  84. data/app/listeners/casino/two_factor_authenticator_activator_listener.rb +0 -23
  85. data/app/listeners/casino/two_factor_authenticator_destroyer_listener.rb +0 -16
  86. data/app/listeners/casino/two_factor_authenticator_overview_listener.rb +0 -11
  87. data/app/listeners/casino/two_factor_authenticator_registrator_listener.rb +0 -11
  88. data/app/processors/casino/api/login_credential_acceptor_processor.rb +0 -46
  89. data/app/processors/casino/api/logout_processor.rb +0 -17
  90. data/app/processors/casino/api/service_ticket_provider_processor.rb +0 -69
  91. data/app/processors/casino/legacy_validator_processor.rb +0 -19
  92. data/app/processors/casino/login_credential_acceptor_processor.rb +0 -63
  93. data/app/processors/casino/login_credential_requestor_processor.rb +0 -70
  94. data/app/processors/casino/logout_processor.rb +0 -23
  95. data/app/processors/casino/other_sessions_destroyer_processor.rb +0 -26
  96. data/app/processors/casino/processor.rb +0 -5
  97. data/app/processors/casino/processor_concern/authentication.rb +0 -87
  98. data/app/processors/casino/processor_concern/browser.rb +0 -14
  99. data/app/processors/casino/processor_concern/login_tickets.rb +0 -28
  100. data/app/processors/casino/processor_concern/proxy_granting_tickets.rb +0 -43
  101. data/app/processors/casino/processor_concern/proxy_tickets.rb +0 -56
  102. data/app/processors/casino/processor_concern/service_tickets.rb +0 -50
  103. data/app/processors/casino/processor_concern/ticket_granting_tickets.rb +0 -65
  104. data/app/processors/casino/processor_concern/tickets.rb +0 -17
  105. data/app/processors/casino/processor_concern/two_factor_authenticators.rb +0 -23
  106. data/app/processors/casino/proxy_ticket_provider_processor.rb +0 -41
  107. data/app/processors/casino/proxy_ticket_validator_processor.rb +0 -22
  108. data/app/processors/casino/second_factor_authentication_acceptor_processor.rb +0 -45
  109. data/app/processors/casino/service_ticket_validator_processor.rb +0 -46
  110. data/app/processors/casino/session_destroyer_processor.rb +0 -25
  111. data/app/processors/casino/session_overview_processor.rb +0 -21
  112. data/app/processors/casino/two_factor_authenticator_activator_processor.rb +0 -41
  113. data/app/processors/casino/two_factor_authenticator_destroyer_processor.rb +0 -33
  114. data/app/processors/casino/two_factor_authenticator_overview_processor.rb +0 -20
  115. data/app/processors/casino/two_factor_authenticator_registrator_processor.rb +0 -24
  116. data/spec/controllers/api/v1/tickets_controller_spec.rb +0 -114
  117. data/spec/controllers/listener/legacy_validator_spec.rb +0 -22
  118. data/spec/controllers/listener/login_credential_acceptor_spec.rb +0 -108
  119. data/spec/controllers/listener/login_credential_requestor_spec.rb +0 -57
  120. data/spec/controllers/listener/logout_spec.rb +0 -38
  121. data/spec/controllers/listener/other_sessions_destroyer_spec.rb +0 -19
  122. data/spec/controllers/listener/proxy_ticket_provider_spec.rb +0 -22
  123. data/spec/controllers/listener/second_factor_authentication_acceptor_spec.rb +0 -74
  124. data/spec/controllers/listener/session_destroyer_spec.rb +0 -25
  125. data/spec/controllers/listener/session_overview_spec.rb +0 -26
  126. data/spec/controllers/listener/ticket_validator_spec.rb +0 -22
  127. data/spec/controllers/listener/two_factor_authenticator_activator_spec.rb +0 -64
  128. data/spec/controllers/listener/two_factor_authenticator_destroyer_spec.rb +0 -40
  129. data/spec/controllers/listener/two_factor_authenticator_overview_spec.rb +0 -16
  130. data/spec/controllers/listener/two_factor_authenticator_registrator_spec.rb +0 -27
  131. data/spec/processor/api/login_credential_acceptor_spec.rb +0 -52
  132. data/spec/processor/api/logout_spec.rb +0 -34
  133. data/spec/processor/api/service_ticket_provider_spec.rb +0 -61
  134. data/spec/processor/legacy_validator_spec.rb +0 -78
  135. data/spec/processor/login_credential_acceptor_spec.rb +0 -164
  136. data/spec/processor/login_credential_requestor_spec.rb +0 -145
  137. data/spec/processor/logout_other_sessions_spec.rb +0 -53
  138. data/spec/processor/logout_spec.rb +0 -72
  139. data/spec/processor/processor_concern/service_tickets_spec.rb +0 -49
  140. data/spec/processor/proxy_ticket_provider_spec.rb +0 -66
  141. data/spec/processor/proxy_ticket_validator_spec.rb +0 -65
  142. data/spec/processor/second_factor_authenticaton_acceptor_spec.rb +0 -94
  143. data/spec/processor/session_destroyer_spec.rb +0 -75
  144. data/spec/processor/session_overview_spec.rb +0 -49
  145. data/spec/processor/ticket_validator_spec.rb +0 -214
  146. data/spec/processor/two_factor_authenticator_activator_spec.rb +0 -122
  147. data/spec/processor/two_factor_authenticator_destroyer_spec.rb +0 -71
  148. data/spec/processor/two_factor_authenticator_overview_spec.rb +0 -56
  149. data/spec/processor/two_factor_authenticator_registrator_spec.rb +0 -48
@@ -1,5 +1,80 @@
1
+ require 'addressable/uri'
2
+
1
3
  module CASino::SessionsHelper
4
+ include CASino::TicketGrantingTicketProcessor
5
+ include CASino::ServiceTicketProcessor
6
+
2
7
  def current_ticket_granting_ticket?(ticket_granting_ticket)
3
8
  ticket_granting_ticket.ticket == cookies[:tgt]
4
9
  end
10
+
11
+ def current_ticket_granting_ticket
12
+ return nil unless cookies[:tgt]
13
+ return @current_ticket_granting_ticket unless @current_ticket_granting_ticket.nil?
14
+ find_valid_ticket_granting_ticket(cookies[:tgt], request.user_agent).tap do |tgt|
15
+ cookies.delete :tgt if tgt.nil?
16
+ @current_ticket_granting_ticket = tgt
17
+ end
18
+ end
19
+
20
+ def current_user
21
+ tgt = current_ticket_granting_ticket
22
+ return nil if tgt.nil?
23
+ tgt.user
24
+ end
25
+
26
+ def ensure_signed_in
27
+ redirect_to login_path unless signed_in?
28
+ end
29
+
30
+ def signed_in?
31
+ !current_ticket_granting_ticket.nil?
32
+ end
33
+
34
+ def sign_in(authentication_result, options = {})
35
+ tgt = acquire_ticket_granting_ticket(authentication_result, request.user_agent, options)
36
+ set_tgt_cookie(tgt)
37
+ handle_signed_in(tgt, options)
38
+ end
39
+
40
+ def set_tgt_cookie(tgt)
41
+ cookies[:tgt] = { value: tgt.ticket }.tap do |cookie|
42
+ if tgt.long_term?
43
+ cookie[:expires] = CASino.config.ticket_granting_ticket[:lifetime_long_term].seconds.from_now
44
+ end
45
+ end
46
+ end
47
+
48
+ def sign_out
49
+ remove_ticket_granting_ticket(cookies[:tgt], request.user_agent)
50
+ cookies.delete :tgt
51
+ end
52
+
53
+ private
54
+ def handle_signed_in(tgt, options = {})
55
+ if tgt.awaiting_two_factor_authentication?
56
+ @ticket_granting_ticket = tgt
57
+ render 'casino/sessions/validate_otp'
58
+ else
59
+ if params[:service].present?
60
+ begin
61
+ handle_signed_in_with_service(tgt, options)
62
+ return
63
+ rescue Addressable::URI::InvalidURIError => e
64
+ Rails.logger.warn "Service #{params[:service]} not valid: #{e}"
65
+ end
66
+ end
67
+ redirect_to sessions_path, status: :see_other
68
+ end
69
+ end
70
+
71
+ def handle_signed_in_with_service(tgt, options)
72
+ if !service_allowed?(params[:service])
73
+ @service = params[:service]
74
+ render 'casino/sessions/service_not_allowed', status: 403
75
+ else
76
+ url = acquire_service_ticket(tgt, params[:service], options).service_with_ticket_url
77
+ redirect_to url, status: :see_other
78
+ end
79
+ end
5
80
  end
@@ -0,0 +1,12 @@
1
+ require 'rqrcode_png'
2
+
3
+ module CASino::TwoFactorAuthenticatorsHelper
4
+ def otp_auth_url(two_factor_authenticator)
5
+ "otpauth://totp/#{u CASino.config.frontend[:sso_name] + ': ' + two_factor_authenticator.user.username}?secret=#{two_factor_authenticator.secret}&issuer=#{u CASino.config.frontend[:sso_name]}"
6
+ end
7
+
8
+ def otp_qr_code_data_url(two_factor_authenticator)
9
+ qr = RQRCode::QRCode.new(otp_auth_url(two_factor_authenticator), size: 5, level: :l)
10
+ qr.to_img.resize(250,250).to_data_url
11
+ end
12
+ end
@@ -0,0 +1,15 @@
1
+ class CASino::AuthTokenTicket < ActiveRecord::Base
2
+ include CASino::ModelConcern::Ticket
3
+ include CASino::ModelConcern::ConsumableTicket
4
+
5
+ self.ticket_prefix = 'ATT'.freeze
6
+
7
+ def self.cleanup
8
+ delete_all(['created_at < ?', CASino.config.auth_token_ticket[:lifetime].seconds.ago])
9
+ end
10
+
11
+ def expired?
12
+ (Time.now - (self.created_at || Time.now)) > CASino.config.auth_token_ticket[:lifetime].seconds
13
+ end
14
+
15
+ end
@@ -1,11 +1,14 @@
1
1
  class CASino::LoginTicket < ActiveRecord::Base
2
- validates :ticket, uniqueness: true
2
+ include CASino::ModelConcern::Ticket
3
+ include CASino::ModelConcern::ConsumableTicket
4
+
5
+ self.ticket_prefix = 'LT'.freeze
3
6
 
4
7
  def self.cleanup
5
- self.delete_all(['created_at < ?', CASino.config.login_ticket[:lifetime].seconds.ago])
8
+ delete_all(['created_at < ?', CASino.config.login_ticket[:lifetime].seconds.ago])
6
9
  end
7
10
 
8
- def to_s
9
- self.ticket
11
+ def expired?
12
+ (Time.now - (self.created_at || Time.now)) > CASino.config.login_ticket[:lifetime].seconds
10
13
  end
11
14
  end
@@ -0,0 +1,20 @@
1
+ module CASino::ModelConcern::ConsumableTicket
2
+ extend ActiveSupport::Concern
3
+
4
+ module ClassMethods
5
+ def consume(ticket_identifier)
6
+ ticket = find_by_ticket(ticket_identifier)
7
+ if ticket.nil?
8
+ Rails.logger.info "#{model_name.human} '#{ticket_identifier}' not found"
9
+ false
10
+ elsif ticket.expired?
11
+ Rails.logger.info "#{model_name.human} '#{ticket.ticket}' expired"
12
+ false
13
+ else
14
+ Rails.logger.debug "#{model_name.human} '#{ticket.ticket}' successfully validated"
15
+ ticket.delete
16
+ true
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,28 @@
1
+ module CASino::ModelConcern::Ticket
2
+ extend ActiveSupport::Concern
3
+
4
+ included do
5
+ before_validation :ensure_ticket_present
6
+ validates :ticket, uniqueness: true
7
+ class_attribute :ticket_prefix
8
+ end
9
+
10
+ def to_s
11
+ ticket
12
+ end
13
+
14
+ private
15
+ TICKET_ALLOWED_CHARACTERS = ('A'..'Z').to_a + ('a'..'z').to_a + ('0'..'9').to_a
16
+ TICKET_LENGTH = 40
17
+
18
+ def ensure_ticket_present
19
+ self.ticket ||= create_random_ticket_string(self.class.ticket_prefix)
20
+ end
21
+
22
+ def create_random_ticket_string(prefix)
23
+ random_string = SecureRandom.random_bytes(TICKET_LENGTH).each_char.map do |char|
24
+ TICKET_ALLOWED_CHARACTERS[(char.ord % TICKET_ALLOWED_CHARACTERS.length)]
25
+ end.join
26
+ "#{prefix}-#{'%d' % (Time.now.to_f * 10000)}-#{random_string}"
27
+ end
28
+ end
@@ -1,7 +1,19 @@
1
1
 
2
2
  class CASino::ProxyGrantingTicket < ActiveRecord::Base
3
+ include CASino::ModelConcern::Ticket
4
+
5
+ self.ticket_prefix = 'PGT'.freeze
6
+
7
+ before_validation :ensure_iou_present
8
+
3
9
  validates :ticket, uniqueness: true
4
10
  validates :iou, uniqueness: true
11
+
5
12
  belongs_to :granter, polymorphic: true
6
13
  has_many :proxy_tickets, dependent: :destroy
14
+
15
+ private
16
+ def ensure_iou_present
17
+ self.iou ||= create_random_ticket_string('PGTIOU')
18
+ end
7
19
  end
@@ -1,6 +1,10 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  class CASino::ProxyTicket < ActiveRecord::Base
4
+ include CASino::ModelConcern::Ticket
5
+
6
+ self.ticket_prefix = 'PT'.freeze
7
+
4
8
  validates :ticket, uniqueness: true
5
9
  belongs_to :proxy_granting_ticket
6
10
  has_many :proxy_granting_tickets, as: :granter, dependent: :destroy
@@ -1,7 +1,10 @@
1
1
  require 'addressable/uri'
2
2
 
3
3
  class CASino::ServiceTicket < ActiveRecord::Base
4
- validates :ticket, uniqueness: true
4
+ include CASino::ModelConcern::Ticket
5
+
6
+ self.ticket_prefix = 'ST'.freeze
7
+
5
8
  belongs_to :ticket_granting_ticket
6
9
  before_destroy :send_single_sign_out_notification, if: :consumed?
7
10
  has_many :proxy_granting_tickets, as: :granter, dependent: :destroy
@@ -15,16 +18,14 @@ class CASino::ServiceTicket < ActiveRecord::Base
15
18
  end
16
19
 
17
20
  def self.cleanup_consumed_hard
18
- self.delete_all(['created_at < ? AND consumed = ?', (CASino.config.service_ticket[:lifetime_consumed].seconds * 2).ago, true])
21
+ self.delete_all(['created_at < ? AND consumed = ?', (CASino.config.service_ticket[:lifetime_consumed] * 2).seconds.ago, true])
19
22
  end
20
23
 
21
-
22
24
  def service=(service)
23
25
  normalized_encoded_service = Addressable::URI.parse(service).normalize.to_str
24
26
  super(normalized_encoded_service)
25
27
  end
26
28
 
27
-
28
29
  def service_with_ticket_url
29
30
  service_uri = Addressable::URI.parse(self.service)
30
31
  service_uri.query_values = (service_uri.query_values(Array) || []) << ['ticket', self.ticket]
@@ -1,11 +1,15 @@
1
1
  require 'user_agent'
2
2
 
3
3
  class CASino::TicketGrantingTicket < ActiveRecord::Base
4
- validates :ticket, uniqueness: true
4
+ include CASino::ModelConcern::Ticket
5
+
6
+ self.ticket_prefix = 'TGC'.freeze
5
7
 
6
8
  belongs_to :user
7
9
  has_many :service_tickets, dependent: :destroy
8
10
 
11
+ scope :active, -> { where(awaiting_two_factor_authentication: false).order('updated_at DESC') }
12
+
9
13
  def self.cleanup(user = nil)
10
14
  if user.nil?
11
15
  base = self
@@ -2,6 +2,8 @@
2
2
  class CASino::TwoFactorAuthenticator < ActiveRecord::Base
3
3
  belongs_to :user
4
4
 
5
+ scope :active, -> { where(active: true) }
6
+
5
7
  def self.cleanup
6
8
  self.delete_all(['(created_at < ?) AND active = ?', self.lifetime.ago, false])
7
9
  end
@@ -0,0 +1,73 @@
1
+ module CASino::AuthenticationProcessor
2
+ extend ActiveSupport::Concern
3
+
4
+ def validate_login_credentials(username, password)
5
+ authentication_result = nil
6
+ authenticators.each do |authenticator_name, authenticator|
7
+ begin
8
+ data = authenticator.validate(username, password)
9
+ rescue CASino::Authenticator::AuthenticatorError => e
10
+ Rails.logger.error "Authenticator '#{authenticator_name}' (#{authenticator.class}) raised an error: #{e}"
11
+ end
12
+ if data
13
+ authentication_result = { authenticator: authenticator_name, user_data: data }
14
+ Rails.logger.info("Credentials for username '#{data[:username]}' successfully validated using authenticator '#{authenticator_name}' (#{authenticator.class})")
15
+ break
16
+ end
17
+ end
18
+ authentication_result
19
+ end
20
+
21
+ def load_user_data(authenticator_name, username)
22
+ authenticator = authenticators[authenticator_name]
23
+ return nil if authenticator.nil?
24
+ return nil unless authenticator.respond_to?(:load_user_data)
25
+ authenticator.load_user_data(username)
26
+ end
27
+
28
+ def authenticators
29
+ @authenticators ||= {}.tap do |authenticators|
30
+ CASino.config.authenticators.each do |name, auth|
31
+ next unless auth.is_a?(Hash)
32
+
33
+ authenticator = if auth[:class]
34
+ auth[:class].constantize
35
+ else
36
+ load_authenticator(auth[:authenticator])
37
+ end
38
+
39
+ authenticators[name] = authenticator.new(auth[:options])
40
+ end
41
+ end
42
+ end
43
+
44
+ private
45
+ def load_authenticator(name)
46
+ gemname, classname = parse_name(name)
47
+
48
+ begin
49
+ require gemname unless CASino.const_defined?(classname)
50
+ CASino.const_get(classname)
51
+ rescue LoadError => error
52
+ raise LoadError, load_error_message(name, gemname, error)
53
+ rescue NameError => error
54
+ raise NameError, name_error_message(name, error)
55
+ end
56
+ end
57
+
58
+ def parse_name(name)
59
+ [ "casino-#{name.underscore}_authenticator", "#{name.camelize}Authenticator" ]
60
+ end
61
+
62
+ def load_error_message(name, gemname, error)
63
+ "Failed to load authenticator '#{name}'. Maybe you have to include " \
64
+ "\"gem '#{gemname}'\" in your Gemfile?\n" \
65
+ " Error: #{error.message}\n"
66
+ end
67
+
68
+ def name_error_message(name, error)
69
+ "Failed to load authenticator '#{name}'. The authenticator class must " \
70
+ "be defined in the CASino namespace.\n" \
71
+ " Error: #{error.message}\n"
72
+ end
73
+ end
@@ -0,0 +1,12 @@
1
+ module CASino::BrowserProcessor
2
+ extend ActiveSupport::Concern
3
+
4
+ def browser_info(user_agent)
5
+ user_agent = UserAgent.parse(user_agent)
6
+ "#{user_agent.browser} (#{user_agent.platform})"
7
+ end
8
+
9
+ def same_browser?(user_agent, other_user_agent)
10
+ user_agent == other_user_agent || browser_info(user_agent) == browser_info(other_user_agent)
11
+ end
12
+ end
@@ -0,0 +1,37 @@
1
+ require 'faraday'
2
+
3
+ module CASino::ProxyGrantingTicketProcessor
4
+ extend ActiveSupport::Concern
5
+
6
+ def acquire_proxy_granting_ticket(pgt_url, service_ticket)
7
+ callback_uri = Addressable::URI.parse(pgt_url)
8
+ if callback_uri.scheme != 'https'
9
+ Rails.logger.warn "Proxy tickets can only be granted to callback servers using HTTPS."
10
+ nil
11
+ else
12
+ contact_callback_server(callback_uri, service_ticket)
13
+ end
14
+ end
15
+
16
+ private
17
+ def contact_callback_server(callback_uri, service_ticket)
18
+ pgt = service_ticket.proxy_granting_tickets.new({
19
+ pgt_url: "#{callback_uri}"
20
+ })
21
+ return unless pgt.valid?
22
+ callback_uri.query_values = (callback_uri.query_values || {}).merge(pgtId: pgt.ticket, pgtIou: pgt.iou)
23
+ response = Faraday.get "#{callback_uri}"
24
+ # TODO: does this follow redirects? CAS specification says that redirects MAY be followed (2.5.4)
25
+ if response.success?
26
+ pgt.save!
27
+ Rails.logger.debug "Proxy-granting ticket generated for service '#{service_ticket.service}': #{pgt.inspect}"
28
+ pgt
29
+ else
30
+ Rails.logger.warn "Proxy-granting ticket callback server responded with a bad result code '#{response.status}'. PGT will not be stored."
31
+ nil
32
+ end
33
+ rescue Faraday::Error::ClientError => error
34
+ Rails.logger.warn "Exception while communicating with proxy-granting ticket callback server: #{error.message}"
35
+ nil
36
+ end
37
+ end
@@ -0,0 +1,81 @@
1
+ module CASino::ServiceTicketProcessor
2
+ extend ActiveSupport::Concern
3
+
4
+ class ServiceNotAllowedError < StandardError; end
5
+ class ValidationResult < CASino::ValidationResult; end
6
+
7
+ RESERVED_CAS_PARAMETER_KEYS = ['service', 'ticket', 'gateway', 'renew']
8
+
9
+ def service_allowed?(service)
10
+ CASino::ServiceRule.allowed?(service)
11
+ end
12
+
13
+ def acquire_service_ticket(ticket_granting_ticket, service, options = {})
14
+ service_url = clean_service_url(service)
15
+ unless service_allowed?(service_url)
16
+ message = "#{service_url} is not in the list of allowed URLs"
17
+ Rails.logger.error message
18
+ raise ServiceNotAllowedError, message
19
+ end
20
+ service_tickets = ticket_granting_ticket.service_tickets
21
+ service_tickets.where(service: service_url).destroy_all
22
+ service_tickets.create!({
23
+ service: service_url,
24
+ issued_from_credentials: !!options[:credentials_supplied]
25
+ })
26
+ end
27
+
28
+ def clean_service_url(dirty_service)
29
+ return dirty_service if dirty_service.blank?
30
+ service_uri = Addressable::URI.parse dirty_service
31
+ unless service_uri.query_values.nil?
32
+ service_uri.query_values = service_uri.query_values(Array).select { |k,v| !RESERVED_CAS_PARAMETER_KEYS.include?(k) }
33
+ end
34
+ if service_uri.query_values.blank?
35
+ service_uri.query_values = nil
36
+ end
37
+
38
+ service_uri.path = (service_uri.path || '').gsub(/\/+\z/, '')
39
+ service_uri.path = '/' if service_uri.path.blank?
40
+
41
+ service_uri.normalize.to_s.tap do |clean_service|
42
+ Rails.logger.debug("Cleaned dirty service URL '#{dirty_service}' to '#{clean_service}'") if dirty_service != clean_service
43
+ end
44
+ end
45
+
46
+ def ticket_valid_for_service?(ticket, service, options = {})
47
+ validate_ticket_for_service(ticket, service, options).success?
48
+ end
49
+
50
+ def validate_ticket_for_service(ticket, service, options = {})
51
+ if ticket.nil?
52
+ result = ValidationResult.new 'INVALID_TICKET', 'Invalid validate request: Ticket does not exist', :warn
53
+ else
54
+ result = validate_existing_ticket_for_service(ticket, service, options)
55
+ ticket.update_attribute(:consumed, true)
56
+ Rails.logger.debug "Consumed ticket '#{ticket.ticket}'"
57
+ end
58
+ if result.success?
59
+ Rails.logger.info "Ticket '#{ticket.ticket}' for service '#{service}' successfully validated"
60
+ else
61
+ Rails.logger.send(result.error_severity, result.error_message)
62
+ end
63
+ result
64
+ end
65
+
66
+ private
67
+ def validate_existing_ticket_for_service(ticket, service, options = {})
68
+ service = clean_service_url(service) if ticket.is_a?(CASino::ServiceTicket)
69
+ if ticket.consumed?
70
+ ValidationResult.new 'INVALID_TICKET', "Ticket '#{ticket.ticket}' already consumed", :warn
71
+ elsif ticket.expired?
72
+ ValidationResult.new 'INVALID_TICKET', "Ticket '#{ticket.ticket}' has expired", :warn
73
+ elsif service != ticket.service
74
+ ValidationResult.new 'INVALID_SERVICE', "Ticket '#{ticket.ticket}' is not valid for service '#{service}'", :warn
75
+ elsif options[:renew] && !ticket.issued_from_credentials?
76
+ ValidationResult.new 'INVALID_TICKET', "Ticket '#{ticket.ticket}' was not issued from credentials but service '#{service}' will only accept a renewed ticket", :info
77
+ else
78
+ ValidationResult.new
79
+ end
80
+ end
81
+ end