casino_core 1.3.5 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data.tar.gz.sig CHANGED
Binary file
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- casino_core (1.3.5)
4
+ casino_core (1.4.0)
5
5
  activerecord (~> 3.2.9)
6
6
  addressable (~> 2.3)
7
7
  faraday (~> 0.8)
data/UPGRADE.md CHANGED
@@ -2,7 +2,16 @@
2
2
 
3
3
  Here is a list of backward-incompatible changes that were introduced.
4
4
 
5
- ## 1.x.y
5
+ ## 1.4.0
6
+
7
+ This release changed some database structure. Be sure to advise users to migrate the database using `bundle exec rake casino_core:db:migrate`.
8
+
9
+ API changes:
10
+
11
+ * `LoginCredentialAcceptor`: `user_logged_in` may receive a third argument (`Time`, optional, default = `nil`) which represents the expiry date of the cookie. If it is `nil`, the cookie should be a session cookie.
12
+ * `Logout`: `user_logged_out` may receive a second argument (`boolean`, optional, default = `false`). When it is `true`, the user should be redirected immediately.
13
+
14
+ ## 1.3.0
6
15
 
7
16
  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
17
 
@@ -0,0 +1,5 @@
1
+ class AddLongTermToTicketGrantingTickets < ActiveRecord::Migration
2
+ def change
3
+ add_column :ticket_granting_tickets, :long_term, :boolean, null: false, default: false
4
+ end
5
+ 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 => 20130203155008) do
14
+ ActiveRecord::Schema.define(:version => 20130323111208) do
15
15
 
16
16
  create_table "login_tickets", :force => true do |t|
17
17
  t.string "ticket", :null => false
@@ -79,6 +79,7 @@ ActiveRecord::Schema.define(:version => 20130203155008) do
79
79
  t.string "user_agent"
80
80
  t.integer "user_id", :null => false
81
81
  t.boolean "awaiting_two_factor_authentication", :default => false, :null => false
82
+ t.boolean "long_term", :default => false, :null => false
82
83
  end
83
84
 
84
85
  add_index "ticket_granting_tickets", ["ticket"], :name => "index_ticket_granting_tickets_on_ticket", :unique => true
@@ -14,18 +14,19 @@ class CASinoCore::Builder::TicketValidationResponse < CASinoCore::Builder
14
14
  ticket = @options[:ticket]
15
15
  if ticket.is_a?(CASinoCore::Model::ProxyTicket)
16
16
  proxies = []
17
- _ticket = ticket
18
- while _ticket.is_a?(CASinoCore::Model::ProxyTicket)
17
+ service_ticket = ticket
18
+ while service_ticket.is_a?(CASinoCore::Model::ProxyTicket)
19
19
  proxy_granting_ticket = ticket.proxy_granting_ticket
20
20
  proxies << proxy_granting_ticket.pgt_url
21
- _ticket = proxy_granting_ticket.granter
21
+ service_ticket = proxy_granting_ticket.granter
22
22
  end
23
- ticket_granting_ticket = _ticket.ticket_granting_ticket
23
+ ticket_granting_ticket = service_ticket.ticket_granting_ticket
24
24
  else
25
+ service_ticket = ticket
25
26
  ticket_granting_ticket = ticket.ticket_granting_ticket
26
27
  end
27
28
 
28
- build_success_xml(service_response, ticket, ticket_granting_ticket, proxies)
29
+ build_success_xml(service_response, ticket, service_ticket, ticket_granting_ticket, proxies)
29
30
  else
30
31
  build_failure_xml(service_response)
31
32
  end
@@ -44,12 +45,21 @@ class CASinoCore::Builder::TicketValidationResponse < CASinoCore::Builder
44
45
  end
45
46
  end
46
47
 
47
- def build_success_xml(service_response, ticket, ticket_granting_ticket, proxies)
48
+ def build_success_xml(service_response, ticket, service_ticket, ticket_granting_ticket, proxies)
48
49
  user = ticket_granting_ticket.user
49
50
  service_response.cas :authenticationSuccess do |authentication_success|
50
51
  authentication_success.cas :user, user.username
51
52
  unless user.extra_attributes.blank?
52
53
  authentication_success.cas :attributes do |attributes|
54
+ attributes.cas :authenticationDate, ticket_granting_ticket.created_at.iso8601
55
+ attributes.cas :longTermAuthenticationRequestTokenUsed, ticket_granting_ticket.long_term?
56
+ attributes.cas :isFromNewLogin, service_ticket.issued_from_credentials?
57
+ # This would probably be the correct way, but current clients do not support this:
58
+ # attributes.cas :userAttributes do |user_attributes|
59
+ # user.extra_attributes.each do |key, value|
60
+ # serialize_extra_attribute(user_attributes, key, value)
61
+ # end
62
+ # end
53
63
  user.extra_attributes.each do |key, value|
