casino_core 1.2.0 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data.tar.gz.sig +0 -0
  2. data/Gemfile.lock +7 -1
  3. data/UPGRADE.md +15 -3
  4. data/casino_core.gemspec +2 -0
  5. data/config/cas.yml +1 -7
  6. data/db/migrate/20130202210100_create_users.rb +0 -6
  7. data/db/migrate/20130203100015_create_two_factor_authenticators.rb +12 -0
  8. data/db/migrate/20130203101351_add_active_to_two_factor_authenticators.rb +5 -0
  9. data/db/migrate/20130203155008_add_awaiting_two_factor_authentication_to_ticket_granting_tickets.rb +5 -0
  10. data/db/schema.rb +16 -5
  11. data/lib/casino_core/helper.rb +1 -0
  12. data/lib/casino_core/helper/proxy_granting_tickets.rb +26 -31
  13. data/lib/casino_core/helper/proxy_tickets.rb +1 -5
  14. data/lib/casino_core/helper/ticket_granting_tickets.rb +3 -2
  15. data/lib/casino_core/helper/two_factor_authenticators.rb +22 -0
  16. data/lib/casino_core/model.rb +2 -0
  17. data/lib/casino_core/model/service_ticket/single_sign_out_notifier.rb +13 -30
  18. data/lib/casino_core/model/ticket_granting_ticket.rb +1 -1
  19. data/lib/casino_core/model/two_factor_authenticator.rb +19 -0
  20. data/lib/casino_core/model/user.rb +5 -0
  21. data/lib/casino_core/model/validation_result.rb +7 -0
  22. data/lib/casino_core/processor.rb +5 -0
  23. data/lib/casino_core/processor/login_credential_acceptor.rb +12 -7
  24. data/lib/casino_core/processor/second_factor_authentication_acceptor.rb +46 -0
  25. data/lib/casino_core/processor/session_overview.rb +1 -1
  26. data/lib/casino_core/processor/two_factor_authenticator_activator.rb +46 -0
  27. data/lib/casino_core/processor/two_factor_authenticator_destroyer.rb +38 -0
  28. data/lib/casino_core/processor/two_factor_authenticator_overview.rb +24 -0
  29. data/lib/casino_core/processor/two_factor_authenticator_registrator.rb +27 -0
  30. data/lib/casino_core/settings.rb +20 -2
  31. data/lib/casino_core/tasks/cleanup.rake +7 -1
  32. data/lib/casino_core/version.rb +1 -1
  33. data/spec/model/two_factor_authenticator_spec.rb +31 -0
  34. data/spec/processor/login_credential_acceptor_spec.rb +10 -0
  35. data/spec/processor/login_credential_requestor_spec.rb +9 -0
  36. data/spec/processor/second_factor_authenticaton_acceptor_spec.rb +83 -0
  37. data/spec/processor/ticket_validator_spec.rb +15 -0
  38. data/spec/processor/two_factor_authenticator_activator_spec.rb +122 -0
  39. data/spec/processor/two_factor_authenticator_destroyer_spec.rb +71 -0
  40. data/spec/processor/two_factor_authenticator_overview_spec.rb +56 -0
  41. data/spec/processor/two_factor_authenticator_registrator_spec.rb +48 -0
  42. data/spec/settings_spec.rb +9 -0
  43. data/spec/support/factories/ticket_granting_ticket_factory.rb +4 -0
  44. data/spec/support/factories/two_factor_authenticator_factory.rb +16 -0
  45. metadata +61 -4
  46. metadata.gz.sig +1 -3
data.tar.gz.sig CHANGED
Binary file
@@ -1,9 +1,11 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- casino_core (1.2.0)
4
+ casino_core (1.3.0)
5
5
  activerecord (~> 3.2.9)
6
6
  addressable (~> 2.3)
7
+ faraday (~> 0.8)
8
+ rotp (~> 1.4)
7
9
  terminal-table (~> 1.4)
8
10
  useragent (~> 0.4)
9
11
 
@@ -29,10 +31,14 @@ GEM
29
31
  diff-lcs (1.1.3)
30
32
  factory_girl (4.2.0)
31
33
  activesupport (>= 3.0.0)
34
+ faraday (0.8.5)
35
+ multipart-post (~> 1.1)
32
36
  i18n (0.6.1)
33
37
  multi_json (1.5.0)
38
+ multipart-post (1.1.5)
34
39
  nokogiri (1.5.6)
35
40
  rake (10.0.3)
