From 8ae828f160deff621204cca109fade2d7884662e Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Fri, 28 Feb 2014 11:22:58 +0100 Subject: [PATCH 1/6] 1) Add validation tests (+ error class in order to pass) 2) Fixed login ticket lifetime bug (maximum_unused_login_ticket_lifetime was not used in login ticket validation) 3) Add remember_me functionality in the core 4) Change how service tickets are generated. Now each service tickets inherits from TicketGrantingTicket in a way to imitate activerecord's belongs_to/has_many. 5) Other minor improvements 6) Tested with rubycas-server-rails engine --- .gitignore | 2 + Gemfile | 18 --- lib/rubycas-server-core.rb | 1 + .../adapters/in_memory/service_ticket.rb | 2 +- .../adapters/in_memory/storage.rb | 5 + .../in_memory/ticket_granting_ticket.rb | 7 +- lib/rubycas-server-core/error.rb | 18 +++ lib/rubycas-server-core/tickets.rb | 16 ++- .../tickets/validations.rb | 15 +- rubycas-server-core.gemspec | 4 + spec/config/config.yml | 1 + .../tickets/validations_spec.rb | 128 ++++++++++++------ spec/spec_helper.rb | 1 + 13 files changed, 148 insertions(+), 70 deletions(-) create mode 100644 lib/rubycas-server-core/error.rb diff --git a/.gitignore b/.gitignore index 9028aad..81e7255 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ *.gem *.rbc +log .bundle .config .yardoc @@ -16,3 +17,4 @@ test/tmp test/version_tmp tmp *.DS_Store +*.db diff --git a/Gemfile b/Gemfile index 9effe0a..851fabc 100644 --- a/Gemfile +++ b/Gemfile @@ -1,20 +1,2 @@ source 'https://rubygems.org' gemspec - -group :development do - # for gems that are nice in development - # but don't break the build when missing - # Example: debugger - gem 'debugger' - gem "guard" - gem "guard-rspec" - - gem 'rb-inotify', :require => false - gem 'rb-fsevent', :require => false - gem 'rb-fchange', :require => false -end - -group :test do - gem 'rake' - gem 'rspec' -end diff --git a/lib/rubycas-server-core.rb b/lib/rubycas-server-core.rb index 2fa2804..cdf9794 100644 --- a/lib/rubycas-server-core.rb +++ b/lib/rubycas-server-core.rb @@ -1,6 +1,7 @@ require "logger" require "r18n-core" require "rubycas-server-core/version" +require "rubycas-server-core/error" require "rubycas-server-core/authenticator" require "rubycas-server-core/settings" require "rubycas-server-core/database" diff --git a/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb index 0cc7e0d..d8c6664 100644 --- a/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb +++ b/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb @@ -2,7 +2,7 @@ module RubyCAS module Server module Core module Tickets - class ServiceTicket < Storage + class ServiceTicket < RubyCAS::Server::Core::Tickets::TicketGrantingTicket attr_accessor :id, :ticket, :consumed, :client_hostname, :username, :created_at, :updated_at, :proxy_granting_ticket, diff --git a/lib/rubycas-server-core/adapters/in_memory/storage.rb b/lib/rubycas-server-core/adapters/in_memory/storage.rb index 37464f9..b8d82f2 100644 --- a/lib/rubycas-server-core/adapters/in_memory/storage.rb +++ b/lib/rubycas-server-core/adapters/in_memory/storage.rb @@ -16,6 +16,11 @@ def save return true end + def save! + self.class.storage[@id] = self + return true + end + end end end diff --git a/lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb index 03d2e87..d61a3e4 100644 --- a/lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb +++ b/lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb @@ -5,7 +5,7 @@ module Tickets class TicketGrantingTicket < Storage attr_accessor :id, :ticket, :client_hostname, :username, :extra_attributes, :service_tickets, :proxy_tickets, - :created_at, :updated_at + :remember_me, :created_at, :updated_at def initialize(tgt = {}) @id = SecureRandom.uuid @@ -15,6 +15,7 @@ def initialize(tgt = {}) @extra_attributes = tgt[:extra_attributes] @service_tickets = tgt[:service_tickets] @proxy_tickets = tgt[:proxy_tickets] + @remember_me = tgt[:remember_me] @created_at = DateTime.now @updated_at = DateTime.now super() @@ -31,6 +32,10 @@ def expired?(max_lifetime) lifetime = Time.now.to_i - created_at.to_time.to_i lifetime > max_lifetime end + + def service_tickets + ServiceTicket + end end end end diff --git a/lib/rubycas-server-core/error.rb b/lib/rubycas-server-core/error.rb new file mode 100644 index 0000000..34e5159 --- /dev/null +++ b/lib/rubycas-server-core/error.rb @@ -0,0 +1,18 @@ +module RubyCAS::Server::Core + # TODO: Add better dependency injection + # TODO: add predefined messages/errors + module Error + class Error + attr_reader :code, :message + + def initialize(code, message) + @code = code + @message = message + end + + def to_s + message + end + end + end +end diff --git a/lib/rubycas-server-core/tickets.rb b/lib/rubycas-server-core/tickets.rb index 34708d6..8aafda1 100644 --- a/lib/rubycas-server-core/tickets.rb +++ b/lib/rubycas-server-core/tickets.rb @@ -10,7 +10,7 @@ def self.generate_login_ticket(client) lt = LoginTicket.new lt.ticket = "LT-" + Util.random_string lt.client_hostname = client - if lt.save + if lt.save! $LOG.debug("Login ticket '#{lt.ticket} has been created for '#{lt.client_hostname}'") return lt else @@ -24,13 +24,19 @@ def self.generate_login_ticket(client) # The optional 'extra_attributes' parameter takes a hash of additional attributes # that will be sent along with the username in the CAS response to subsequent # validation requests from clients. - def self.generate_ticket_granting_ticket(username, client, extra_attributes = {}) + def self.generate_ticket_granting_ticket( + username, + client, + remember_me = false, + extra_attributes = {} + ) tgt = TicketGrantingTicket.new tgt.ticket = "TGC-" + Util.random_string tgt.username = username - tgt.extra_attributes = extra_attributes + tgt.remember_me = remember_me + tgt.extra_attributes = extra_attributes.to_s tgt.client_hostname = client - if tgt.save + if tgt.save! $LOG.debug("Generated ticket granting ticket '#{tgt.ticket}' for user" + " '#{tgt.username}' at '#{tgt.client_hostname}'" + (extra_attributes.empty? ? "" : " with extra attributes #{extra_attributes.inspect}")) @@ -41,7 +47,7 @@ def self.generate_ticket_granting_ticket(username, client, extra_attributes = {} end def self.generate_service_ticket(service, username, tgt, client) - st = ServiceTicket.new + st = tgt.service_tickets.new st.ticket = "ST-" + Util.random_string st.service = service st.username = username diff --git a/lib/rubycas-server-core/tickets/validations.rb b/lib/rubycas-server-core/tickets/validations.rb index 74b0931..b2638c9 100644 --- a/lib/rubycas-server-core/tickets/validations.rb +++ b/lib/rubycas-server-core/tickets/validations.rb @@ -1,6 +1,9 @@ +require "rubycas-server-core/error" + module RubyCAS::Server::Core::Tickets module Validations include R18n::Helpers + include RubyCAS::Server::Core::Error # Validate login ticket # @@ -17,11 +20,11 @@ def validate_login_ticket(ticket) if lt.consumed? error = t.error.login_ticket_already_used $LOG.warn "Login ticket '#{ticket}' already consumed!" - elsif not lt.expired?(RubyCAS::Server::Core::Settings.maximum_unused_service_ticket_lifetime) + elsif not lt.expired?(RubyCAS::Server::Core::Settings.maximum_unused_login_ticket_lifetime) $LOG.info "Login ticket '#{ticket}' successfully validated" lt.consume! success = true - elsif lt.expired?(RubyCAS::Server::Core::Settings.maximum_unused_service_ticket_lifetime) + elsif lt.expired?(RubyCAS::Server::Core::Settings.maximum_unused_login_ticket_lifetime) error = t.error.login_timeout $LOG.warn "Expired login ticket '#{ticket}'" end @@ -38,7 +41,13 @@ def validate_ticket_granting_ticket(ticket) $LOG.debug "No ticket granting ticket given." if ticket.nil? if tgt = TicketGrantingTicket.find_by_ticket(ticket) - if tgt.expired?(RubyCAS::Server::Core::Settings.maximum_session_lifetime) + if tgt.remember_me + max_lifetime = RubyCAS::Server::Core::Settings.maximum_session_lifetime + else + max_lifetime = RubyCAS::Server::Core::Settings.maximum_remember_me_lifetime + end + + if tgt.expired?(max_lifetime) tgt.destroy error = "Your session has expired. Please log in again." $LOG.info "Ticket granting ticket '#{ticket}' for user '#{tgt.username}' expired." diff --git a/rubycas-server-core.gemspec b/rubycas-server-core.gemspec index 033fe05..5eaf9cb 100644 --- a/rubycas-server-core.gemspec +++ b/rubycas-server-core.gemspec @@ -19,4 +19,8 @@ Gem::Specification.new do |gem| gem.add_dependency "r18n-core" gem.add_dependency "activesupport", ">= 3.0" + + gem.add_development_dependency "rake" + gem.add_development_dependency "rspec" + gem.add_development_dependency "bundler" end diff --git a/spec/config/config.yml b/spec/config/config.yml index cf7cfe1..a549e60 100644 --- a/spec/config/config.yml +++ b/spec/config/config.yml @@ -28,6 +28,7 @@ maximum_unused_login_ticket_lifetime: 300 maximum_unused_service_ticket_lifetime: 300 maximum_session_lifetime: 172800 +maximum_remember_me_lifetime: 604800 downcase_username: true default_locale: en diff --git a/spec/rubycas-server-core/tickets/validations_spec.rb b/spec/rubycas-server-core/tickets/validations_spec.rb index 3833dbc..f95fe23 100644 --- a/spec/rubycas-server-core/tickets/validations_spec.rb +++ b/spec/rubycas-server-core/tickets/validations_spec.rb @@ -1,62 +1,106 @@ require "spec_helper" describe RubyCAS::Server::Core::Tickets::Validations do + before(:all) do + Tickets = RubyCAS::Server::Core::Tickets + end + before do - RubyCAS::Server::Core.setup("spec/config/config.yml") - klass = Class.new { + RubyCAS::Server::Core.setup("spec/config/config.yml") + klass = Class.new { include RubyCAS::Server::Core::Tickets include RubyCAS::Server::Core::Tickets::Validations - } - @cas = klass.new - @client_hostname = "myhost.test" - Tickets = RubyCAS::Server::Core::Tickets + } + @cas = klass.new + @client_hostname = "myhost.test" end - describe "validate login ticket" do - it "should validate login ticket" do - @lt = Tickets.generate_login_ticket(@client_hostname) - success, error = @cas.validate_login_ticket(@lt.ticket) - success.should be_true - error.should be_nil - end - end + describe "validations" - describe "validate ticket_granting_ticket(username, extra_attributes = {})" do - before do - @username = 'myuser' - @client_hostname = "myhost.test" - @tgt = Tickets.generate_ticket_granting_ticket(@username, @client_hostname) - end + describe "#validate_login_ticket" do + context "with valid ticket" do + it "should validate login ticket" do + @lt = Tickets.generate_login_ticket(@client_hostname) + success, error = @cas.validate_login_ticket(@lt.ticket) + success.should be_true + error.should be_nil + end + end - it "should validate ticket granting ticket" do - success, error = @cas.validate_ticket_granting_ticket(@tgt.ticket) - success.should be_true - error.should be_nil + context "with invalid ticket" do + it "should not validate login ticket" do + @lt = Tickets.generate_login_ticket(@client_hostname) + success, error = @cas.validate_login_ticket("#{@lt.ticket}random") + expect(success).to be false + expect(error).not_to be nil + end + end end - end - describe "validate service_ticket(service, username, tgt)" do - before do - @username = 'testuser' - @client_hostname = "myhost.test" - @service = 'myservice.test' - @tgt = Tickets.generate_ticket_granting_ticket(@username, @client_hostname) - @st = Tickets.generate_service_ticket(@service, @username, @tgt, @client_hostname) - end + describe "#validate_ticket_granting_ticket(username, extra_attributes = {})" do + before do + @username = 'myuser' + @client_hostname = "myhost.test" + @tgt = Tickets.generate_ticket_granting_ticket(@username, @client_hostname) + end + + context "with valid tgt" do + it "should validate ticket granting ticket" do + success, error = @cas.validate_ticket_granting_ticket(@tgt.ticket) + expect(success).to eq @tgt + expect(error).to eq nil + end + end - it "should validate service ticket" do - success, error = @cas.validate_service_ticket(@service, @st.ticket) - success.should be_true - error.should be_nil + context "with invalid gt" do + it "should not validate ticket granting ticket" do + success, error = @cas.validate_ticket_granting_ticket("#{@tgt.ticket}random") + expect(success).to eq nil + expect(error).not_to eq nil + end + end end - end - describe "validate proxy_ticket(target_service, pgt)" do + describe "validate service_ticket(service, username, tgt)" do + context "with valid ticket" do + before do + @username = 'testuser' + @client_hostname = "myhost.test" + @service = 'myservice.test' + @tgt = Tickets.generate_ticket_granting_ticket(@username, @client_hostname) + @st = Tickets.generate_service_ticket(@service, @username, @tgt, @client_hostname) + end + + it "should validate service ticket" do + success, error = @cas.validate_service_ticket(@service, @st.ticket) + expect(success).to eq @st + expect(error).to be nil + end + end + + context "with invalid ticket" do + before do + @username = 'testuser' + @client_hostname = "myhost.test" + @service = 'myservice.test' + @tgt = Tickets.generate_ticket_granting_ticket(@username, @client_hostname) + @st = Tickets.generate_service_ticket(@service, @username, @tgt, @client_hostname) + end + + it "does not validate service ticket (throws an error)" do + success, error = @cas.validate_service_ticket(@service, "#{@st.ticket}-random_string") + expect(error).not_to be nil + end + end - before do - pending("Proxy ticket is not yet implemented") end - end + describe "validate proxy_ticket(target_service, pgt)" do + + before do + pending("Proxy ticket is not yet implemented") + end + + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 641cea1..951f8f1 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,6 +2,7 @@ require 'bundler/setup' require 'rubycas-server-core' require 'rubycas-server-core/adapters/in_memory' + begin require 'debugger' rescue LoadError From 37da917877c60fee3d23d173bda606560c759cdb Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Fri, 28 Feb 2014 14:22:35 +0100 Subject: [PATCH 2/6] Fix exception for activerecord adapter --- lib/rubycas-server-core/database.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubycas-server-core/database.rb b/lib/rubycas-server-core/database.rb index adb148b..63851f9 100644 --- a/lib/rubycas-server-core/database.rb +++ b/lib/rubycas-server-core/database.rb @@ -4,7 +4,7 @@ module Core module Database extend self def setup(config_file) - raise NotImplementedError, "Database adapter is missing, add it to your Gemfile, please refer to https://github.com/rubycas/rubycas-server-core/wiki for more details" + #raise NotImplementedError, "Database adapter is missing, add it to your Gemfile, please refer to https://github.com/rubycas/rubycas-server-core/wiki for more details" end end end From 36255c9e5ee13eeeec4ccdcab3ff47e3f6f40c9b Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Sat, 1 Mar 2014 22:24:49 +0100 Subject: [PATCH 3/6] Add rake to gemfile --- Gemfile | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Gemfile b/Gemfile index 851fabc..c4311e8 100644 --- a/Gemfile +++ b/Gemfile @@ -1,2 +1,4 @@ source 'https://rubygems.org' gemspec + +gem 'rake' From 1ba6881ad7aef7689f33991a4b1828f97bbbdf10 Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Sat, 1 Mar 2014 22:36:09 +0100 Subject: [PATCH 4/6] Remove duplicate gem --- rubycas-server-core.gemspec | 1 - 1 file changed, 1 deletion(-) diff --git a/rubycas-server-core.gemspec b/rubycas-server-core.gemspec index 5eaf9cb..8c0af37 100644 --- a/rubycas-server-core.gemspec +++ b/rubycas-server-core.gemspec @@ -20,7 +20,6 @@ Gem::Specification.new do |gem| gem.add_dependency "r18n-core" gem.add_dependency "activesupport", ">= 3.0" - gem.add_development_dependency "rake" gem.add_development_dependency "rspec" gem.add_development_dependency "bundler" end From fcf6a63c7f1fe83a18878c6b0553ed4eb2d69979 Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Sat, 1 Mar 2014 22:54:41 +0100 Subject: [PATCH 5/6] Add rspec to gemfile --- Gemfile | 1 + 1 file changed, 1 insertion(+) diff --git a/Gemfile b/Gemfile index c4311e8..806ac9c 100644 --- a/Gemfile +++ b/Gemfile @@ -2,3 +2,4 @@ source 'https://rubygems.org' gemspec gem 'rake' +gem 'rspec' From 114165bf043c5822fd55182225e99951d47a0198 Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Tue, 29 Apr 2014 13:49:04 +0200 Subject: [PATCH 6/6] method overloading in setup hash or file --- lib/rubycas-server-core/settings.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/rubycas-server-core/settings.rb b/lib/rubycas-server-core/settings.rb index c075e5a..502048f 100644 --- a/lib/rubycas-server-core/settings.rb +++ b/lib/rubycas-server-core/settings.rb @@ -11,9 +11,12 @@ module Settings @_settings = HashWithIndifferentAccess.new attr_reader :_settings - def load!(file_name) - config = YAML::load_file(file_name).with_indifferent_access - @_settings.merge!(config) + def load!(config) + if config.is_a? String + config = YAML::load_file(config).with_indifferent_access + elsif config.is_a? Hash + @_settings.merge!(config) + end end def method_missing(name, *args, &block)