casino_core 1.0.6 → 1.0.7
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/README.md +1 -1
- data/Rakefile +1 -1
- data/VERSION +1 -1
- data/casino_core.gemspec +3 -3
- data/lib/casino_core.rb +5 -0
- data/lib/casino_core/authenticator.rb +2 -0
- data/lib/casino_core/helper/authentication.rb +5 -1
- data/lib/casino_core/model/proxy_ticket.rb +1 -1
- data/lib/casino_core/model/service_ticket.rb +1 -1
- data/lib/casino_core/model/ticket_granting_ticket.rb +8 -2
- data/spec/model/proxy_ticket_spec.rb +40 -9
- data/spec/model/service_ticket_spec.rb +42 -3
- data/spec/model/ticket_granting_ticket_spec.rb +64 -1
- data/spec/processor/login_credential_acceptor_spec.rb +15 -1
- data/spec/spec_helper.rb +13 -11
- metadata +4 -4
data/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CASinoCore [](https://travis-ci.org/rbCAS/CASinoCore)
|
|
2
2
|
|
|
3
3
|
A CAS server core library.
|
|
4
4
|
|
data/Rakefile
CHANGED
|
@@ -20,7 +20,7 @@ require 'jeweler'
|
|
|
20
20
|
Jeweler::Tasks.new do |gem|
|
|
21
21
|
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
|
22
22
|
gem.name = "casino_core"
|
|
23
|
-
gem.homepage = "http://
|
|
23
|
+
gem.homepage = "http://rbcas.org/"
|
|
24
24
|
gem.license = "MIT"
|
|
25
25
|
gem.summary = "A CAS server core library."
|
|
26
26
|
gem.description = gem.summary
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
1.0.
|
|
1
|
+
1.0.7
|
data/casino_core.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = "casino_core"
|
|
8
|
-
s.version = "1.0.
|
|
8
|
+
s.version = "1.0.7"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Nils Caspar"]
|
|
12
|
-
s.date = "
|
|
12
|
+
s.date = "2013-01-01"
|
|
13
13
|
s.description = "A CAS server core library."
|
|
14
14
|
s.email = "ncaspar@me.com"
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -117,7 +117,7 @@ Gem::Specification.new do |s|
|
|
|
117
117
|
"spec/support/factories/service_ticket_factory.rb",
|
|
118
118
|
"spec/support/factories/ticket_granting_ticket_factory.rb"
|
|
119
119
|
]
|
|
120
|
-
s.homepage = "http://
|
|
120
|
+
s.homepage = "http://rbcas.org/"
|
|
121
121
|
s.licenses = ["MIT"]
|
|
122
122
|
s.require_paths = ["lib"]
|
|
123
123
|
s.rubygems_version = "1.8.24"
|
data/lib/casino_core.rb
CHANGED
|
@@ -19,6 +19,11 @@ module CASinoCore
|
|
|
19
19
|
config = YAML.load_file('config/cas.yml')[@environment].symbolize_keys
|
|
20
20
|
recursive_symbolize_keys!(config)
|
|
21
21
|
CASinoCore::Settings.init config
|
|
22
|
+
|
|
23
|
+
ActiveSupport::Inflector.inflections do |inflect|
|
|
24
|
+
inflect.acronym 'CAS'
|
|
25
|
+
inflect.acronym 'CASino'
|
|
26
|
+
end
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
private
|
|
@@ -2,6 +2,8 @@ module CASinoCore
|
|
|
2
2
|
class Authenticator
|
|
3
3
|
autoload :Static, 'casino_core/authenticator/static.rb'
|
|
4
4
|
|
|
5
|
+
class AuthenticatorError < StandardError; end
|
|
6
|
+
|
|
5
7
|
def validate(username, password)
|
|
6
8
|
raise NotImplementedError, "This method must be implemented by a class extending #{self.class}"
|
|
7
9
|
end
|
|
@@ -5,7 +5,11 @@ module CASinoCore
|
|
|
5
5
|
def validate_login_credentials(username, password)
|
|
6
6
|
authentication_result = nil
|
|
7
7
|
CASinoCore::Settings.authenticators.each do |authenticator_name, authenticator|
|
|
8
|
-
|
|
8
|
+
begin
|
|
9
|
+
data = authenticator.validate(username, password)
|
|
10
|
+
rescue CASinoCore::Authenticator::AuthenticatorError => e
|
|
11
|
+
logger.error "Authenticator '#{authenticator_name}' (#{authenticator.class}) raised an error: #{e}"
|
|
12
|
+
end
|
|
9
13
|
if data
|
|
10
14
|
authentication_result = { authenticator: authenticator_name, user_data: data }
|
|
11
15
|
logger.info("Credentials for username '#{data[:username]}' successfully validated using authenticator '#{authenticator_name}' (#{authenticator.class})")
|
|
@@ -10,8 +10,14 @@ class CASinoCore::Model::TicketGrantingTicket < ActiveRecord::Base
|
|
|
10
10
|
after_destroy :destroy_proxy_granting_tickets
|
|
11
11
|
|
|
12
12
|
def browser_info
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
unless self.user_agent.blank?
|
|
14
|
+
user_agent = UserAgent.parse(self.user_agent)
|
|
15
|
+
if user_agent.platform.nil?
|
|
16
|
+
"#{user_agent.browser}"
|
|
17
|
+
else
|
|
18
|
+
"#{user_agent.browser} (#{user_agent.platform})"
|
|
19
|
+
end
|
|
20
|
+
end
|
|
15
21
|
end
|
|
16
22
|
|
|
17
23
|
def same_user?(other_ticket)
|
|
@@ -1,32 +1,63 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe CASinoCore::Model::ProxyTicket do
|
|
4
|
-
let(:
|
|
5
|
-
ticket = described_class.new ticket: 'PT-12345', service: '
|
|
4
|
+
let(:unconsumed_ticket) {
|
|
5
|
+
ticket = described_class.new ticket: 'PT-12345', service: 'any_string_is_valid'
|
|
6
6
|
ticket.proxy_granting_ticket_id = 1
|
|
7
7
|
ticket
|
|
8
8
|
}
|
|
9
|
+
let(:consumed_ticket) {
|
|
10
|
+
ticket = described_class.new ticket: 'PT-54321', service: 'any_string_is_valid'
|
|
11
|
+
ticket.proxy_granting_ticket_id = 1
|
|
12
|
+
ticket.consumed = true
|
|
13
|
+
ticket.save!
|
|
14
|
+
ticket
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
describe '#expired?' do
|
|
18
|
+
[:unconsumed, :consumed].each do |state|
|
|
19
|
+
context "with an #{state} ticket" do
|
|
20
|
+
let(:ticket) { send("#{state}_ticket") }
|
|
21
|
+
|
|
22
|
+
context 'with an expired ticket' do
|
|
23
|
+
before(:each) do
|
|
24
|
+
ticket.created_at = (CASinoCore::Settings.service_ticket[:"lifetime_#{state}"].seconds + 1).ago
|
|
25
|
+
ticket.save!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns true' do
|
|
29
|
+
ticket.expired?.should == true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with an unexpired ticket' do
|
|
34
|
+
it 'returns false' do
|
|
35
|
+
ticket.expired?.should == false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
9
41
|
|
|
10
42
|
describe '.cleanup_unconsumed' do
|
|
11
43
|
it 'deletes expired unconsumed service tickets' do
|
|
12
|
-
|
|
13
|
-
|
|
44
|
+
unconsumed_ticket.created_at = 10.hours.ago
|
|
45
|
+
unconsumed_ticket.save!
|
|
14
46
|
lambda do
|
|
15
47
|
described_class.cleanup_unconsumed
|
|
16
48
|
end.should change(described_class, :count).by(-1)
|
|
17
|
-
described_class.find_by_ticket('
|
|
49
|
+
described_class.find_by_ticket('PT-12345').should be_false
|
|
18
50
|
end
|
|
19
51
|
end
|
|
20
52
|
|
|
21
53
|
describe '.cleanup_consumed' do
|
|
22
54
|
it 'deletes expired consumed service tickets' do
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
ticket.save!
|
|
55
|
+
consumed_ticket.created_at = 10.days.ago
|
|
56
|
+
consumed_ticket.save!
|
|
26
57
|
lambda do
|
|
27
58
|
described_class.cleanup_consumed
|
|
28
59
|
end.should change(described_class, :count).by(-1)
|
|
29
|
-
described_class.find_by_ticket('
|
|
60
|
+
described_class.find_by_ticket('PT-12345').should be_false
|
|
30
61
|
end
|
|
31
62
|
end
|
|
32
63
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
2
|
|
|
3
3
|
describe CASinoCore::Model::ServiceTicket do
|
|
4
|
-
let(:
|
|
4
|
+
let(:unconsumed_ticket) {
|
|
5
5
|
ticket = described_class.new ticket: 'ST-12345', service: 'https://example.com/cas-service'
|
|
6
6
|
ticket.ticket_granting_ticket_id = 1
|
|
7
7
|
ticket
|
|
@@ -14,10 +14,35 @@ describe CASinoCore::Model::ServiceTicket do
|
|
|
14
14
|
ticket
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
+
describe '#expired?' do
|
|
18
|
+
[:unconsumed, :consumed].each do |state|
|
|
19
|
+
context "with an #{state} ticket" do
|
|
20
|
+
let(:ticket) { send("#{state}_ticket") }
|
|
21
|
+
|
|
22
|
+
context 'with an expired ticket' do
|
|
23
|
+
before(:each) do
|
|
24
|
+
ticket.created_at = (CASinoCore::Settings.service_ticket[:"lifetime_#{state}"].seconds + 1).ago
|
|
25
|
+
ticket.save!
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
it 'returns true' do
|
|
29
|
+
ticket.expired?.should == true
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
context 'with an unexpired ticket' do
|
|
34
|
+
it 'returns false' do
|
|
35
|
+
ticket.expired?.should == false
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
17
42
|
describe '.cleanup_unconsumed' do
|
|
18
43
|
it 'deletes expired unconsumed service tickets' do
|
|
19
|
-
|
|
20
|
-
|
|
44
|
+
unconsumed_ticket.created_at = 10.hours.ago
|
|
45
|
+
unconsumed_ticket.save!
|
|
21
46
|
lambda do
|
|
22
47
|
described_class.cleanup_unconsumed
|
|
23
48
|
end.should change(described_class, :count).by(-1)
|
|
@@ -25,6 +50,20 @@ describe CASinoCore::Model::ServiceTicket do
|
|
|
25
50
|
end
|
|
26
51
|
end
|
|
27
52
|
|
|
53
|
+
describe '.cleanup_consumed_hard' do
|
|
54
|
+
before(:each) do
|
|
55
|
+
described_class::SingleSignOutNotifier.any_instance.stub(:notify).and_return(false)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
it 'deletes consumed service tickets with an unreachable Single Sign Out callback server' do
|
|
59
|
+
consumed_ticket.created_at = 10.days.ago
|
|
60
|
+
consumed_ticket.save!
|
|
61
|
+
lambda do
|
|
62
|
+
described_class.cleanup_consumed_hard
|
|
63
|
+
end.should change(described_class, :count).by(-1)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
28
67
|
describe '.cleanup_consumed' do
|
|
29
68
|
before(:each) do
|
|
30
69
|
described_class::SingleSignOutNotifier.any_instance.stub(:notify).and_return(true)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
require 'spec_helper'
|
|
2
|
+
require 'useragent'
|
|
2
3
|
|
|
3
4
|
describe CASinoCore::Model::TicketGrantingTicket do
|
|
4
|
-
let(:ticket_granting_ticket) {
|
|
5
|
+
let(:ticket_granting_ticket) { FactoryGirl.create :ticket_granting_ticket }
|
|
5
6
|
let(:service_ticket) { FactoryGirl.create :service_ticket, ticket_granting_ticket: ticket_granting_ticket }
|
|
6
7
|
let(:consumed_service_ticket) { FactoryGirl.create :service_ticket, :consumed, ticket_granting_ticket: ticket_granting_ticket }
|
|
7
8
|
|
|
@@ -25,4 +26,66 @@ describe CASinoCore::Model::TicketGrantingTicket do
|
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
29
|
+
|
|
30
|
+
describe '#browser_info' do
|
|
31
|
+
let(:user_agent) { Object.new }
|
|
32
|
+
before(:each) do
|
|
33
|
+
user_agent.stub(:browser).and_return('TestBrowser')
|
|
34
|
+
UserAgent.stub(:parse).and_return(user_agent)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
context 'without platform' do
|
|
38
|
+
before(:each) do
|
|
39
|
+
user_agent.stub(:platform).and_return(nil)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
it 'returns the browser name' do
|
|
43
|
+
ticket_granting_ticket.browser_info.should == 'TestBrowser'
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
context 'with a platform' do
|
|
48
|
+
before(:each) do
|
|
49
|
+
user_agent.stub(:platform).and_return('Linux')
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
it 'returns the browser name' do
|
|
53
|
+
ticket_granting_ticket.browser_info.should == 'TestBrowser (Linux)'
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe '#same_user?' do
|
|
59
|
+
context 'with a nil value' do
|
|
60
|
+
let(:other_ticket_granting_ticket) { nil }
|
|
61
|
+
|
|
62
|
+
it 'should return false' do
|
|
63
|
+
ticket_granting_ticket.same_user?(other_ticket_granting_ticket).should == false
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
context 'with a ticket from another user' do
|
|
68
|
+
let(:other_ticket_granting_ticket) { FactoryGirl.create :ticket_granting_ticket, username: 'bla' }
|
|
69
|
+
|
|
70
|
+
it 'should return false' do
|
|
71
|
+
ticket_granting_ticket.same_user?(other_ticket_granting_ticket).should == false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
context 'with a ticket from another authenticator' do
|
|
76
|
+
let(:other_ticket_granting_ticket) { FactoryGirl.create :ticket_granting_ticket, authenticator: 'bla' }
|
|
77
|
+
|
|
78
|
+
it 'should return false' do
|
|
79
|
+
ticket_granting_ticket.same_user?(other_ticket_granting_ticket).should == false
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
context 'with a ticket from the same user and authenticator' do
|
|
84
|
+
let(:other_ticket_granting_ticket) { FactoryGirl.create :ticket_granting_ticket }
|
|
85
|
+
|
|
86
|
+
it 'should return true' do
|
|
87
|
+
ticket_granting_ticket.same_user?(other_ticket_granting_ticket).should == true
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
28
91
|
end
|
|
@@ -32,18 +32,32 @@ describe CASinoCore::Processor::LoginCredentialAcceptor do
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
context 'with valid credentials' do
|
|
35
|
+
let(:service) { 'https://www.example.org' }
|
|
35
36
|
let(:login_data) { { lt: login_ticket.ticket, username: 'testuser', password: 'foobar123', service: service } }
|
|
36
37
|
|
|
37
38
|
before(:each) do
|
|
38
39
|
listener.stub(:user_logged_in)
|
|
39
40
|
end
|
|
40
41
|
|
|
42
|
+
context 'when all authenticators raise an error' do
|
|
43
|
+
before(:each) do
|
|
44
|
+
CASinoCore::Authenticator::Static.any_instance.stub(:validate) do
|
|
45
|
+
raise CASinoCore::Authenticator::AuthenticatorError, 'error123'
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
it 'calls the #invalid_login_credentials method on the listener' do
|
|
50
|
+
listener.should_receive(:invalid_login_credentials).with(kind_of(CASinoCore::Model::LoginTicket))
|
|
51
|
+
processor.process(login_data)
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
41
55
|
context 'without a service' do
|
|
42
56
|
let(:service) { nil }
|
|
43
57
|
|
|
44
58
|
it 'calls the #user_logged_in method on the listener' do
|
|
45
59
|
listener.should_receive(:user_logged_in).with(nil, /^TGC\-/)
|
|
46
|
-
processor.process(
|
|
60
|
+
processor.process(login_data)
|
|
47
61
|
end
|
|
48
62
|
|
|
49
63
|
it 'generates a ticket-granting ticket' do
|
data/spec/spec_helper.rb
CHANGED
|
@@ -1,8 +1,20 @@
|
|
|
1
|
+
require 'active_support/core_ext'
|
|
2
|
+
require 'simplecov'
|
|
3
|
+
|
|
4
|
+
SimpleCov.start do
|
|
5
|
+
add_filter '/spec'
|
|
6
|
+
base_path = "#{File.dirname(__FILE__)}/../"
|
|
7
|
+
Dir["#{base_path}lib/casino_core/*.rb"].each do |f|
|
|
8
|
+
f.gsub!(/\A#{base_path}(.+)\.rb\z/, '\1')
|
|
9
|
+
name = File.basename(f).humanize.pluralize
|
|
10
|
+
add_group name, f
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
1
14
|
require 'database_cleaner'
|
|
2
15
|
require 'logger'
|
|
3
16
|
require 'webmock/rspec'
|
|
4
17
|
require 'casino_core'
|
|
5
|
-
require 'simplecov'
|
|
6
18
|
|
|
7
19
|
# This file was generated by the `rspec --init` command. Conventionally, all
|
|
8
20
|
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
|
@@ -24,16 +36,6 @@ RSpec.configure do |config|
|
|
|
24
36
|
# --seed 1234
|
|
25
37
|
config.order = 'random'
|
|
26
38
|
|
|
27
|
-
SimpleCov.start do
|
|
28
|
-
add_filter '/spec'
|
|
29
|
-
base_path = "#{File.dirname(__FILE__)}/../"
|
|
30
|
-
Dir["#{base_path}lib/casino_core/*.rb"].each do |f|
|
|
31
|
-
f.gsub!(/\A#{base_path}(.+)\.rb\z/, '\1')
|
|
32
|
-
name = File.basename(f).humanize.pluralize
|
|
33
|
-
add_group name, f
|
|
34
|
-
end
|
|
35
|
-
end
|
|
36
|
-
|
|
37
39
|
CASinoCore.setup 'test'
|
|
38
40
|
CASinoCore::Settings.logger.level = ::Logger::Severity::UNKNOWN
|
|
39
41
|
|
metadata
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name: casino_core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
4
|
prerelease:
|
|
5
|
-
version: 1.0.
|
|
5
|
+
version: 1.0.7
|
|
6
6
|
platform: ruby
|
|
7
7
|
authors:
|
|
8
8
|
- Nils Caspar
|
|
@@ -10,7 +10,7 @@ autorequire:
|
|
|
10
10
|
bindir: bin
|
|
11
11
|
cert_chain: []
|
|
12
12
|
|
|
13
|
-
date:
|
|
13
|
+
date: 2013-01-01 00:00:00 Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: activerecord
|
|
@@ -275,7 +275,7 @@ files:
|
|
|
275
275
|
- spec/support/factories/proxy_ticket_factory.rb
|
|
276
276
|
- spec/support/factories/service_ticket_factory.rb
|
|
277
277
|
- spec/support/factories/ticket_granting_ticket_factory.rb
|
|
278
|
-
homepage: http://
|
|
278
|
+
homepage: http://rbcas.org/
|
|
279
279
|
licenses:
|
|
280
280
|
- MIT
|
|
281
281
|
post_install_message:
|
|
@@ -288,7 +288,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
288
288
|
requirements:
|
|
289
289
|
- - ">="
|
|
290
290
|
- !ruby/object:Gem::Version
|
|
291
|
-
hash:
|
|
291
|
+
hash: 903042986350696611
|
|
292
292
|
segments:
|
|
293
293
|
- 0
|
|
294
294
|
version: "0"
|