41
+ rotp (1.4.1)
36
42
  rspec (2.12.0)
37
43
  rspec-core (~> 2.12.0)
38
44
  rspec-expectations (~> 2.12.0)
data/UPGRADE.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  Here is a list of backward-incompatible changes that were introduced.
4
4
 
5
+ ## 1.x.y
6
+
7
+ This release adds support for two-factor authentication using a [TOTP](http://en.wikipedia.org/wiki/Time-based_One-time_Password_Algorithm) (time-based one-time password) which can be generated with applications like [Google Authenticator](http://support.google.com/a/bin/answer.py?hl=en&answer=1037451) (iPhone, Android, BlackBerry) or gadgets such as the [YubiKey](http://www.yubico.com/products/yubikey-hardware/yubikey/).
8
+
9
+ If you would like to support two-factor authentication in your web application, please have a look at the corresponding processors: `SecondFactorAuthenticationAcceptor`, `TwoFactorAuthenticatorActivator`, `TwoFactorAuthenticatorDestroyer`, `TwoFactorAuthenticatorOverview`, `TwoFactorAuthenticatorRegistrator`
10
+
11
+ New callbacks:
12
+
13
+ * `LoginCredentialAcceptor`: calls `#two_factor_authentication_pending` on the listener, when two-factor authentication is enabled for this user.
14
+
15
+ If you don't want to support two-factor authentication, nothing has to be changed.
16
+
5
17
  ## 1.2.0
6
18
 
7
19
  API changes:
@@ -12,9 +24,9 @@ API changes:
12
24
 
13
25
  API changes:
14
26
 
15
- * `login_credential_acceptor`: The parameters of `#process` changed from `params, cookies, user_agent` to just `params, user_agent`
27
+ * `LoginCredentialAcceptor`: The parameters of `#process` changed from `params, cookies, user_agent` to just `params, user_agent`
16
28
 
17
29
  New callbacks:
18
30
 
19
- * `login_credential_requestor` and `login_credential_acceptor` call `#service_not_allowed` on the listener, when a service is not in the service whitelist.
20
- * `api/service_ticket_provider` calls `#service_not_allowed_via_api` on the listener, when a service is not in the service whitelist.
31
+ * `LoginCredentialRequestor` and `LoginCredentialAcceptor` call `#service_not_allowed` on the listener, when a service is not in the service whitelist.
32
+ * `API::ServiceTicketProvider` calls `#service_not_allowed_via_api` on the listener, when a service is not in the service whitelist.
@@ -34,5 +34,7 @@ Gem::Specification.new do |s|
34
34
  s.add_runtime_dependency 'addressable', '~> 2.3'
35
35
  s.add_runtime_dependency 'terminal-table', '~> 1.4'
36
36
  s.add_runtime_dependency 'useragent', '~> 0.4'
37
+ s.add_runtime_dependency 'faraday', '~> 0.8'
38
+ s.add_runtime_dependency 'rotp', '~> 1.4'
37
39
  end
38
40
 
@@ -1,12 +1,6 @@
1
1
  defaults: &defaults
2
- login_ticket:
3
- lifetime: 600
4
2
  service_ticket:
5
- lifetime_unconsumed: 300
6
- lifetime_consumed: 86400
7
- proxy_ticket:
8
- lifetime_unconsumed: 300
9
- lifetime_consumed: 86400
3
+ lifetime_unconsumed: 299
10
4
  authenticators:
11
5
  static_1:
12
6
  class: "CASinoCore::Authenticator::Static"
@@ -1,11 +1,5 @@
1
1
  class CreateUsers < ActiveRecord::Migration
2
2
  def up
3
- tgt = CASinoCore::Model::TicketGrantingTicket.new
4
- tgt.authenticator = 'foo'
5
- tgt.username = 'bar'
6
- tgt.ticket = 'TGT-bla'
7
- tgt.save!
8
-
9
3
  create_table :users do |t|
10
4
  t.string :authenticator, null: false
11
5
  t.string :username, null: false
@@ -0,0 +1,12 @@
1
+ class CreateTwoFactorAuthenticators < ActiveRecord::Migration
2
+ def change
3
+ create_table :two_factor_authenticators do |t|
4
+ t.integer :user_id, null: false
5
+ t.string :secret, null: false
6
+
7
+ t.timestamps
8
+ end
9
+
10
+ add_index :two_factor_authenticators, :user_id
11
+ end
12
+ end
@@ -0,0 +1,5 @@
1
+ class AddActiveToTwoFactorAuthenticators < ActiveRecord::Migration
2
+ def change
3
+ add_column :two_factor_authenticators, :active, :boolean, null: false, default: false
4
+ end
5
+ end
@@ -0,0 +1,5 @@
1
+ class AddAwaitingTwoFactorAuthenticationToTicketGrantingTickets < ActiveRecord::Migration
2
+ def change
3
+ add_column :ticket_granting_tickets, :awaiting_two_factor_authentication, :boolean, null: false, default: false
4
+ end
5
+ end
@@ -11,7 +11,7 @@
11
11
  #
12
12
  # It's strongly recommended to check this file into your version control system.
13
13
 
14
- ActiveRecord::Schema.define(:version => 20130202210100) do
14
+ ActiveRecord::Schema.define(:version => 20130203155008) do
15
15
 
16
16
  create_table "login_tickets", :force => true do |t|
17
17
  t.string "ticket", :null => false
@@ -73,15 +73,26 @@ ActiveRecord::Schema.define(:version => 20130202210100) do
73
73
  add_index "service_tickets", ["ticket_granting_ticket_id"], :name => "index_service_tickets_on_ticket_granting_ticket_id"
74
74
 
75
75
  create_table "ticket_granting_tickets", :force => true do |t|
76
- t.string "ticket", :null => false
77
- t.datetime "created_at", :null => false
78
- t.datetime "updated_at", :null => false
76
+ t.string "ticket", :null => false
77
+ t.datetime "created_at", :null => false
78
+ t.datetime "updated_at", :null => false
79
79
  t.string "user_agent"
80
- t.integer "user_id", :null => false
80
+ t.integer "user_id", :null => false
81
+ t.boolean "awaiting_two_factor_authentication", :default => false, :null => false
81
82
  end
82
83
 
83
84
  add_index "ticket_granting_tickets", ["ticket"], :name => "index_ticket_granting_tickets_on_ticket", :unique => true
84
85
 
86
+ create_table "two_factor_authenticators", :force => true do |t|
87
+ t.integer "user_id", :null => false
88
+ t.string "secret", :null => false
89
+ t.datetime "created_at", :null => false
90
+ t.datetime "updated_at", :null => false
91
+ t.boolean "active", :default => false, :null => false
92
+ end
93
+
94
+ add_index "two_factor_authenticators", ["user_id"], :name => "index_two_factor_authenticators_on_user_id"
95
+
85
96
  create_table "users", :force => true do |t|
86
97
  t.string "authenticator", :null => false
87
98
  t.string "username", :null => false
@@ -12,5 +12,6 @@ module CASinoCore
12
12
  autoload :ServiceTickets, 'casino_core/helper/service_tickets.rb'
13
13
  autoload :Tickets, 'casino_core/helper/tickets.rb'
14
14
  autoload :TicketGrantingTickets, 'casino_core/helper/ticket_granting_tickets.rb'
15
+ autoload :TwoFactorAuthenticators, 'casino_core/helper/two_factor_authenticators.rb'
15
16
  end
16
17
  end
@@ -1,5 +1,5 @@
1
1
  require 'addressable/uri'
2
- require 'net/https'
2
+ require 'faraday'
3
3
 
4
4
  require 'casino_core/helper/logger'
5
5
  require 'casino_core/helper/tickets'
@@ -11,41 +11,36 @@ module CASinoCore
11
11
  include CASinoCore::Helper::Tickets
12
12
 
13
13
  def acquire_proxy_granting_ticket(pgt_url, service_ticket)
14
- begin
15
- return contact_callback_server(pgt_url, service_ticket)
16
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
17
- logger.warn "Exception while communicating with proxy-granting ticket callback server: #{e.message}"
14
+ callback_uri = Addressable::URI.parse(pgt_url)
15
+ if callback_uri.scheme != 'https'
16
+ logger.warn "Proxy tickets can only be granted to callback servers using HTTPS."
17
+ nil
18
+ else
19
+ contact_callback_server(callback_uri, service_ticket)
18
20
  end
19
- nil
20
21
  end
21
22
 
22
23
  private
23
- def contact_callback_server(pgt_url, service_ticket)
24
- callback_uri = Addressable::URI.parse(pgt_url)
25
- https = Net::HTTP.new(callback_uri.host, callback_uri.port || 443)
26
- https.use_ssl = true
27
-
28
- https.start do |conn|
29
- pgt = service_ticket.proxy_granting_tickets.new({
30
- ticket: random_ticket_string('PGT'),
31
- iou: random_ticket_string('PGTIOU'),
32
- pgt_url: pgt_url
33
- })
34
-
35
- callback_uri.query_values = (callback_uri.query_values || {}).merge(pgtId: pgt.ticket, pgtIou: pgt.iou)
36
-
37
- response = conn.request_get(callback_uri.request_uri)
38
- # TODO: follow redirects... 2.5.4 says that redirects MAY be followed
39
- if "#{response.code}" == "200"
40
- # 3.4 (proxy-granting ticket IOU)
41
- pgt.save!
42
- logger.debug "Proxy-granting ticket generated for service '#{service_ticket.service}': #{pgt.inspect}"
43
- pgt
44
- else
45
- logger.warn "Proxy-granting ticket callback server responded with a bad result code '#{response.code}'. PGT will not be stored."
46
- nil
47
- end
24
+ def contact_callback_server(callback_uri, service_ticket)
25
+ pgt = service_ticket.proxy_granting_tickets.new({
26
+ ticket: random_ticket_string('PGT'),
27
+ iou: random_ticket_string('PGTIOU'),
28
+ pgt_url: "#{callback_uri}"
29
+ })
30
+ callback_uri.query_values = (callback_uri.query_values || {}).merge(pgtId: pgt.ticket, pgtIou: pgt.iou)
31
+ response = Faraday.get "#{callback_uri}"
32
+ # TODO: does this follow redirects? CAS specification says that redirects MAY be followed (2.5.4)
33
+ if response.success?
34
+ pgt.save!
35
+ logger.debug "Proxy-granting ticket generated for service '#{service_ticket.service}': #{pgt.inspect}"
36
+ pgt
37
+ else
38
+ logger.warn "Proxy-granting ticket callback server responded with a bad result code '#{response.status}'. PGT will not be stored."
39
+ nil
48
40
  end
41
+ rescue Faraday::Error::ClientError => error
42
+ logger.warn "Exception while communicating with proxy-granting ticket callback server: #{error.message}"
43
+ nil
49
44
  end
50
45
  end
51
46
  end
@@ -2,11 +2,7 @@ module CASinoCore
2
2
  module Helper
3
3
  module ProxyTickets
4
4
 
5
- class ValidationResult < Struct.new(:error_code, :error_message, :error_severity)
6
- def success?
7
- self.error_code.nil?
8
- end
9
- end
5
+ class ValidationResult < CASinoCore::Model::ValidationResult; end
10
6
 
11
7
  include CASinoCore::Helper::Logger
12
8
  include CASinoCore::Helper::Tickets
@@ -7,9 +7,9 @@ module CASinoCore
7
7
  include CASinoCore::Helper::Browser
8
8
  include CASinoCore::Helper::Logger
9
9
 
10
- def find_valid_ticket_granting_ticket(tgt, user_agent)
10
+ def find_valid_ticket_granting_ticket(tgt, user_agent, ignore_two_factor = false)
11
11
  ticket_granting_ticket = CASinoCore::Model::TicketGrantingTicket.where(ticket: tgt).first
12
- unless ticket_granting_ticket.nil?
12
+ unless ticket_granting_ticket.nil? || (!ignore_two_factor && ticket_granting_ticket.awaiting_two_factor_authentication?)
13
13
  if same_browser?(ticket_granting_ticket.user_agent, user_agent)
14
14
  ticket_granting_ticket.user_agent = user_agent
15
15
  ticket_granting_ticket.touch
@@ -27,6 +27,7 @@ module CASinoCore
27
27
  user = load_or_initialize_user(authentication_result[:authenticator], user_data[:username], user_data[:extra_attributes])
28
28
  user.ticket_granting_tickets.create!({
29
29
  ticket: random_ticket_string('TGC'),
30
+ awaiting_two_factor_authentication: !user.active_two_factor_authenticator.nil?,
30
31
  user_agent: user_agent
31
32
  })
32
33
  end
@@ -0,0 +1,22 @@
1
+ require 'addressable/uri'
2
+
3
+ module CASinoCore
4
+ module Helper
5
+ module TwoFactorAuthenticators
6
+ class ValidationResult < CASinoCore::Model::ValidationResult; end
7
+
8
+ def validate_one_time_password(otp, authenticator)
9
+ if authenticator.nil? || authenticator.expired?
10
+ ValidationResult.new 'INVALID_AUTHENTICATOR', 'Authenticator does not exist or expired', :warn
11
+ else
12
+ totp = ROTP::TOTP.new(authenticator.secret)
13
+ if totp.verify_with_drift(otp, CASinoCore::Settings.two_factor_authenticator[:drift])
14
+ ValidationResult.new
15
+ else
16
+ ValidationResult.new 'INVALID_OTP', 'One-time password not valid', :warn
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -8,6 +8,8 @@ module CASinoCore
8
8
  autoload :ProxyGrantingTicket, 'casino_core/model/proxy_granting_ticket.rb'
9
9
  autoload :ProxyTicket, 'casino_core/model/proxy_ticket.rb'
10
10
  autoload :TicketGrantingTicket, 'casino_core/model/ticket_granting_ticket.rb'
11
+ autoload :TwoFactorAuthenticator, 'casino_core/model/two_factor_authenticator.rb'
11
12
  autoload :User, 'casino_core/model/user.rb'
13
+ autoload :ValidationResult, 'casino_core/model/validation_result.rb'
12
14
  end
13
15
  end
@@ -1,7 +1,6 @@
1
1
  require 'builder'
2
- require 'net/https'
2
+ require 'faraday'
3
3
  require 'casino_core/model/service_ticket'
4
- require 'addressable/uri'
5
4
 
6
5
  class CASinoCore::Model::ServiceTicket::SingleSignOutNotifier
7
6
  include CASinoCore::Helper::Logger
@@ -11,10 +10,7 @@ class CASinoCore::Model::ServiceTicket::SingleSignOutNotifier
11
10
  end
12
11
 
13
12
  def notify
14
- xml = build_xml
15
- uri = Addressable::URI.parse(@service_ticket.service)
16
- request = build_request(uri, xml)
17
- send_notification(uri, request)
13
+ send_notification @service_ticket.service, build_xml
18
14
  end
19
15
 
20
16
  private
@@ -32,31 +28,18 @@ class CASinoCore::Model::ServiceTicket::SingleSignOutNotifier
32
28
  xml.target!
33
29
  end
34
30
 
35
- def build_request(uri, xml)
36
- request = Net::HTTP::Post.new(uri.request_uri)
37
- request.set_form_data(logoutRequest: xml)
38
- return request
39
- end
40
-
41
- def send_notification(uri, request)
31
+ def send_notification(url, xml)
42
32
  logger.info "Sending Single Sign Out notification for ticket '#{@service_ticket.ticket}'"
43
- begin
44
- http = Net::HTTP.new(uri.host, uri.port)
45
- http.use_ssl = true if uri.scheme =='https'
46
-
47
- http.start do |conn|
48
- response = conn.request(request)
49
- if response.kind_of? Net::HTTPSuccess
50
- logger.info "Logout notification successfully posted to #{uri}."
51
- return true
52
- else
53
- logger.warn "Service #{uri} responed to logout notification with code '#{response.code}'!"
54
- return false
55
- end
56
- end
57
- rescue Timeout::Error, Errno::EINVAL, Errno::ECONNRESET, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError => e
58
- logger.warn "Failed to send logout notification to service #{uri} due to #{e}"
59
- return false
33
+ result = Faraday.post(url, logoutRequest: xml)
34
+ if result.success?
35
+ logger.info "Logout notification successfully posted to #{url}."
36
+ true
37
+ else
38
+ logger.warn "Service #{url} responed to logout notification with code '#{result.status}'!"
39
+ false
60
40
  end
41
+ rescue Faraday::Error::ClientError => error
42
+ logger.warn "Failed to send logout notification to service #{url} due to #{error}"
43
+ false
61
44
  end
62
45
  end
@@ -1,7 +1,7 @@
1
1
  require 'casino_core/model'
2
2
 
3
3
  class CASinoCore::Model::TicketGrantingTicket < ActiveRecord::Base
4
- attr_accessible :ticket, :user_agent
4
+ attr_accessible :ticket, :user_agent, :awaiting_two_factor_authentication
5
5
  validates :ticket, uniqueness: true
6
6
 
7
7
  belongs_to :user
@@ -0,0 +1,19 @@
1
+ require 'casino_core/model'
2
+
3
+ class CASinoCore::Model::TwoFactorAuthenticator < ActiveRecord::Base
4
+ attr_accessible :secret
5
+
6
+ belongs_to :user
7
+
8
+ def self.cleanup
9
+ self.delete_all(['(created_at < ?) AND active = ?', self.lifetime.ago, false])
10
+ end
11
+
12
+ def self.lifetime
13
+ CASinoCore::Settings.two_factor_authenticator[:lifetime_inactive].seconds
14
+ end
15
+
16
+ def expired?
17
+ !self.active? && (Time.now - (self.created_at || Time.now)) > self.class.lifetime
18
+ end
19
+ end
@@ -5,4 +5,9 @@ class CASinoCore::Model::User < ActiveRecord::Base
5
5
  serialize :extra_attributes, Hash
6
6
 
7
7
  has_many :ticket_granting_tickets
8
+ has_many :two_factor_authenticators
9
+
10
+ def active_two_factor_authenticator
11
+ self.two_factor_authenticators.where(active: true).first
12
+ end
8
13
  end
@@ -0,0 +1,7 @@
1
+ require 'casino_core/model'
2
+
3
+ class CASinoCore::Model::ValidationResult < Struct.new(:error_code, :error_message, :error_severity)
4
+ def success?
5
+ self.error_code.nil?
6
+ end
7
+ end
@@ -8,9 +8,14 @@ module CASinoCore
8
8
  autoload :Logout, 'casino_core/processor/logout.rb'
9
9
  autoload :ProxyTicketProvider, 'casino_core/processor/proxy_ticket_provider.rb'
10
10
  autoload :ProxyTicketValidator, 'casino_core/processor/proxy_ticket_validator.rb'
11
+ autoload :SecondFactorAuthenticationAcceptor, 'casino_core/processor/second_factor_authentication_acceptor.rb'
11
12
  autoload :ServiceTicketValidator, 'casino_core/processor/service_ticket_validator.rb'
12
13
  autoload :SessionDestroyer, 'casino_core/processor/session_destroyer.rb'
13
14
  autoload :SessionOverview, 'casino_core/processor/session_overview.rb'
15
+ autoload :TwoFactorAuthenticatorActivator, 'casino_core/processor/two_factor_authenticator_activator.rb'
16
+ autoload :TwoFactorAuthenticatorDestroyer, 'casino_core/processor/two_factor_authenticator_destroyer.rb'
17
+ autoload :TwoFactorAuthenticatorOverview, 'casino_core/processor/two_factor_authenticator_overview.rb'
18
+ autoload :TwoFactorAuthenticatorRegistrator, 'casino_core/processor/two_factor_authenticator_registrator.rb'
14
19
 
15
20
  autoload :API, 'casino_core/processor/api.rb'
16
21
 
@@ -18,6 +18,7 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
18
18
  # * `#invalid_login_ticket` and `#invalid_login_credentials`: The first argument is a LoginTicket.
19
19
  # See {CASinoCore::Processor::LoginCredentialRequestor} for details.
20
20
  # * `#service_not_allowed`: The user tried to access a service that this CAS server is not allowed to serve.
21
+ # * `#two_factor_authentication_pending`: The user should be asked to enter his OTP. The first argument (String) is the ticket-granting ticket. The ticket-granting ticket is not active yet. Use SecondFactorAuthenticatonAcceptor to activate it.
21
22
  #
22
23
  # @param [Hash] params parameters supplied by user
23
24
  # @param [String] user_agent user-agent delivered by the client
@@ -42,14 +43,18 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
42
43
  end
43
44
 
44
45
  def user_logged_in(authentication_result)
45
- begin
46
- ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, @user_agent)
47
- url = unless @params[:service].nil?
48
- acquire_service_ticket(ticket_granting_ticket, @params[:service], true).service_with_ticket_url
46
+ ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, @user_agent)
47
+ if ticket_granting_ticket.awaiting_two_factor_authentication?
48
+ @listener.two_factor_authentication_pending(ticket_granting_ticket.ticket)
49
+ else
50
+ begin
51
+ url = unless @params[:service].blank?
52
+ acquire_service_ticket(ticket_granting_ticket, @params[:service], true).service_with_ticket_url
53
+ end
54
+ @listener.user_logged_in(url, ticket_granting_ticket.ticket)
55
+ rescue ServiceNotAllowedError => e
56
+ @listener.service_not_allowed(clean_service_url @params[:service])
49
57
  end
50
- @listener.user_logged_in(url, ticket_granting_ticket.ticket)
51
- rescue ServiceNotAllowedError => e
52
- @listener.service_not_allowed(clean_service_url @params[:service])
53
58
  end
54
59
  end
55
60
  end