54
64
  serialize_extra_attribute(attributes, key, value)
55
65
  end
@@ -29,14 +29,15 @@ module CASinoCore
29
29
  end
30
30
  end
31
31
 
32
- def acquire_ticket_granting_ticket(authentication_result, user_agent = nil)
32
+ def acquire_ticket_granting_ticket(authentication_result, user_agent = nil, long_term = nil)
33
33
  user_data = authentication_result[:user_data]
34
34
  user = load_or_initialize_user(authentication_result[:authenticator], user_data[:username], user_data[:extra_attributes])
35
35
  cleanup_expired_ticket_granting_tickets(user)
36
36
  user.ticket_granting_tickets.create!({
37
37
  ticket: random_ticket_string('TGC'),
38
38
  awaiting_two_factor_authentication: !user.active_two_factor_authenticator.nil?,
39
- user_agent: user_agent
39
+ user_agent: user_agent,
40
+ long_term: !!long_term
40
41
  })
41
42
  end
42
43
 
@@ -42,5 +42,6 @@ class CASinoCore::Model::ServiceTicket < ActiveRecord::Base
42
42
  def send_single_sing_out_notification
43
43
  notifier = SingleSignOutNotifier.new(self)
44
44
  notifier.notify
45
+ true
45
46
  end
46
47
  end
@@ -1,17 +1,19 @@
1
1
  require 'casino_core/model'
2
2
 
3
3
  class CASinoCore::Model::TicketGrantingTicket < ActiveRecord::Base
4
- attr_accessible :ticket, :user_agent, :awaiting_two_factor_authentication
4
+ attr_accessible :ticket, :user_agent, :awaiting_two_factor_authentication, :long_term
5
5
  validates :ticket, uniqueness: true
6
6
 
7
7
  belongs_to :user
8
- has_many :service_tickets
9
-
10
- before_destroy :destroy_service_tickets
11
- after_destroy :destroy_proxy_granting_tickets
8
+ has_many :service_tickets, dependent: :destroy
12
9
 
13
10
  def self.cleanup
14
- self.destroy_all(['created_at < ?', CASinoCore::Settings.ticket_granting_ticket[:lifetime].seconds.ago])
11
+ self.destroy_all([
12
+ '(created_at < ? AND long_term = ?) OR created_at < ?',
13
+ CASinoCore::Settings.ticket_granting_ticket[:lifetime].seconds.ago,
14
+ false,
15
+ CASinoCore::Settings.ticket_granting_ticket[:lifetime_long_term].seconds.ago
16
+ ])
15
17
  end
16
18
 
17
19
  def browser_info
@@ -34,25 +36,11 @@ class CASinoCore::Model::TicketGrantingTicket < ActiveRecord::Base
34
36
  end
35
37
 
36
38
  def expired?
37
- lifetime = CASinoCore::Settings.ticket_granting_ticket[:lifetime]
38
- (Time.now - (self.created_at || Time.now)) > lifetime
39
- end
40
-
41
- private
42
- def destroy_service_tickets
43
- self.service_tickets.each do |service_ticket|
44
- unless service_ticket.destroy
45
- service_ticket.ticket_granting_ticket_id = nil
46
- service_ticket.save
47
- end
48
- end
49
- end
50
-
51
- # Deletes proxy-granting tickets of service tickets that
52
- # could not be deleted (see #destroy_service_tickets)
53
- def destroy_proxy_granting_tickets
54
- self.service_tickets.each do |service_ticket|
55
- service_ticket.proxy_granting_tickets.destroy_all
39
+ if long_term?
40
+ lifetime = CASinoCore::Settings.ticket_granting_ticket[:lifetime_long_term]
41
+ else
42
+ lifetime = CASinoCore::Settings.ticket_granting_ticket[:lifetime]
56
43
  end
44
+ (Time.now - (self.created_at || Time.now)) > lifetime
57
45
  end
58
46
  end
@@ -15,6 +15,8 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
15
15
  # The method will call one of the following methods on the listener:
16
16
  # * `#user_logged_in`: The first argument (String) is the URL (if any), the user should be redirected to.
17
17
  # The second argument (String) is the ticket-granting ticket. It should be stored in a cookie named "tgt".
