casino_core 0.0.1

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 (58) hide show
  1. data/.document +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +48 -0
  4. data/.travis.yml +3 -0
  5. data/Gemfile +24 -0
  6. data/Gemfile.lock +64 -0
  7. data/LICENSE.txt +20 -0
  8. data/README.md +19 -0
  9. data/Rakefile +44 -0
  10. data/VERSION +1 -0
  11. data/casino_core.gemspec +126 -0
  12. data/config/cas.yml +26 -0
  13. data/config/database.yml +18 -0
  14. data/db/migrate/20121112154930_create_ticket_granting_tickets.rb +11 -0
  15. data/db/migrate/20121112160009_create_login_tickets.rb +9 -0
  16. data/db/migrate/20121112165804_ticket_should_not_be_null.rb +5 -0
  17. data/db/migrate/20121122180310_add_user_agent_to_ticket_granting_tickets.rb +5 -0
  18. data/db/migrate/20121124170004_add_index_for_username_to_ticket_granting_tickets.rb +5 -0
  19. data/db/migrate/20121124183542_create_service_tickets.rb +13 -0
  20. data/db/migrate/20121124183732_add_ticket_indexes.rb +6 -0
  21. data/db/migrate/20121124195013_add_consumed_to_service_tickets.rb +5 -0
  22. data/db/migrate/20121125091934_add_issued_from_credentials_to_service_tickets.rb +5 -0
  23. data/db/migrate/20121125185415_create_proxy_granting_tickets.rb +14 -0
  24. data/db/migrate/20121125190013_tickets_should_be_unique.rb +8 -0
  25. data/db/schema.rb +61 -0
  26. data/lib/casino_core.rb +33 -0
  27. data/lib/casino_core/authenticator.rb +9 -0
  28. data/lib/casino_core/authenticator/static.rb +23 -0
  29. data/lib/casino_core/helper.rb +21 -0
  30. data/lib/casino_core/helper/browser.rb +16 -0
  31. data/lib/casino_core/helper/service_tickets.rb +30 -0
  32. data/lib/casino_core/model.rb +10 -0
  33. data/lib/casino_core/model/login_ticket.rb +11 -0
  34. data/lib/casino_core/model/proxy_granting_ticket.rb +8 -0
  35. data/lib/casino_core/model/service_ticket.rb +32 -0
  36. data/lib/casino_core/model/service_ticket/single_sign_out_notifier.rb +53 -0
  37. data/lib/casino_core/model/ticket_granting_ticket.rb +9 -0
  38. data/lib/casino_core/processor.rb +15 -0
  39. data/lib/casino_core/processor/legacy_validator.rb +49 -0
  40. data/lib/casino_core/processor/login_credential_acceptor.rb +67 -0
  41. data/lib/casino_core/processor/login_credential_requestor.rb +45 -0
  42. data/lib/casino_core/processor/logout.rb +23 -0
  43. data/lib/casino_core/processor/session_destroyer.rb +17 -0
  44. data/lib/casino_core/railtie.rb +10 -0
  45. data/lib/casino_core/rake_tasks.rb +14 -0
  46. data/lib/casino_core/settings.rb +26 -0
  47. data/lib/casino_core/tasks/cleanup.rake +26 -0
  48. data/lib/casino_core/tasks/database.rake +60 -0
  49. data/spec/authenticator/static_spec.rb +42 -0
  50. data/spec/model/login_ticket_spec.rb +16 -0
  51. data/spec/model/service_ticket_spec.rb +45 -0
  52. data/spec/processor/legacy_validator_spec.rb +87 -0
  53. data/spec/processor/login_credential_acceptor_spec.rb +70 -0
  54. data/spec/processor/login_credential_requestor_spec.rb +75 -0
  55. data/spec/processor/logout_spec.rb +55 -0
  56. data/spec/processor/session_destroyer_spec.rb +68 -0
  57. data/spec/spec_helper.rb +33 -0
  58. metadata +234 -0
