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.
- data.tar.gz.sig +0 -0
- data/Gemfile.lock +7 -1
- data/UPGRADE.md +15 -3
- data/casino_core.gemspec +2 -0
- data/config/cas.yml +1 -7
- data/db/migrate/20130202210100_create_users.rb +0 -6
- data/db/migrate/20130203100015_create_two_factor_authenticators.rb +12 -0
- data/db/migrate/20130203101351_add_active_to_two_factor_authenticators.rb +5 -0
- data/db/migrate/20130203155008_add_awaiting_two_factor_authentication_to_ticket_granting_tickets.rb +5 -0
- data/db/schema.rb +16 -5
- data/lib/casino_core/helper.rb +1 -0
- data/lib/casino_core/helper/proxy_granting_tickets.rb +26 -31
- data/lib/casino_core/helper/proxy_tickets.rb +1 -5
- data/lib/casino_core/helper/ticket_granting_tickets.rb +3 -2
- data/lib/casino_core/helper/two_factor_authenticators.rb +22 -0
- data/lib/casino_core/model.rb +2 -0
- data/lib/casino_core/model/service_ticket/single_sign_out_notifier.rb +13 -30
- data/lib/casino_core/model/ticket_granting_ticket.rb +1 -1
- data/lib/casino_core/model/two_factor_authenticator.rb +19 -0
- data/lib/casino_core/model/user.rb +5 -0
- data/lib/casino_core/model/validation_result.rb +7 -0
- data/lib/casino_core/processor.rb +5 -0
- data/lib/casino_core/processor/login_credential_acceptor.rb +12 -7
- data/lib/casino_core/processor/second_factor_authentication_acceptor.rb +46 -0
- data/lib/casino_core/processor/session_overview.rb +1 -1
- data/lib/casino_core/processor/two_factor_authenticator_activator.rb +46 -0
- data/lib/casino_core/processor/two_factor_authenticator_destroyer.rb +38 -0
- data/lib/casino_core/processor/two_factor_authenticator_overview.rb +24 -0
- data/lib/casino_core/processor/two_factor_authenticator_registrator.rb +27 -0
- data/lib/casino_core/settings.rb +20 -2
- data/lib/casino_core/tasks/cleanup.rake +7 -1
- data/lib/casino_core/version.rb +1 -1
- data/spec/model/two_factor_authenticator_spec.rb +31 -0
- data/spec/processor/login_credential_acceptor_spec.rb +10 -0
- data/spec/processor/login_credential_requestor_spec.rb +9 -0
- data/spec/processor/second_factor_authenticaton_acceptor_spec.rb +83 -0
- data/spec/processor/ticket_validator_spec.rb +15 -0
- data/spec/processor/two_factor_authenticator_activator_spec.rb +122 -0
- data/spec/processor/two_factor_authenticator_destroyer_spec.rb +71 -0
- data/spec/processor/two_factor_authenticator_overview_spec.rb +56 -0
- data/spec/processor/two_factor_authenticator_registrator_spec.rb +48 -0
- data/spec/settings_spec.rb +9 -0
- data/spec/support/factories/ticket_granting_ticket_factory.rb +4 -0
- data/spec/support/factories/two_factor_authenticator_factory.rb +16 -0
- metadata +61 -4
- metadata.gz.sig +1 -3
data.tar.gz.sig
CHANGED
|
Binary file
|
data/Gemfile.lock
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
casino_core (1.
|
|
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
|
-
* `
|
|
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
|
-
* `
|
|
20
|
-
* `
|
|
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.
|
data/casino_core.gemspec
CHANGED
|
@@ -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
|
|
data/config/cas.yml
CHANGED
|
@@ -1,12 +1,6 @@
|
|
|
1
1
|
defaults: &defaults
|
|
2
|
-
login_ticket:
|
|
3
|
-
lifetime: 600
|
|
4
2
|
service_ticket:
|
|
5
|
-
lifetime_unconsumed:
|
|
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
|
data/db/schema.rb
CHANGED
|
@@ -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 =>
|
|
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",
|
|
77
|
-
t.datetime "created_at",
|
|
78
|
-
t.datetime "updated_at",
|
|
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",
|
|
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
|
data/lib/casino_core/helper.rb
CHANGED
|
@@ -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 '
|
|
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
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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(
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
response
|
|
38
|
-
|
|
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 <
|
|
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
|
data/lib/casino_core/model.rb
CHANGED
|
@@ -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 '
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
|
@@ -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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|