18
+ # The third argument (Time, optional, default = nil) is for "Remember Me" functionality.
19
+ # This is the cookies expiration date. If it is `nil`, the cookie should be a session cookie.
18
20
  # * `#invalid_login_ticket` and `#invalid_login_credentials`: The first argument is a LoginTicket.
19
21
  # See {CASinoCore::Processor::LoginCredentialRequestor} for details.
20
22
  # * `#service_not_allowed`: The user tried to access a service that this CAS server is not allowed to serve.
@@ -43,7 +45,8 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
43
45
  end
44
46
 
45
47
  def user_logged_in(authentication_result)
46
- ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, @user_agent)
48
+ long_term = @params[:rememberMe]
49
+ ticket_granting_ticket = acquire_ticket_granting_ticket(authentication_result, @user_agent, long_term)
47
50
  if ticket_granting_ticket.awaiting_two_factor_authentication?
48
51
  @listener.two_factor_authentication_pending(ticket_granting_ticket.ticket)
49
52
  else
@@ -51,7 +54,11 @@ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
51
54
  url = unless @params[:service].blank?
52
55
  acquire_service_ticket(ticket_granting_ticket, @params[:service], true).service_with_ticket_url
53
56
  end
54
- @listener.user_logged_in(url, ticket_granting_ticket.ticket)
57
+ if long_term
58
+ @listener.user_logged_in(url, ticket_granting_ticket.ticket, CASinoCore::Settings.ticket_granting_ticket[:lifetime_long_term].seconds.from_now)
59
+ else
60
+ @listener.user_logged_in(url, ticket_granting_ticket.ticket)
61
+ end
55
62
  rescue ServiceNotAllowedError => e
56
63
  @listener.service_not_allowed(clean_service_url @params[:service])
57
64
  end
@@ -18,6 +18,10 @@ class CASinoCore::Processor::Logout < CASinoCore::Processor
18
18
  params ||= {}
19
19
  cookies ||= {}
20
20
  remove_ticket_granting_ticket(cookies[:tgt], user_agent)
21
- @listener.user_logged_out(params[:url])
21
+ if params[:service] && CASinoCore::Model::ServiceRule.allowed?(params[:service])
22
+ @listener.user_logged_out(params[:service], true)
23
+ else
24
+ @listener.user_logged_out(params[:url])
25
+ end
22
26
  end
23
27
  end
@@ -9,13 +9,14 @@ module CASinoCore
9
9
  lifetime: 600
10
10
  },
11
11
  ticket_granting_ticket: {
12
- lifetime: 86400
12
+ lifetime: 86400,
13
+ lifetime_long_term: 864000
13
14
  },
14
15
  service_ticket: {
15
16
  lifetime_unconsumed: 300,
16
17
  lifetime_consumed: 86400,
17
18
  single_sign_out_notification: {
18
- timeout: 10
19
+ timeout: 5
19
20
  }
20
21
  },
21
22
  proxy_ticket: {
@@ -1,3 +1,3 @@
1
1
  module CASinoCore
2
- VERSION = '1.3.5'
2
+ VERSION = '1.4.0'
3
3
  end
@@ -23,7 +23,7 @@ describe CASinoCore::Model::ServiceTicket::SingleSignOutNotifier do
23
23
 
24
24
  it 'sets the timeout values' do
25
25
  [:read_timeout=, :open_timeout=].each do |timeout|
26
- Net::HTTP.any_instance.should_receive(timeout).with(10)
26
+ Net::HTTP.any_instance.should_receive(timeout).with(CASinoCore::Settings.service_ticket[:single_sign_out_notification][:timeout])
27
27
  end
28
28
  notifier.notify
29
29
  end
@@ -106,11 +106,11 @@ describe CASinoCore::Model::ServiceTicket do
106
106
  described_class::SingleSignOutNotifier.any_instance.stub(:notify).and_return(false)
107
107
  end
108
108
 
109
- it 'does not delete the service ticket' do
109
+ it 'does delete the service ticket anyway' do
110
110
  consumed_ticket
111
111
  lambda {
112
112
  consumed_ticket.destroy
113
- }.should_not change(CASinoCore::Model::ServiceTicket, :count)
113
+ }.should change(CASinoCore::Model::ServiceTicket, :count).by(-1)
114
114
  end
115
115
  end
116
116
  end
@@ -4,9 +4,10 @@ require 'useragent'
4
4
  describe CASinoCore::Model::TicketGrantingTicket do
5
5
  let(:ticket_granting_ticket) { FactoryGirl.create :ticket_granting_ticket }
