casino_core 1.2.0 → 1.3.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 (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