From 1cd26ca4ad85332c8057a9d8d897e342469f23ff Mon Sep 17 00:00:00 2001 From: Filippos Vasilakis Date: Fri, 28 Feb 2014 11:22:58 +0100 Subject: [PATCH] Some refactoring and enhancements Some refactoring and enhancements Some refactoring and enhancements Vital changes for Rails integration 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 Fix exception for activerecord adapter Add rake to gemfile Remove duplicate gem Add rspec to gemfile method overloading in setup hash or file Update README.md Update to rspec 3.1, fix tests, add circleci wip Update to rspec 3.1, fix tests add more specific gem versions add ruby-version for circleci update ruby version in circle.yml remove .ruby-version file Update README.md Enhance adapter pattern Extract memory adapter to another gem Add basic adapter api update version relax rake and bundler dependencies Remove in_memory adapter wip add rubycas-server-memory as test dependency in bundler refactor module methods, fix tests move module-based tickets generation api to Generations module transform validations/generations to instance methods fix tests let instead of instance vars minor changes bump version Update README.md remove root dir method fix tests User masters gemfile Add rubycas-server-memory to gemfile --- .gitignore | 2 + .ruby-gemset | 1 - Gemfile | 1 + README.md | 51 +++++- circle.yml | 3 + lib/rubycas-server-core.rb | 4 +- lib/rubycas-server-core/adapters/in_memory.rb | 21 --- .../adapters/in_memory/login_ticket.rb | 45 ------ .../in_memory/proxy_granting_ticket.rb | 38 ----- .../adapters/in_memory/proxy_ticket.rb | 50 ------ .../adapters/in_memory/service_ticket.rb | 56 ------- .../adapters/in_memory/storage.rb | 23 --- .../in_memory/ticket_granting_ticket.rb | 38 ----- lib/rubycas-server-core/database.rb | 2 +- lib/rubycas-server-core/error.rb | 24 +++ lib/rubycas-server-core/settings.rb | 10 +- lib/rubycas-server-core/tickets.rb | 147 ++++++++++++------ .../tickets/generations.rb | 76 +++++++++ .../tickets/validations.rb | 15 +- lib/rubycas-server-core/version.rb | 7 +- rubycas-server-core.gemspec | 13 +- spec/config/config.yml | 1 + .../tickets/generations_spec.rb | 109 +++++++++++++ .../tickets/validations_spec.rb | 122 ++++++++++----- spec/rubycas-server-core/tickets_spec.rb | 52 +++---- spec/spec_helper.rb | 3 +- 26 files changed, 514 insertions(+), 400 deletions(-) delete mode 100644 .ruby-gemset create mode 100644 circle.yml delete mode 100644 lib/rubycas-server-core/adapters/in_memory.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/login_ticket.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/proxy_granting_ticket.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/proxy_ticket.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/service_ticket.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/storage.rb delete mode 100644 lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb create mode 100644 lib/rubycas-server-core/error.rb create mode 100644 lib/rubycas-server-core/tickets/generations.rb create mode 100644 spec/rubycas-server-core/tickets/generations_spec.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/.ruby-gemset b/.ruby-gemset deleted file mode 100644 index d1ae265..0000000 --- a/.ruby-gemset +++ /dev/null @@ -1 +0,0 @@ -rubycas-server-core diff --git a/Gemfile b/Gemfile index 4617764..0e39a2a 100644 --- a/Gemfile +++ b/Gemfile @@ -17,4 +17,5 @@ end group :test do gem 'rake' gem 'rspec' + gem 'rubycas-server-memory', github: 'vasilakisfil/rubycas-server-memory' end diff --git a/README.md b/README.md index d122101..a5f6b9c 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,60 @@ rubycas-server-core =================== -[![Build Status](https://travis-ci.org/rubycas/rubycas-server-core.png)](https://travis-ci.org/rubycas/rubycas-server-core) -The core logic for handling CAS requests independent of any particular storage or web presentation technology. +[![Circle CI](https://circleci.com/gh/vasilakisfil/rubycas-server-core.svg?style=svg)](https://circleci.com/gh/vasilakisfil/rubycas-server-core) +The core logic for handling CAS requests independent of any particular storage or web presentation technology. ## Requirements * ruby 2.1.x +## Adapters +Currently available adapters are: +* [rubycas-server-activerecord](https://github.com/kollegorna/rubycas-server-activerecord) +* [rubycas-server-memory](https://github.com/vasilakisfil/rubycas-server-memory) + +If you want to create a new adapter check these 2 adapters how they are implemented. Essentially you need to implement the following methods for each ticket: + +```ruby + +class XXXTicket + def initialize(options = {}) + end + + #deprecated + def self.find_by_ticket(ticket) + #returns the ticket based on the ticket id + #it will be removed soon + end + + def self.find_by(opts = {}) + #returns the ticket based on the constraints in the hash (activerecord-style) + end + + def save! + #saves the ticket in the storage + #throws an exception in case of an error + end + + def save + #saves the ticket in the storage + end + + def consumed? + #returns true if ticket is already consumed + end + + def consume! + #consumes the ticket + end + + def expired?(max_lifetime = 100) + #checks if the ticket is already expired + end + +end +``` + ## Contributing 1. Fork it diff --git a/circle.yml b/circle.yml new file mode 100644 index 0000000..6508ed0 --- /dev/null +++ b/circle.yml @@ -0,0 +1,3 @@ +machine: + ruby: + version: 2.1.2 diff --git a/lib/rubycas-server-core.rb b/lib/rubycas-server-core.rb index 2fa2804..381f81f 100644 --- a/lib/rubycas-server-core.rb +++ b/lib/rubycas-server-core.rb @@ -1,12 +1,14 @@ 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" require "rubycas-server-core/util" -require "rubycas-server-core/tickets" +require "rubycas-server-core/tickets/generations" require "rubycas-server-core/tickets/validations" +require "rubycas-server-core/tickets" module RubyCAS module Server diff --git a/lib/rubycas-server-core/adapters/in_memory.rb b/lib/rubycas-server-core/adapters/in_memory.rb deleted file mode 100644 index 490b702..0000000 --- a/lib/rubycas-server-core/adapters/in_memory.rb +++ /dev/null @@ -1,21 +0,0 @@ -require 'securerandom' -require "rubycas-server-core/adapters/in_memory/storage" -require "rubycas-server-core/adapters/in_memory/login_ticket" -require "rubycas-server-core/adapters/in_memory/ticket_granting_ticket" -require "rubycas-server-core/adapters/in_memory/service_ticket" -require "rubycas-server-core/adapters/in_memory/proxy_ticket" -require "rubycas-server-core/adapters/in_memory/proxy_granting_ticket" - -module RubyCAS - module Server - module Core - module Database - extend self - def setup(config_file) - # InMemory adapter do not require any settings - return true - end - end - end - end -end diff --git a/lib/rubycas-server-core/adapters/in_memory/login_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/login_ticket.rb deleted file mode 100644 index 7bfaba7..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/login_ticket.rb +++ /dev/null @@ -1,45 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class LoginTicket < Storage - attr_accessor :id, :ticket, :consumed, :client_hostname, - :created_at, :updated_at - - - def initialize(lt = {}) - @id = SecureRandom.uuid - @ticket = lt[:ticket] - @consumed = lt[:consumed] - @client_hostname = lt[:client_hostname] - @created_at = DateTime.now - @updated_at = DateTime.now - super() - end - - def self.find_by_ticket(ticket) - @storage.each do |id,lt| - return lt if lt.ticket == ticket - end - return nil - end - - def consumed? - consumed - end - - def consume! - consumed = true - self.save - end - - def expired?(max_lifetime = 100) - lifetime = Time.now.to_i - created_at.to_time.to_i - lifetime > max_lifetime - end - - end - end - end - end -end diff --git a/lib/rubycas-server-core/adapters/in_memory/proxy_granting_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/proxy_granting_ticket.rb deleted file mode 100644 index e78f52c..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/proxy_granting_ticket.rb +++ /dev/null @@ -1,38 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class ProxyGrantingTicket < Storage - - attr_accessor :id, :ticket, :client_hostname, :iou, - :created_at, :updated_at, :service_ticket, - :proxy_tickets - - def initialize(pgt = {}) - @id = SecureRandom.uuid - @ticket = pgt[:ticket] - @client_hostname = pgt[:client_hostname] - @created_at = DateTime.now - @updated_at = DateTime.now - @service_ticket = pgt[:service_ticket] - @proxy_tickets = pgt[:proxy_tickets] - super() - end - - - def self.find_by_ticket(ticket) - @storage.each do |id,pgt| - return pgt if pgt.ticket == ticket - end - return nil - end - - def expired?(max_lifetime = 100) - lifetime = Time.now.to_i - created_at.to_time.to_i - lifetime > max_lifetime - end - end - end - end - end -end diff --git a/lib/rubycas-server-core/adapters/in_memory/proxy_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/proxy_ticket.rb deleted file mode 100644 index d81f8ff..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/proxy_ticket.rb +++ /dev/null @@ -1,50 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class ProxyTicket < Storage - - attr_accessor :id, :ticket, :service, :consumed, :client_hostname, - :username, :created_at, :updated_at, :proxy_granting_ticket, - :ticket_granting_ticket - - def initialize(pt = {}) - @id = SecureRandom.uuid - @ticket = pt[:ticket] - @service = pt[:service] - @consumed = pt[:consumed] - @client_hostname = pt[:client_hostname] - @username = pt[:username] - @created_at = DateTime.now - @updated_at = DateTime.now - @proxy_granting_ticket = pt[:proxy_granting_ticket] - @ticket_granting_ticket = pt[:ticket_granting_ticket] - super() - end - - - def self.find_by_ticket(ticket) - @storage.each do |id,pt| - return pt if pt.ticket == ticket - end - return nil - end - - def consumed? - consumed - end - - def consume! - consumed = true - self.save - end - - def expired?(max_lifetime = 100) - lifetime = Time.now.to_i - created_at.to_time.to_i - lifetime > max_lifetime - end - end - end - end - end -end diff --git a/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb b/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb deleted file mode 100644 index 0cc7e0d..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/service_ticket.rb +++ /dev/null @@ -1,56 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class ServiceTicket < Storage - - attr_accessor :id, :ticket, :consumed, :client_hostname, - :username, :created_at, :updated_at, :proxy_granting_ticket, - :ticket_granting_ticket - attr_reader :service - - def initialize(st = {}) - @id = SecureRandom.uuid - @ticket = st[:ticket] - @service = st[:service] - @consumed = st[:consumed] - @client_hostname = st[:client_hostname] - @username = st[:username] - @created_at = DateTime.now - @updated_at = DateTime.now - @proxy_granting_ticket = st[:proxy_granting_ticket] - @ticket_granting_ticket = st[:ticket_granting_ticket] - super() - end - - - def self.find_by_ticket(ticket) - @storage.each do |id,st| - return st if st.ticket == ticket - end - return nil - end - - def consumed? - consumed - end - - def consume! - consumed = true - self.save - end - - def expired?(max_lifetime = 100) - lifetime = Time.now.to_i - created_at.to_time.to_i - lifetime > max_lifetime - end - - def service=(url) - @service = RubyCAS::Server::Core::Util.clean_service_url(url) - end - - end - end - end - end -end diff --git a/lib/rubycas-server-core/adapters/in_memory/storage.rb b/lib/rubycas-server-core/adapters/in_memory/storage.rb deleted file mode 100644 index 37464f9..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/storage.rb +++ /dev/null @@ -1,23 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class Storage - class << self - attr_accessor :storage - end - - def initialize - self.class.storage = {} unless self.class.storage - end - - def save - self.class.storage[@id] = self - return true - end - - end - 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 deleted file mode 100644 index 03d2e87..0000000 --- a/lib/rubycas-server-core/adapters/in_memory/ticket_granting_ticket.rb +++ /dev/null @@ -1,38 +0,0 @@ -module RubyCAS - module Server - module Core - module Tickets - class TicketGrantingTicket < Storage - attr_accessor :id, :ticket, :client_hostname, :username, - :extra_attributes, :service_tickets, :proxy_tickets, - :created_at, :updated_at - - def initialize(tgt = {}) - @id = SecureRandom.uuid - @ticket = tgt[:ticket] - @client_hostname = tgt[:client_hostname] - @username = tgt[:username] - @extra_attributes = tgt[:extra_attributes] - @service_tickets = tgt[:service_tickets] - @proxy_tickets = tgt[:proxy_tickets] - @created_at = DateTime.now - @updated_at = DateTime.now - super() - end - - def self.find_by_ticket(ticket) - @storage.each do |id, tgt| - return tgt if tgt.ticket == ticket - end - return nil - end - - def expired?(max_lifetime) - lifetime = Time.now.to_i - created_at.to_time.to_i - lifetime > max_lifetime - end - end - end - end - end -end 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 diff --git a/lib/rubycas-server-core/error.rb b/lib/rubycas-server-core/error.rb new file mode 100644 index 0000000..46783dc --- /dev/null +++ b/lib/rubycas-server-core/error.rb @@ -0,0 +1,24 @@ +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 + + class RubyCASServerCoreError < StandardError + end + + class RecordNotFound < RubyCASServerCoreError + end + end +end diff --git a/lib/rubycas-server-core/settings.rb b/lib/rubycas-server-core/settings.rb index c075e5a..8a47918 100644 --- a/lib/rubycas-server-core/settings.rb +++ b/lib/rubycas-server-core/settings.rb @@ -11,9 +11,13 @@ 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 + @_settings.merge! YAML::load_file(config).with_indifferent_access + elsif config.is_a? Hash + @_settings.merge!(config) + end end def method_missing(name, *args, &block) diff --git a/lib/rubycas-server-core/tickets.rb b/lib/rubycas-server-core/tickets.rb index 34708d6..9e2999a 100644 --- a/lib/rubycas-server-core/tickets.rb +++ b/lib/rubycas-server-core/tickets.rb @@ -4,64 +4,123 @@ module RubyCAS module Server module Core module Tickets + class LT + extend ::RubyCAS::Server::Core::Tickets::Generations + extend ::RubyCAS::Server::Core::Tickets::Validations - # One time login ticket for given client - def self.generate_login_ticket(client) - lt = LoginTicket.new - lt.ticket = "LT-" + Util.random_string - lt.client_hostname = client - if lt.save - $LOG.debug("Login ticket '#{lt.ticket} has been created for '#{lt.client_hostname}'") + def self.create!(client = "localhost") + lt = generate_login_ticket(client) + raise 'error that should be handled by rubycas-server-core gem' if !lt + return lt + end + + def self.create(client = "localhost") + generate_login_ticket(client) + end + + def self.validate(lt) + validate_login_ticket(lt) + end + + def self.find_by(options) + Tickets::LoginTicket.find_by( + options + ) + end + + def self.find_by!(options) + lt = Tickets::LoginTicket.find_by( + options + ) + raise 'error that should be handled by rubycas-server-core gem' if !lt return lt - else - return nil end end - # Creates a TicketGrantingTicket for the given username. This is done when the user logs in - # for the first time to establish their SSO session (after their credentials have been validated). - # - # 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 = {}) - tgt = TicketGrantingTicket.new - tgt.ticket = "TGC-" + Util.random_string - tgt.username = username - tgt.extra_attributes = extra_attributes - tgt.client_hostname = client - 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}")) + class TGT + extend ::RubyCAS::Server::Core::Tickets::Generations + extend ::RubyCAS::Server::Core::Tickets::Validations + + def self.create!(user, client = "localhost", remember_me = false, extra_attributes = {}) + tgt = generate_ticket_granting_ticket( + user, client, remember_me, extra_attributes + ) + raise 'error that should be handled by rubycas-server-core gem' if !tgt + return tgt + end + + def self.create(user, client = "localhost", remember_me = false, extra_attributes = {}) + generate_ticket_granting_ticket( + user, client, remember_me, extra_attributes + ) + end + + def self.validate(tgt) + validate_ticket_granting_ticket(tgt) + end + + + def self.find_by(options) + Tickets::TicketGrantingTicket.find_by( + options + ) + end + + def self.find_by!(options) + tgt = Tickets::TicketGrantingTicket.find_by( + options + ) + raise 'error that should be handled by rubycas-server-core gem' if !tgt return tgt - else - return nil end end - def self.generate_service_ticket(service, username, tgt, client) - st = ServiceTicket.new - st.ticket = "ST-" + Util.random_string - st.service = service - st.username = username - st.ticket_granting_ticket = tgt - st.client_hostname = client - if st.save - $LOG.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" + - " for user '#{st.username}' at '#{st.client_hostname}'") + class ST + extend ::RubyCAS::Server::Core::Tickets::Generations + extend ::RubyCAS::Server::Core::Tickets::Validations + + def self.create!(service, user, tgt, client="localhost") + st = generate_service_ticket(service, user, tgt, client) + raise 'error that should be handled by rubycas-server-core gem' if !st return st - else - return nil end - end - def self.generate_proxy_ticket(target_service, pgt, client) - raise NotImplementedError + def self.create(service, user, tgt, client="localhost") + generate_service_ticket(service, user, tgt, client) + end + + def self.validate(service, ticket) + validate_service_ticket(service, ticket) + end + + def self.find_by(options) + Tickets::ServiceTicket.find_by( + options + ) + end + + def self.find_by!(options) + st = Tickets::ServiceTicket.find_by( + options + ) + raise 'error that should be handled by rubycas-server-core gem' if !st + return st + end end - def self.generate_proxy_granting_ticket(pgt_url, st, client) - raise NotImplementedError + class Utils + include ::RubyCAS::Server::Core + + def self.clean_service_url(service_url) + return "" if service_url.nil? + service_url.encode!('UTF-16', 'UTF-8', invalid: :replace, replace: '') + service_url.encode!('UTF-8', 'UTF-16') + Util.clean_service_url(service_url) + end + + def self.build_ticketed_url(service, ticket) + Util.build_ticketed_url(service, ticket) + end end end end diff --git a/lib/rubycas-server-core/tickets/generations.rb b/lib/rubycas-server-core/tickets/generations.rb new file mode 100644 index 0000000..4b35e37 --- /dev/null +++ b/lib/rubycas-server-core/tickets/generations.rb @@ -0,0 +1,76 @@ +require 'rubycas-server-core/util' + +module RubyCAS + module Server + module Core + module Tickets + module Generations + # One time login ticket for given client + def generate_login_ticket(client) + lt = LoginTicket.new + lt.ticket = "LT-" + Util.random_string + lt.client_hostname = client + if lt.save! + $LOG.debug("Login ticket '#{lt.ticket} has been created for '#{lt.client_hostname}'") + return lt + else + return nil + end + end + + # Creates a TicketGrantingTicket for the given username. This is done when the user logs in + # for the first time to establish their SSO session (after their credentials have been validated). + # + # 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 generate_ticket_granting_ticket( + username, + client, + remember_me = false, + extra_attributes = {} + ) + tgt = TicketGrantingTicket.new + tgt.ticket = "TGC-" + Util.random_string + tgt.username = username + tgt.remember_me = remember_me + tgt.extra_attributes = extra_attributes.to_s + tgt.client_hostname = client + 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}")) + return tgt + else + return nil + end + end + + def generate_service_ticket(service, username, tgt, client) + st = tgt.service_tickets.new + st.ticket = "ST-" + Util.random_string + st.service = service + st.username = username + st.ticket_granting_ticket = tgt + st.client_hostname = client + if st.save + $LOG.debug("Generated service ticket '#{st.ticket}' for service '#{st.service}'" + + " for user '#{st.username}' at '#{st.client_hostname}'") + return st + else + return nil + end + end + + def generate_proxy_ticket(target_service, pgt, client) + raise NotImplementedError + end + + def generate_proxy_granting_ticket(pgt_url, st, client) + raise NotImplementedError + end + end + end + end + end +end diff --git a/lib/rubycas-server-core/tickets/validations.rb b/lib/rubycas-server-core/tickets/validations.rb index cdb62f0..4d6566b 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/lib/rubycas-server-core/version.rb b/lib/rubycas-server-core/version.rb index 59bff30..96bb2dc 100644 --- a/lib/rubycas-server-core/version.rb +++ b/lib/rubycas-server-core/version.rb @@ -4,11 +4,10 @@ module Core module Version # :nodoc: all MAJOR = 0 - MINOR = 1 - PATCH = 0 - TAG = 'alpha' + MINOR = 2 + PATCH = 1 - STRING = [MAJOR, MINOR, PATCH, TAG].join('.') + STRING = [MAJOR, MINOR, PATCH].join('.') end end end diff --git a/rubycas-server-core.gemspec b/rubycas-server-core.gemspec index 033fe05..0a61173 100644 --- a/rubycas-server-core.gemspec +++ b/rubycas-server-core.gemspec @@ -17,6 +17,15 @@ Gem::Specification.new do |gem| gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib"] - gem.add_dependency "r18n-core" - gem.add_dependency "activesupport", ">= 3.0" + gem.required_ruby_version = '>= 1.9.2' + gem.required_rubygems_version = '>= 1.3.6' + + gem.add_dependency 'r18n-core', '~> 2.0.3' + gem.add_dependency 'activesupport', '>= 3.0' + + gem.add_development_dependency 'rspec', '~> 3.1.0' + gem.add_development_dependency 'rake' + gem.add_development_dependency 'bundler' + gem.add_development_dependency 'pry' + gem.add_development_dependency 'rubycas-server-memory', '0.0.2' 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/generations_spec.rb b/spec/rubycas-server-core/tickets/generations_spec.rb new file mode 100644 index 0000000..042ec86 --- /dev/null +++ b/spec/rubycas-server-core/tickets/generations_spec.rb @@ -0,0 +1,109 @@ +require "spec_helper" + +module RubyCAS::Server::Core::Tickets + describe RubyCAS::Server::Core::Tickets::Generations do + let(:client_hostname) { 'myhost.test' } + let(:username) { 'myuser' } + let(:service) { 'https://myservice.test' } + + before do + RubyCAS::Server::Core.setup("spec/config/config.yml") + @generations = Class.new + @generations.extend(RubyCAS::Server::Core::Tickets::Generations) + end + + describe '.generate_login_ticket(client_hostname)' do + before do + @lt = @generations.generate_login_ticket(client_hostname) + end + + it "should return a login ticket" do + @lt.class.should == LoginTicket + end + + it "should set the client_hostname" do + @lt.client_hostname.should == client_hostname + end + + it "should set the ticket string" do + @lt.ticket.should_not be_nil + end + + it "should set the ticket string starting with 'LT'" do + @lt.ticket.should(match(/^LT/)) + end + + it "should not mark the ticket as consumed" do + @lt.consumed.should be_nil + end + end + + describe ".generate_ticket_granting_ticket(username, extra_attributes = {})" do + before do + @tgt = @generations.generate_ticket_granting_ticket(username, client_hostname) + end + + it "should return a TicketGrantingTicket" do + @tgt.class.should == TicketGrantingTicket + end + + it "should set the tgt's ticket string" do + @tgt.ticket.should_not be_nil + end + + it "should generate a ticket string starting with 'TGC'" do + @tgt.ticket.should(match(/^TGC/)) + end + + it "should set the tgt's username string" do + @tgt.username.should == username + end + + it "should set the tgt's client_hostname" do + @tgt.client_hostname.should == client_hostname + end + end + + describe ".generate_service_ticket(service, username, tgt)" do + before do + @tgt = @generations.generate_ticket_granting_ticket(username, client_hostname) + @st = @generations.generate_service_ticket(service, username, @tgt, client_hostname) + end + + it "should return a ServiceTicket" do + @st.class.should == ServiceTicket + end + + it "should not include the service identifer in the ticket string" do + @st.ticket.should_not(match(/#{service}/)) + end + + it "should not mark the ST as consumed" do + @st.consumed.should be_nil + end + + it "must generate a ticket that starts with 'ST-'" do + @st.ticket.should(match(/^ST-/)) + end + + it "should assoicate the ST with the supplied TGT" do + @st.ticket_granting_ticket.id.should == @tgt.id + end + end + + describe ".generate_proxy_ticket(target_service, pgt)" do + it "should return a ProxyGrantingTicket" do + skip('Not supported') + end + + it "should not consume the generated ticket" do + skip('Not supported') + end + + it "should start the ticket string with PT-" do + skip('Not supported') + end + end + end +end + diff --git a/spec/rubycas-server-core/tickets/validations_spec.rb b/spec/rubycas-server-core/tickets/validations_spec.rb index 6ddc1d9..4b07842 100644 --- a/spec/rubycas-server-core/tickets/validations_spec.rb +++ b/spec/rubycas-server-core/tickets/validations_spec.rb @@ -1,62 +1,108 @@ require "spec_helper" describe RubyCAS::Server::Core::Tickets::Validations do + before do - 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 + RubyCAS::Server::Core.setup("spec/config/config.yml") + @generations = Class.new + @generations.extend(RubyCAS::Server::Core::Tickets::Generations) + @validations = Class.new + @validations.extend(RubyCAS::Server::Core::Tickets::Validations) 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) + @lt = @generations.generate_login_ticket(@client_hostname) + success, error = @validations.validate_login_ticket(@lt.ticket) success.should be_truthy error.should be_nil end end + describe "validations" + describe "#validate_login_ticket" do + context "with valid ticket" do + it "should validate login ticket" do + @lt = @generations.generate_login_ticket(@client_hostname) + success, error = @validations.validate_login_ticket(@lt.ticket) + success.should be_truthy + error.should be_nil + end + 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) + context "with invalid ticket" do + it "should not validate login ticket" do + @lt = @generations.generate_login_ticket(@client_hostname) + success, error = @validations.validate_login_ticket("#{@lt.ticket}random") + expect(success).to be false + expect(error).not_to be nil + end + end end - it "should validate ticket granting ticket" do - success, error = @cas.validate_ticket_granting_ticket(@tgt.ticket) - success.should be_truthy - error.should be_nil - end - end + describe "#validate_ticket_granting_ticket(username, extra_attributes = {})" do + before do + @username = 'myuser' + @client_hostname = "myhost.test" + @tgt = @generations.generate_ticket_granting_ticket(@username, @client_hostname) + 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 + context "with valid tgt" do + it "should validate ticket granting ticket" do + success, error = @validations.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_truthy - error.should be_nil + context "with invalid gt" do + it "should not validate ticket granting ticket" do + success, error = @validations.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 = @generations.generate_ticket_granting_ticket(@username, @client_hostname) + @st = @generations.generate_service_ticket(@service, @username, @tgt, @client_hostname) + end + + it "should validate service ticket" do + success, error = @validations.validate_service_ticket(@service, @st.ticket) + expect(success).to eq true + expect(error).to be nil + end + end + + context "with invalid ticket" do + before do + @username = 'testuser' + @client_hostname = "myhost.test" + @service = 'myservice.test' + @tgt = @generations.generate_ticket_granting_ticket(@username, @client_hostname) + @st = @generations.generate_service_ticket(@service, @username, @tgt, @client_hostname) + end + + it "does not validate service ticket (throws an error)" do + _, error = @validations.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/rubycas-server-core/tickets_spec.rb b/spec/rubycas-server-core/tickets_spec.rb index 30f6ab8..d8aa045 100644 --- a/spec/rubycas-server-core/tickets_spec.rb +++ b/spec/rubycas-server-core/tickets_spec.rb @@ -1,6 +1,6 @@ require "spec_helper" -module RubyCAS::Server::Core +module RubyCAS::Server::Core::Tickets describe RubyCAS::Server::Core::Tickets do let(:client_hostname) { 'myhost.test' } let(:username) { 'myuser' } @@ -8,18 +8,13 @@ module RubyCAS::Server::Core before do RubyCAS::Server::Core.setup("spec/config/config.yml") - klass = Class.new { - include RubyCAS::Server::Core::Tickets - } - @cas = klass.new - @client_hostname = "myhost.test" end - describe '.generate_login_ticket(client_hostname)' do - let(:lt) { Tickets.generate_login_ticket(client_hostname) } + describe LT do + let(:lt) { LT.create!(client_hostname) } it "should return a login ticket" do - lt.class.should == Tickets::LoginTicket + lt.class.should == LoginTicket end it "should set the client_hostname" do @@ -31,7 +26,7 @@ module RubyCAS::Server::Core end it "should set the ticket string starting with 'LT'" do - lt.ticket.should match /^LT/ + lt.ticket.should(match(/^LT/)) end it "should not mark the ticket as consumed" do @@ -39,40 +34,42 @@ module RubyCAS::Server::Core end end - describe ".generate_ticket_granting_ticket(username, extra_attributes = {})" do - let(:tgt) { Tickets.generate_ticket_granting_ticket(username, client_hostname) } + describe TGT do + before do + @tgt = TGT.create!(username, client_hostname) + end it "should return a TicketGrantingTicket" do - tgt.class.should == Tickets::TicketGrantingTicket + @tgt.class.should == TicketGrantingTicket end it "should set the tgt's ticket string" do - tgt.ticket.should_not be_nil + @tgt.ticket.should_not be_nil end it "should generate a ticket string starting with 'TGC'" do - tgt.ticket.should match /^TGC/ + @tgt.ticket.should(match(/^TGC/)) end it "should set the tgt's username string" do - tgt.username.should == username + @tgt.username.should == username end it "should set the tgt's client_hostname" do - tgt.client_hostname.should == client_hostname + @tgt.client_hostname.should == client_hostname end end - describe ".generate_service_ticket(service, username, tgt)" do - let(:tgt) { Tickets.generate_ticket_granting_ticket(username, client_hostname) } - let(:st) { Tickets.generate_service_ticket(service, username, tgt, client_hostname) } + describe ST do + let(:tgt) { TGT.create!(username, client_hostname) } + let(:st) { ST.create!(service, username, tgt, client_hostname) } it "should return a ServiceTicket" do - st.class.should == Tickets::ServiceTicket + st.class.should == ServiceTicket end it "should not include the service identifer in the ticket string" do - st.ticket.should_not match /#{service}/ + st.ticket.should_not(match(/#{service}/)) end it "should not mark the ST as consumed" do @@ -80,7 +77,7 @@ module RubyCAS::Server::Core end it "must generate a ticket that starts with 'ST-'" do - st.ticket.should match /^ST-/ + st.ticket.should(match(/^ST-/)) end it "should assoicate the ST with the supplied TGT" do @@ -90,18 +87,15 @@ module RubyCAS::Server::Core describe ".generate_proxy_ticket(target_service, pgt)" do it "should return a ProxyGrantingTicket" do - pending("Proxy ticket is not implemented yet") - fail + skip('Not supported') end it "should not consume the generated ticket" do - pending("Proxy ticket is not implemented yet") - fail + skip('Not supported') end it "should start the ticket string with PT-" do - pending("Proxy ticket is not implemented yet") - fail + skip('Not supported') end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 641cea1..5d8f948 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,7 +1,8 @@ require 'rubygems' require 'bundler/setup' require 'rubycas-server-core' -require 'rubycas-server-core/adapters/in_memory' +require 'rubycas/server/memory' + begin require 'debugger' rescue LoadError