@@ -0,0 +1,67 @@
1
+ require 'casino_core/processor'
2
+ require 'casino_core/helper'
3
+
4
+ class CASinoCore::Processor::LoginCredentialAcceptor < CASinoCore::Processor
5
+ include CASinoCore::Helper
6
+ include CASinoCore::Helper::ServiceTickets
7
+
8
+ def process(params = nil, cookies = nil, user_agent = nil)
9
+ params ||= {}
10
+ cookies ||= {}
11
+ if login_ticket_valid?(params[:lt])
12
+ user_data = validate_login_credentials(params[:username], params[:password])
13
+ if !user_data.nil?
14
+ ticket_granting_ticket = acquire_ticket_granting_ticket(user_data, user_agent)
15
+ url = unless params[:service].nil?
16
+ acquire_service_ticket(ticket_granting_ticket, params[:service], true).service_with_ticket_url
17
+ end
18
+ @listener.user_logged_in(url, ticket_granting_ticket.ticket)
19
+ else
20
+ @listener.invalid_login_credentials
21
+ end
22
+ else
23
+ @listener.invalid_login_ticket
24
+ end
25
+ end
26
+
27
+ private
28
+ def login_ticket_valid?(lt)
29
+ ticket = CASinoCore::Model::LoginTicket.find_by_ticket lt
30
+ if ticket.nil?
31
+ logger.info "Login ticket '#{lt}' not found"
32
+ false
33
+ elsif ticket.created_at < CASinoCore::Settings.login_ticket[:lifetime].seconds.ago
34
+ logger.info "Login ticket '#{ticket.ticket}' expired"
35
+ false
36
+ else
37
+ logger.debug "Login ticket '#{ticket.ticket}' successfully validated"
38
+ ticket.delete
39
+ true
40
+ end
41
+ end
42
+
43
+ def validate_login_credentials(username, password)
44
+ user_data = nil
45
+ CASinoCore::Settings.authenticators.each do |authenticator|
46
+ data = authenticator.validate(username, password)
47
+ if data
48
+ if data[:username].nil?
49
+ data[:username] = username
50
+ end
51
+ user_data = data
52
+ logger.info("Credentials for username '#{data[:username]}' successfully validated using #{authenticator.class}")
53
+ break
54
+ end
55
+ end
56
+ user_data
57
+ end
58
+
59
+ def acquire_ticket_granting_ticket(user_data, user_agent = nil)
60
+ CASinoCore::Model::TicketGrantingTicket.create!({
61
+ ticket: random_ticket_string('TGC'),
62
+ username: user_data[:username],
63
+ extra_attributes: user_data[:extra_attributes],
64
+ user_agent: user_agent
65
+ })
66
+ end
67
+ end
@@ -0,0 +1,45 @@
1
+ require 'casino_core/processor'
2
+ require 'casino_core/helper'
3
+
4
+ class CASinoCore::Processor::LoginCredentialRequestor < CASinoCore::Processor
5
+ include CASinoCore::Helper
6
+ include CASinoCore::Helper::ServiceTickets
7
+ include CASinoCore::Helper::Browser
8
+
9
+ def process(params = nil, cookies = nil, user_agent = nil)
10
+ params ||= {}
11
+ cookies ||= {}
12
+ request_env ||= {}
13
+ if !params[:renew] && (ticket_granting_ticket = find_valid_ticket_granting_ticket(cookies[:tgt], user_agent))
14
+ # TODO create new service ticket and url
15
+ service_url_with_ticket = unless params[:service].nil?
16
+ acquire_service_ticket(ticket_granting_ticket, params[:service], true).service_with_ticket_url
17
+ end
18
+ @listener.user_logged_in(service_url_with_ticket)
19
+ else
20
+ login_ticket = acquire_login_ticket
21
+ @listener.user_not_logged_in(login_ticket)
22
+ end
23
+ end
24
+
25
+ private
26
+ def acquire_login_ticket
27
+ ticket = CASinoCore::Model::LoginTicket.create ticket: random_ticket_string('LT')
28
+ logger.debug "Created login ticket '#{ticket.ticket}'"
29
+ ticket
30
+ end
31
+
32
+ def find_valid_ticket_granting_ticket(tgt, user_agent)
33
+ ticket_granting_ticket = CASinoCore::Model::TicketGrantingTicket.where(ticket: tgt).first
34
+ unless ticket_granting_ticket.nil?
35
+ if same_browser?(ticket_granting_ticket.user_agent, user_agent)
36
+ ticket_granting_ticket.user_agent = user_agent
37
+ ticket_granting_ticket.save!
38
+ ticket_granting_ticket
39
+ else
40
+ logger.info 'User-Agent changed: ticket-granting ticket not valid for this browser'
41
+ nil
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ require 'casino_core/processor'
2
+ require 'casino_core/helper'
3
+ require 'casino_core/model'
4
+
5
+ class CASinoCore::Processor::Logout < CASinoCore::Processor
6
+ include CASinoCore::Helper
7
+
8
+ def process(params = nil, cookies = nil)
9
+ params = params || {}
10
+ cookies ||= {}
11
+ session_destroyer = CASinoCore::Processor::SessionDestroyer.new(DummyListener.new)
12
+ session_destroyer.process(cookies[:tgt])
13
+ @listener.user_logged_out(params[:url])
14
+ end
15
+
16
+ class DummyListener
17
+ def ticket_deleted(*args)
18
+ end
19
+
20
+ def ticket_not_found(*args)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,17 @@
1
+ require 'casino_core/processor'
2
+ require 'casino_core/helper'
3
+ require 'casino_core/model'
4
+
5
+ class CASinoCore::Processor::SessionDestroyer < CASinoCore::Processor
6
+ include CASinoCore::Helper
7
+
8
+ def process(tgt)
9
+ ticket = CASinoCore::Model::TicketGrantingTicket.where(ticket: tgt).first
10
+ if ticket.nil?
11
+ @listener.ticket_not_found
12
+ else
13
+ ticket.destroy
14
+ @listener.ticket_deleted
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,10 @@
1
+ require 'casino_core'
2
+ require 'rails'
3
+
4
+ module CASinoCore
5
+ class Railtie < Rails::Railtie
6
+ rake_tasks do
7
+ CASinoCore::RakeTasks.load_tasks
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,14 @@
1
+ module CASinoCore
2
+ class RakeTasks
3
+ class << self
4
+ def load_tasks
5
+ %w(
6
+ database
7
+ cleanup
8
+ ).each do |task|
9
+ load "casino_core/tasks/#{task}.rake"
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,26 @@
1
+ require 'casino_core/authenticator'
2
+
3
+ module CASinoCore
4
+ class Settings
5
+ class << self
6
+ attr_accessor :login_ticket, :service_ticket, :authenticators
7
+ def init(config = {})
8
+ config.each do |key,value|
9
+ if respond_to?("#{key}=")
10
+ send("#{key}=", value)
11
+ end
12
+ end
13
+ end
14
+
15
+ def authenticators=(authenticators)
16
+ @authenticators = []
17
+ authenticators.each do |authenticator|
18
+ unless authenticator.is_a?(CASinoCore::Authenticator)
19
+ authenticator = authenticator[:class].constantize.new(authenticator[:options])
20
+ end
21
+ @authenticators << authenticator
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+ require 'active_record'
4
+ require 'casino_core/model'
5
+
6
+ namespace :casino_core do
7
+ namespace :cleanup do
8
+ desc 'Remove expired service tickets.'
9
+ task service_tickets: 'casino_core:db:configure_connection' do
10
+ [:consumed, :unconsumed].each do |type|
11
+ rows_affected = CASinoCore::Model::ServiceTicket.send("cleanup_#{type}")
12
+ puts "Deleted #{rows_affected} #{type} service tickets."
13
+ end
14
+ end
15
+
16
+ desc 'Remove expired login tickets.'
17
+ task login_tickets: 'casino_core:db:configure_connection' do
18
+ rows_affected = CASinoCore::Model::LoginTicket.cleanup
19
+ puts "Deleted #{rows_affected} login tickets."
20
+ end
21
+
22
+ desc 'Perform all cleanup tasks.'
23
+ task all: [:service_tickets, :login_tickets] do
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,60 @@
1
+ require 'yaml'
2
+ require 'logger'
3
+ require 'active_record'
4
+
5
+ namespace :casino_core do
6
+ namespace :db do
7
+ task :environment do
8
+ BASE_DIR = if Gem.loaded_specs['casino_core'].nil?
9
+ Dir.pwd
10
+ else
11
+ Gem.loaded_specs['casino_core'].full_gem_path
12
+ end
13
+ DATABASE_ENV = ENV['DATABASE_ENV'] || ENV['RAILS_ENV'] || 'development'
14
+ MIGRATIONS_DIR = File.join(BASE_DIR, 'db', 'migrate')
15
+ SCHEMA_PATH = ENV['SCHEMA'] || File.join(BASE_DIR, 'db', 'schema.rb')
16
+ end
17
+
18
+ task :configuration => :environment do
19
+ @config = YAML.load_file('config/database.yml')[DATABASE_ENV]
20
+ end
21
+
22
+ task :configure_connection => :configuration do
23
+ ActiveRecord::Base.establish_connection @config
24
+ ActiveRecord::Base.logger = Logger.new STDOUT if @config['logger']
25
+ end
26
+
27
+ desc 'Migrate the database (options: VERSION=x, VERBOSE=false)'
28
+ task :migrate => :configure_connection do
29
+ ActiveRecord::Migration.verbose = ENV["VERBOSE"] ? ENV["VERBOSE"] == "true" : true
30
+ ActiveRecord::Migrator.migrate(ActiveRecord::Migrator.migrations_paths, ENV["VERSION"] ? ENV["VERSION"].to_i : nil) do |migration|
31
+ ENV["SCOPE"].blank? || (ENV["SCOPE"] == migration.scope)
32
+ end
33
+ end
34
+
35
+ desc 'Rolls the schema back to the previous version (specify steps w/ STEP=n).'
36
+ task :rollback => [:environment, :load_config] do
37
+ step = ENV['STEP'] ? ENV['STEP'].to_i : 1
38
+ ActiveRecord::Migrator.rollback(ActiveRecord::Migrator.migrations_paths, step)
39
+ end
40
+
41
+ namespace :schema do
42
+ desc 'Create a db/schema.rb file that can be portably used against any DB supported by AR'
43
+ task :dump => :configure_connection do
44
+ require 'active_record/schema_dumper'
45
+ File.open(SCHEMA_PATH, "w:utf-8") do |file|
46
+ ActiveRecord::SchemaDumper.dump(ActiveRecord::Base.connection, file)
47
+ end
48
+ end
49
+
50
+ desc 'Load a schema.rb file into the database'
51
+ task :load => :configure_connection do
52
+ if File.exists?(SCHEMA_PATH)
53
+ load(SCHEMA_PATH)
54
+ else
55
+ abort %{#{SCHEMA_PATH} doesn't exist yet.}
56
+ end
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe CASinoCore::Authenticator::Static do
4
+ subject {
5
+ CASinoCore::Authenticator::Static.new({
6
+ users: {
7
+ user: {
8
+ password: 'testing123',
9
+ fullname: 'Example User'
10
+ }
11
+ }
12
+ })
13
+ }
14
+
15
+ context '#validate' do
16
+ context 'with invalid credentials' do
17
+ it 'returns false for an unknown username' do
18
+ subject.validate('foobar', 'test').should == false
19
+ end
20
+
21
+ it 'returns false for a known username with wrong password' do
22
+ subject.validate('user', 'test').should == false
23
+ end
24
+ end
25
+
26
+ context 'with valid credentials' do
27
+ let(:result) { subject.validate('user', 'testing123') }
28
+
29
+ it 'does not return false' do
30
+ result.should_not == false
31
+ end
32
+
33
+ it 'returns the username' do
34
+ result[:username].should == 'user'
35
+ end
36
+
37
+ it 'returns extra attributes' do
38
+ result[:extra_attributes][:fullname].should == 'Example User'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,16 @@
1
+ require 'spec_helper'
2
+
3
+ describe CASinoCore::Model::LoginTicket do
4
+ describe '.cleanup' do
5
+ it 'deletes expired login tickets' do
6
+ ticket = described_class.new ticket: 'LT-12345'
7
+ ticket.save!
8
+ ticket.created_at = 10.hours.ago
9
+ ticket.save!
10
+ lambda do
11
+ described_class.cleanup
12
+ end.should change(described_class, :count).by(-1)
13
+ described_class.find_by_ticket('LT-12345').should be_false
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ describe CASinoCore::Model::ServiceTicket do
4
+ let(:ticket) {
5
+ ticket = described_class.new ticket: 'ST-12345', service: 'https://example.com/cas-service'
6
+ ticket.ticket_granting_ticket_id = 1
7
+ ticket
8
+ }
9
+
10
+ describe '.cleanup_unconsumed' do
11
+ it 'deletes expired unconsumed service tickets' do
12
+ ticket.created_at = 10.hours.ago
13
+ ticket.save!
14
+ lambda do
15
+ described_class.cleanup_unconsumed
16
+ end.should change(described_class, :count).by(-1)
17
+ described_class.find_by_ticket('ST-12345').should be_false
18
+ end
19
+ end
20
+
21
+ describe '.cleanup_consumed' do
22
+ before(:each) do
23
+ described_class::SingleSignOutNotifier.any_instance.stub(:notify).and_return(true)
24
+ end
25
+
26
+ it 'deletes expired consumed service tickets' do
27
+ ticket.consumed = true
28
+ ticket.created_at = 10.days.ago
29
+ ticket.save!
30
+ lambda do
31
+ described_class.cleanup_consumed
32
+ end.should change(described_class, :count).by(-1)
33
+ described_class.find_by_ticket('ST-12345').should be_false
34
+ end
35
+ end
36
+
37
+ describe '.destroy' do
38
+ it 'sends out a single sign out notification' do
39
+ described_class::SingleSignOutNotifier.any_instance.should_receive(:notify).and_return(true)
40
+ ticket.consumed = true
41
+ ticket.save!
42
+ ticket.destroy
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,87 @@
1
+ require 'spec_helper'
2
+
3
+ describe CASinoCore::Processor::LegacyValidator do
4
+ describe '#process' do
5
+ let(:listener) { Object.new }
6
+ let(:processor) { described_class.new(listener) }
7
+ let(:user_agent) { 'TestBrowser 1.0' }
8
+ let(:ticket_granting_ticket) {
9
+ CASinoCore::Model::TicketGrantingTicket.create!({
10
+ ticket: 'TGC-HXdkW233TsRtiqYGq4b8U7',
11
+ username: 'test',
12
+ extra_attributes: nil,
13
+ user_agent: user_agent
14
+ })
15
+ }
16
+ let(:service) { 'https://example.com/cas-service' }
17
+ let(:service_ticket) { ticket_granting_ticket.service_tickets.create! ticket: 'ST-2nOcXx56dtPTsB069yYf0h', service: service }
18
+ let(:parameters) { { service: service, ticket: service_ticket.ticket }}
19
+
20
+ before(:each) do
21
+ listener.stub(:validation_failed)
22
+ listener.stub(:validation_succeeded)
23
+ end
24
+
25
+ context 'with an unconsumed service ticket' do
26
+ context 'without renew flag' do
27
+ it 'consumes the service ticket' do
28
+ processor.process(parameters)
29
+ service_ticket.reload
30
+ service_ticket.consumed.should == true
31
+ end
32
+
33
+ it 'calls the #validation_succeeded method on the listener' do
34
+ listener.should_receive(:validation_succeeded).with("yes\ntest\n")
35
+ processor.process(parameters)
36
+ end
37
+ end
38
+
39
+ context 'with renew flag' do
40
+ let(:parameters) { { service: service, ticket: service_ticket.ticket, renew: 'true' }}
41
+
42
+ context 'with a service ticket without issued_from_credentials flag' do
43
+ it 'consumes the service ticket' do
44
+ processor.process(parameters)
45
+ service_ticket.reload
46
+ service_ticket.consumed.should == true
47
+ end
48
+
49
+ it 'calls the #validation_failed method on the listener' do
50
+ listener.should_receive(:validation_failed).with("no\n\n")
51
+ processor.process(parameters)
52
+ end
53
+ end
54
+
55
+ context 'with a service ticket with issued_from_credentials flag' do
56
+ before(:each) do
57
+ service_ticket.issued_from_credentials = true
58
+ service_ticket.save!
59
+ end
60
+
61
+ it 'consumes the service ticket' do
62
+ processor.process(parameters)
63
+ service_ticket.reload
64
+ service_ticket.consumed.should == true
65
+ end
66
+
67
+ it 'calls the #validation_succeeded method on the listener' do
68
+ listener.should_receive(:validation_succeeded).with("yes\ntest\n")
69
+ processor.process(parameters)
70
+ end
71
+ end
72
+ end
73
+ end
74
+
75
+ context 'with a consumed service ticket' do
76
+ before(:each) do
77
+ service_ticket.consumed = true
78
+ service_ticket.save!
79
+ end
80
+
81
+ it 'calls the #validation_failed method on the listener' do
82
+ listener.should_receive(:validation_failed).with("no\n\n")
83
+ processor.process(parameters)
84
+ end
85
+ end
86
+ end
87
+ end