6
6
  let(:service_ticket) { FactoryGirl.create :service_ticket, ticket_granting_ticket: ticket_granting_ticket }
7
- let(:consumed_service_ticket) { FactoryGirl.create :service_ticket, :consumed, ticket_granting_ticket: ticket_granting_ticket }
8
7
 
9
8
  describe '#destroy' do
9
+ let!(:consumed_service_ticket) { FactoryGirl.create :service_ticket, :consumed, ticket_granting_ticket: ticket_granting_ticket }
10
+
10
11
  context 'when notification for a service ticket fails' do
11
12
  before(:each) do
12
13
  CASinoCore::Model::ServiceTicket::SingleSignOutNotifier.any_instance.stub(:notify).and_return(false)
@@ -19,10 +20,10 @@ describe CASinoCore::Model::TicketGrantingTicket do
19
20
  }.should change(CASinoCore::Model::ProxyGrantingTicket, :count).by(-1)
20
21
  end
21
22
 
22
- it 'nullifies depending service tickets' do
23
+ it 'deletes depending service tickets' do
23
24
  lambda {
24
25
  ticket_granting_ticket.destroy
25
- }.should change { consumed_service_ticket.reload.ticket_granting_ticket_id }.from(ticket_granting_ticket.id).to(nil)
26
+ }.should change(CASinoCore::Model::ServiceTicket, :count).by(-1)
26
27
  end
27
28
  end
28
29
  end
@@ -82,6 +83,32 @@ describe CASinoCore::Model::TicketGrantingTicket do
82
83
  end
83
84
 
84
85
  describe '#expired?' do
86
+ context 'with a long-term ticket' do
87
+ context 'when almost expired' do
88
+ before(:each) do
89
+ ticket_granting_ticket.created_at = 9.days.ago
90
+ ticket_granting_ticket.long_term = true
91
+ ticket_granting_ticket.save!
92
+ end
93
+
94
+ it 'returns false' do
95
+ ticket_granting_ticket.expired?.should == false
96
+ end
97
+ end
98
+
99
+ context 'when expired' do
100
+ before(:each) do
101
+ ticket_granting_ticket.created_at = 30.days.ago
102
+ ticket_granting_ticket.long_term = true
103
+ ticket_granting_ticket.save!
104
+ end
105
+
106
+ it 'returns true' do
107
+ ticket_granting_ticket.expired?.should == true
108
+ end
109
+ end
110
+ end
111
+
85
112
  context 'with an expired ticket' do
86
113
  before(:each) do
87
114
  ticket_granting_ticket.created_at = 25.hours.ago
@@ -111,5 +138,24 @@ describe CASinoCore::Model::TicketGrantingTicket do
111
138
  end.should change(described_class, :count).by(-1)
112
139
  described_class.find_by_ticket(ticket_granting_ticket.ticket).should be_false
113
140
  end
141
+
142
+ it 'does not delete almost expired long-term ticket-granting tickets' do
143
+ ticket_granting_ticket.created_at = 9.days.ago
144
+ ticket_granting_ticket.long_term = true
145
+ ticket_granting_ticket.save!
146
+ lambda do
147
+ described_class.cleanup
148
+ end.should_not change(described_class, :count)
149
+ end
150
+
151
+ it 'does delete expired long-term ticket-granting tickets' do
152
+ ticket_granting_ticket.created_at = 30.days.ago
153
+ ticket_granting_ticket.long_term = true
154
+ ticket_granting_ticket.save!
155
+ lambda do
156
+ described_class.cleanup
157
+ end.should change(described_class, :count).by(-1)
158
+ described_class.find_by_ticket(ticket_granting_ticket.ticket).should be_false
159
+ end
114
160
  end
115
161
  end
@@ -41,6 +41,21 @@ describe CASinoCore::Processor::LoginCredentialAcceptor do
41
41
  listener.stub(:user_logged_in)
42
42
  end
43
43
 
44
+ context 'with rememberMe set' do
45
+ let(:login_data_with_remember_me) { login_data.merge(rememberMe: true) }
46
+
47
+ it 'calls the #user_logged_in method on the listener with an expiration date set' do
48
+ listener.should_receive(:user_logged_in).with(/^#{service}\/\?ticket=ST\-/, /^TGC\-/, kind_of(Time))
49
+ processor.process(login_data_with_remember_me)
50
+ end
51
+
52
+ it 'creates a long-term ticket-granting ticket' do
53
+ processor.process(login_data_with_remember_me)
54
+ tgt = CASinoCore::Model::TicketGrantingTicket.last
55
+ tgt.long_term.should == true
56
+ end
57
+ end
58
+
44
59
  context 'with two-factor authentication enabled' do
45
60
  let(:user) { CASinoCore::Model::User.create! username: username, authenticator: authenticator }
46
61
  let!(:two_factor_authenticator) { FactoryGirl.create :two_factor_authenticator, user: user }
@@ -35,6 +35,29 @@ describe CASinoCore::Processor::Logout do
35
35
  processor.process(params, cookies, user_agent)
36
36
  end
37
37
  end
38
+
39
+ context 'with a service' do
40
+ let(:params) { { :service => url } }
41
+ let(:url) { 'http://www.example.org' }
42
+
43
+ context '(whitelisted)' do
44
+ it 'calls the #user_logged_out method on the listener and passes the URL and the redirect_immediate flag' do
45
+ listener.should_receive(:user_logged_out).with(url, true)
46
+ processor.process(params, cookies, user_agent)
47
+ end
48
+ end
49
+
50
+ context '(not whitelisted)' do
51
+ before(:each) do
52
+ FactoryGirl.create :service_rule, :regex, url: '^https://.*'
53
+ end
54
+
55
+ it 'calls the #user_logged_out method on the listener and passes no URL' do
56
+ listener.should_receive(:user_logged_out).with(nil)
57
+ processor.process(params, cookies, user_agent)
58
+ end
59
+ end
60
+ end
38
61
  end
39
62
 
40
63
  context 'with an invlaid ticket-granting ticket' do
@@ -29,6 +29,21 @@ require 'spec_helper'
29
29
  end
30
30
 
31
31
  context 'with an unconsumed service ticket' do
32
+ context 'issued from a long_term ticket-granting ticket' do
33
+ before(:each) do
34
+ tgt = service_ticket.ticket_granting_ticket
35
+ tgt.long_term = true
36
+ tgt.save!
37
+ end
38
+
39
+ it 'calls the #validation_succeeded method on the listener' do
40
+ listener.should_receive(:validation_succeeded).with(
41
+ /<cas\:longTermAuthenticationRequestTokenUsed>true<\/cas\:longTermAuthenticationRequestTokenUsed>/
42
+ )
43
+ processor.process(parameters)
44
+ end
45
+ end
46
+
32
47
  context 'without renew flag' do
33
48
  it 'consumes the service ticket' do
34
49
  processor.process(parameters)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: casino_core
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.5
4
+ version: 1.4.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -36,7 +36,7 @@ cert_chain:
36
36
  b1VSdnUwRzgvWXlIVUFtSVUvV0tyanIxYmdjZjFWUnYKUjRLRDFNblVWL3Y1
37
37
  MDJwaU1sWG1qeE9XZGJLOHl2UUVIa3N1L3pqYkNqU3UrTTJrd0ZtV0dzeDVu
38
38
  eCtWZHc9PQotLS0tLUVORCBDRVJUSUZJQ0FURS0tLS0tCg==
39
- date: 2013-03-19 00:00:00.000000000 Z
39
+ date: 2013-03-24 00:00:00.000000000 Z
40
40
  dependencies:
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: rake
@@ -342,6 +342,7 @@ files:
342
342
  - db/migrate/20130203100015_create_two_factor_authenticators.rb
343
343
  - db/migrate/20130203101351_add_active_to_two_factor_authenticators.rb
344
344
  - db/migrate/20130203155008_add_awaiting_two_factor_authentication_to_ticket_granting_tickets.rb
345
+ - db/migrate/20130323111208_add_long_term_to_ticket_granting_tickets.rb
345
346
  - db/schema.rb
346
347
  - lib/casino_core.rb
347
348
  - lib/casino_core/authenticator.rb
@@ -447,15 +448,21 @@ required_ruby_version: !ruby/object:Gem::Requirement
447
448
  - - ! '>='
448
449
  - !ruby/object:Gem::Version
449
450
  version: '0'
451
+ segments:
452
+ - 0
453
+ hash: -3391276428784871857
450
454
  required_rubygems_version: !ruby/object:Gem::Requirement
451
455
  none: false
452
456
  requirements:
453
457
  - - ! '>='
454
458
  - !ruby/object:Gem::Version
455
459
  version: '0'
460
+ segments:
461
+ - 0
462
+ hash: -3391276428784871857
456
463
  requirements: []
457
464
  rubyforge_project:
458
- rubygems_version: 1.8.24
465
+ rubygems_version: 1.8.25
459
466
  signing_key:
460
467
  specification_version: 3
461
468
  summary: A CAS server core library.
metadata.gz.sig CHANGED
Binary file