diff --git a/Rakefile b/Rakefile index 2508fa3ec..7c2e2c4e9 100644 --- a/Rakefile +++ b/Rakefile @@ -1,27 +1,6 @@ require 'rubygems' require 'rake' -begin - require 'jeweler' - Jeweler::Tasks.new do |gem| - gem.name = "ruby-saml" - gem.summary = %Q{SAML Ruby Tookit} - gem.description = %Q{SAML toolkit for Ruby on Rails} - gem.email = "support@onelogin.com" - gem.homepage = "http://github.com/onelogin/ruby-saml" - gem.authors = ["OneLogin LLC"] - gem.add_dependency("canonix","~> 0.1") - gem.add_dependency("uuid","~> 2.3") - gem.add_development_dependency "shoulda" - gem.add_development_dependency "ruby-debug" - gem.add_development_dependency "mocha" - #gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings - end - Jeweler::GemcutterTasks.new -rescue LoadError - puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler" -end - #not being used yet. require 'rake/testtask' Rake::TestTask.new(:test) do |test| @@ -43,7 +22,7 @@ rescue LoadError end end -task :test => :check_dependencies +task :test task :default => :test diff --git a/lib/onelogin/ruby-saml/authrequest.rb b/lib/onelogin/ruby-saml/authrequest.rb new file mode 100644 index 000000000..2be0b113a --- /dev/null +++ b/lib/onelogin/ruby-saml/authrequest.rb @@ -0,0 +1,74 @@ +require "base64" +require "uuid" +require "zlib" +require "cgi" +require "rexml/document" +require "rexml/xpath" + +module Onelogin + module Saml + include REXML + class Authrequest + def create(settings, params = {}) + uuid = "_" + UUID.new.generate + time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") + # Create AuthnRequest root element using REXML + request_doc = REXML::Document.new + + root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" } + root.attributes['ID'] = uuid + root.attributes['IssueInstant'] = time + root.attributes['Version'] = "2.0" + + # Conditionally defined elements based on settings + if settings.assertion_consumer_service_url != nil + root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url + end + if settings.issuer != nil + issuer = root.add_element "saml:Issuer", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } + issuer.text = settings.issuer + end + if settings.name_identifier_format != nil + root.add_element "samlp:NameIDPolicy", { + "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", + # Might want to make AllowCreate a setting? + "AllowCreate" => "true", + "Format" => settings.name_identifier_format + } + end + + # BUG fix here -- if an authn_context is defined, add the tags with an "exact" + # match required for authentication to succeed. If this is not defined, + # the IdP will choose default rules for authentication. (Shibboleth IdP) + if settings.authn_context != nil + requested_context = root.add_element "samlp:RequestedAuthnContext", { + "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", + "Comparison" => "exact", + } + class_ref = requested_context.add_element "saml:AuthnContextClassRef", { + "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion", + } + class_ref.text = settings.authn_context + end + + request = "" + request_doc.write(request) + + Logging.debug "Created AuthnRequest: #{request}" + + deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5] + base64_request = Base64.encode64(deflated_request) + encoded_request = CGI.escape(base64_request) + params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?' + request_params = "#{params_prefix}SAMLRequest=#{encoded_request}" + + params.each_pair do |key, value| + request_params << "&#{key}=#{CGI.escape(value.to_s)}" + end + + settings.idp_sso_target_url + request_params + end + + end + end +end diff --git a/lib/onelogin/ruby-saml/logging.rb b/lib/onelogin/ruby-saml/logging.rb new file mode 100644 index 000000000..cf6c7c21e --- /dev/null +++ b/lib/onelogin/ruby-saml/logging.rb @@ -0,0 +1,22 @@ +# Simplistic log class when we're running in Rails +module Onelogin + module Saml + class Logging + def self.debug(message) + if defined? Rails + Rails.logger.debug message + else + puts message + end + end + + def self.info(message) + if defined? Rails + Rails.logger.info message + else + puts message + end + end + end + end +end diff --git a/lib/onelogin/ruby-saml/metadata.rb b/lib/onelogin/ruby-saml/metadata.rb new file mode 100644 index 000000000..9869ceb81 --- /dev/null +++ b/lib/onelogin/ruby-saml/metadata.rb @@ -0,0 +1,47 @@ +require "rexml/document" +require "rexml/xpath" +require "uri" + +# Class to return SP metadata based on the settings requested. +# Return this XML in a controller, then give that URL to the the +# IdP administrator. The IdP will poll the URL and your settings +# will be updated automatically +module Onelogin + module Saml + include REXML + class Metadata + def generate(settings) + meta_doc = REXML::Document.new + root = meta_doc.add_element "md:EntityDescriptor", { + "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata" + } + sp_sso = root.add_element "md:SPSSODescriptor", { + "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol" + } + if settings.issuer != nil + root.attributes["entityID"] = settings.issuer + end + if settings.name_identifier_format != nil + name_id = sp_sso.add_element "md:NameIDFormat" + name_id.text = settings.name_identifier_format + end + if settings.assertion_consumer_service_url != nil + sp_sso.add_element "md:AssertionConsumerService", { + # Add this as a setting to create different bindings? + "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", + "Location" => settings.assertion_consumer_service_url + } + end + meta_doc << REXML::XMLDecl.new + ret = "" + # pretty print the XML so IdP administrators can easily see what the SP supports + meta_doc.write(ret, 1) + + Logging.debug "Generated metadata:\n#{ret}" + + return ret + + end + end + end +end diff --git a/lib/onelogin/ruby-saml/response.rb b/lib/onelogin/ruby-saml/response.rb new file mode 100644 index 000000000..895d26b8c --- /dev/null +++ b/lib/onelogin/ruby-saml/response.rb @@ -0,0 +1,146 @@ +require "xml_security" +require "time" + +module Onelogin + module Saml + + class Response + ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" + PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" + DSIG = "http://www.w3.org/2000/09/xmldsig#" + + attr_accessor :options, :response, :document, :settings + + def initialize(response, options = {}) + raise ArgumentError.new("Response cannot be nil") if response.nil? + self.options = options + self.response = response + self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response)) + end + + def is_valid? + validate(soft = true) + end + + def validate! + validate(soft = false) + end + + # The value of the user identifier as designated by the initialization request response + def name_id + @name_id ||= begin + node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) + node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) + node.nil? ? nil : node.text + end + end + + # A hash of alle the attributes with the response. Assuming there is only one value for each key + def attributes + @attr_statements ||= begin + result = {} + + stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION }) + return {} if stmt_element.nil? + + stmt_element.elements.each do |attr_element| + name = attr_element.attributes["Name"] + value = attr_element.elements.first.text + + result[name] = value + end + + result.keys.each do |key| + result[key.intern] = result[key] + end + + result + end + end + + # When this user session should expire at latest + def session_expires_at + @expires_at ||= begin + node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION }) + parse_time(node, "SessionNotOnOrAfter") + end + end + + # Conditions (if any) for the assertion to run + def conditions + @conditions ||= begin + REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION }) + end + end + + def issuer + @issuer ||= begin + node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) + node.nil? ? nil : node.text + end + end + + private + + def validation_error(message) + raise ValidationError.new(message) + end + + def validate(soft = true) + validate_response_state(soft) && + validate_conditions(soft) && + document.validate(get_fingerprint, soft) + end + + def validate_response_state(soft = true) + if response.empty? + return soft ? false : validation_error("Blank response") + end + + if settings.nil? + return soft ? false : validation_error("No settings on response") + end + + if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? + return soft ? false : validation_error("No fingerprint or certificate on settings") + end + + true + end + + def get_fingerprint + if settings.idp_cert + cert = OpenSSL::X509::Certificate.new(settings.idp_cert) + Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":") + else + settings.idp_cert_fingerprint + end + end + + def validate_conditions(soft = true) + return true if conditions.nil? + return true if options[:skip_conditions] + + if not_before = parse_time(conditions, "NotBefore") + if Time.now.utc < not_before + return soft ? false : validation_error("Current time is earlier than NotBefore condition") + end + end + + if not_on_or_after = parse_time(conditions, "NotOnOrAfter") + if Time.now.utc >= not_on_or_after + return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition") + end + end + + true + end + + def parse_time(node, attribute) + if node && node.attributes[attribute] + Time.parse(node.attributes[attribute]) + end + end + end + end +end diff --git a/lib/onelogin/ruby-saml/settings.rb b/lib/onelogin/ruby-saml/settings.rb new file mode 100644 index 000000000..1c95b6085 --- /dev/null +++ b/lib/onelogin/ruby-saml/settings.rb @@ -0,0 +1,9 @@ +module Onelogin + module Saml + class Settings + attr_accessor :assertion_consumer_service_url, :issuer, :sp_name_qualifier + attr_accessor :idp_sso_target_url, :idp_cert_fingerprint, :idp_cert, :name_identifier_format + attr_accessor :authn_context + end + end +end diff --git a/lib/onelogin/saml/validation_error.rb b/lib/onelogin/ruby-saml/validation_error.rb similarity index 100% rename from lib/onelogin/saml/validation_error.rb rename to lib/onelogin/ruby-saml/validation_error.rb diff --git a/lib/onelogin/ruby-saml/version.rb b/lib/onelogin/ruby-saml/version.rb new file mode 100644 index 000000000..0bd0593e2 --- /dev/null +++ b/lib/onelogin/ruby-saml/version.rb @@ -0,0 +1,5 @@ +module Onelogin + module Saml + VERSION = '0.4.8' + end +end diff --git a/lib/onelogin/saml.rb b/lib/onelogin/saml.rb deleted file mode 100644 index b1cdda739..000000000 --- a/lib/onelogin/saml.rb +++ /dev/null @@ -1,8 +0,0 @@ -require 'onelogin/saml/logging' -require 'onelogin/saml/authrequest' -require 'onelogin/saml/response' -require 'onelogin/saml/settings' -require 'onelogin/saml/validation_error' -require 'onelogin/saml/metadata' - - diff --git a/lib/onelogin/saml/authrequest.rb b/lib/onelogin/saml/authrequest.rb deleted file mode 100644 index 3687f4fdb..000000000 --- a/lib/onelogin/saml/authrequest.rb +++ /dev/null @@ -1,73 +0,0 @@ -require "base64" -require "uuid" -require "zlib" -require "cgi" -require "rexml/document" -require "rexml/xpath" - -module Onelogin::Saml -include REXML - class Authrequest - def create(settings, params = {}) - - uuid = "_" + UUID.new.generate - time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") - # Create AuthnRequest root element using REXML - request_doc = REXML::Document.new - - root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol" } - root.attributes['ID'] = uuid - root.attributes['IssueInstant'] = time - root.attributes['Version'] = "2.0" - - # Conditionally defined elements based on settings - if settings.assertion_consumer_service_url != nil - root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url - end - if settings.issuer != nil - issuer = root.add_element "saml:Issuer", { "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" } - issuer.text = settings.issuer - end - if settings.name_identifier_format != nil - root.add_element "samlp:NameIDPolicy", { - "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", - # Might want to make AllowCreate a setting? - "AllowCreate" => "true", - "Format" => settings.name_identifier_format - } - end - - # BUG fix here -- if an authn_context is defined, add the tags with an "exact" - # match required for authentication to succeed. If this is not defined, - # the IdP will choose default rules for authentication. (Shibboleth IdP) - if settings.authn_context != nil - requested_context = root.add_element "samlp:RequestedAuthnContext", { - "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", - "Comparison" => "exact", - } - class_ref = requested_context.add_element "saml:AuthnContextClassRef", { - "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion", - } - class_ref.text = settings.authn_context - end - - request = "" - request_doc.write(request) - - Logging.debug "Created AuthnRequest: #{request}" - - deflated_request = Zlib::Deflate.deflate(request, 9)[2..-5] - base64_request = Base64.encode64(deflated_request) - encoded_request = CGI.escape(base64_request) - params_prefix = (settings.idp_sso_target_url =~ /\?/) ? '&' : '?' - request_params = "#{params_prefix}SAMLRequest=#{encoded_request}" - - params.each_pair do |key, value| - request_params << "&#{key}=#{CGI.escape(value.to_s)}" - end - - settings.idp_sso_target_url + request_params - end - - end -end diff --git a/lib/onelogin/saml/logging.rb b/lib/onelogin/saml/logging.rb deleted file mode 100644 index 6b3c97200..000000000 --- a/lib/onelogin/saml/logging.rb +++ /dev/null @@ -1,20 +0,0 @@ -# Simplistic log class when we're running in Rails -module Onelogin::Saml - class Logging - def self.debug(message) - if defined? Rails - Rails.logger.debug message - else - puts message - end - end - - def self.info(message) - if defined? Rails - Rails.logger.info message - else - puts message - end - end - end -end \ No newline at end of file diff --git a/lib/onelogin/saml/metadata.rb b/lib/onelogin/saml/metadata.rb deleted file mode 100644 index adfabe769..000000000 --- a/lib/onelogin/saml/metadata.rb +++ /dev/null @@ -1,46 +0,0 @@ -require "rexml/document" -require "rexml/xpath" -require "uri" - -# Class to return SP metadata based on the settings requested. -# Return this XML in a controller, then give that URL to the the -# IdP administrator. The IdP will poll the URL and your settings -# will be updated automatically -module Onelogin::Saml - include REXML - class Metadata - def generate(settings) - meta_doc = REXML::Document.new - root = meta_doc.add_element "md:EntityDescriptor", { - "xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata" - } - sp_sso = root.add_element "md:SPSSODescriptor", { - "protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol" - } - if settings.issuer != nil - root.attributes["entityID"] = settings.issuer - end - if settings.name_identifier_format != nil - name_id = sp_sso.add_element "md:NameIDFormat" - name_id.text = settings.name_identifier_format - end - if settings.assertion_consumer_service_url != nil - sp_sso.add_element "md:AssertionConsumerService", { - # Add this as a setting to create different bindings? - "Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", - "Location" => settings.assertion_consumer_service_url - } - end - meta_doc << REXML::XMLDecl.new - ret = "" - # pretty print the XML so IdP administrators can easily see what the SP supports - meta_doc.write(ret, 1) - - Logging.debug "Generated metadata:\n#{ret}" - - return ret - - end - end -end - diff --git a/lib/onelogin/saml/response.rb b/lib/onelogin/saml/response.rb deleted file mode 100644 index 2f364af43..000000000 --- a/lib/onelogin/saml/response.rb +++ /dev/null @@ -1,144 +0,0 @@ -require "xml_security" -require "time" - -module Onelogin::Saml - - class Response - ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion" - PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol" - DSIG = "http://www.w3.org/2000/09/xmldsig#" - - attr_accessor :options, :response, :document, :settings - - def initialize(response, options = {}) - raise ArgumentError.new("Response cannot be nil") if response.nil? - self.options = options - self.response = response - self.document = XMLSecurity::SignedDocument.new(Base64.decode64(response)) - end - - def is_valid? - validate(soft = true) - end - - def validate! - validate(soft = false) - end - - # The value of the user identifier as designated by the initialization request response - def name_id - @name_id ||= begin - node = REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) - node ||= REXML::XPath.first(document, "/p:Response[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Assertion/a:Subject/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION }) - node.nil? ? nil : node.text - end - end - - # A hash of alle the attributes with the response. Assuming there is only one value for each key - def attributes - @attr_statements ||= begin - result = {} - - stmt_element = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AttributeStatement", { "p" => PROTOCOL, "a" => ASSERTION }) - return {} if stmt_element.nil? - - stmt_element.elements.each do |attr_element| - name = attr_element.attributes["Name"] - value = attr_element.elements.first.text - - result[name] = value - end - - result.keys.each do |key| - result[key.intern] = result[key] - end - - result - end - end - - # When this user session should expire at latest - def session_expires_at - @expires_at ||= begin - node = REXML::XPath.first(document, "/p:Response/a:Assertion/a:AuthnStatement", { "p" => PROTOCOL, "a" => ASSERTION }) - parse_time(node, "SessionNotOnOrAfter") - end - end - - # Conditions (if any) for the assertion to run - def conditions - @conditions ||= begin - REXML::XPath.first(document, "/p:Response/a:Assertion[@ID='#{document.signed_element_id[1,document.signed_element_id.size]}']/a:Conditions", { "p" => PROTOCOL, "a" => ASSERTION }) - end - end - - def issuer - @issuer ||= begin - node = REXML::XPath.first(document, "/p:Response/a:Issuer", { "p" => PROTOCOL, "a" => ASSERTION }) - node.nil? ? nil : node.text - end - end - - private - - def validation_error(message) - raise ValidationError.new(message) - end - - def validate(soft = true) - validate_response_state(soft) && - validate_conditions(soft) && - document.validate(get_fingerprint, soft) - end - - def validate_response_state(soft = true) - if response.empty? - return soft ? false : validation_error("Blank response") - end - - if settings.nil? - return soft ? false : validation_error("No settings on response") - end - - if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? - return soft ? false : validation_error("No fingerprint or certificate on settings") - end - - true - end - - def get_fingerprint - if settings.idp_cert - cert = OpenSSL::X509::Certificate.new(settings.idp_cert) - Digest::SHA1.hexdigest(cert.to_der).upcase.scan(/../).join(":") - else - settings.idp_cert_fingerprint - end - end - - def validate_conditions(soft = true) - return true if conditions.nil? - return true if options[:skip_conditions] - - if not_before = parse_time(conditions, "NotBefore") - if Time.now.utc < not_before - return soft ? false : validation_error("Current time is earlier than NotBefore condition") - end - end - - if not_on_or_after = parse_time(conditions, "NotOnOrAfter") - if Time.now.utc >= not_on_or_after - return soft ? false : validation_error("Current time is on or after NotOnOrAfter condition") - end - end - - true - end - - def parse_time(node, attribute) - if node && node.attributes[attribute] - Time.parse(node.attributes[attribute]) - end - end - end -end diff --git a/lib/onelogin/saml/settings.rb b/lib/onelogin/saml/settings.rb deleted file mode 100644 index e16ffed23..000000000 --- a/lib/onelogin/saml/settings.rb +++ /dev/null @@ -1,7 +0,0 @@ -module Onelogin::Saml - class Settings - attr_accessor :assertion_consumer_service_url, :issuer, :sp_name_qualifier - attr_accessor :idp_sso_target_url, :idp_cert_fingerprint, :idp_cert, :name_identifier_format - attr_accessor :authn_context - end -end diff --git a/lib/ruby-saml.rb b/lib/ruby-saml.rb index c95d0b346..5d65a56c1 100644 --- a/lib/ruby-saml.rb +++ b/lib/ruby-saml.rb @@ -1,5 +1,7 @@ -module Onelogin -end - -require 'onelogin/saml' - +require 'onelogin/ruby-saml/logging' +require 'onelogin/ruby-saml/authrequest' +require 'onelogin/ruby-saml/response' +require 'onelogin/ruby-saml/settings' +require 'onelogin/ruby-saml/validation_error' +require 'onelogin/ruby-saml/metadata' +require 'onelogin/ruby-saml/version' diff --git a/ruby-saml.gemspec b/ruby-saml.gemspec index 7bd64dfea..ff8c412aa 100644 --- a/ruby-saml.gemspec +++ b/ruby-saml.gemspec @@ -1,11 +1,9 @@ -# Generated by jeweler -# DO NOT EDIT THIS FILE DIRECTLY -# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command -# -*- encoding: utf-8 -*- +$LOAD_PATH.push File.expand_path('../lib', __FILE__) +require 'onelogin/ruby-saml/version' Gem::Specification.new do |s| - s.name = %q{ruby-saml} - s.version = "0.4.7" + s.name = 'ruby-saml' + s.version = Onelogin::Saml::VERSION s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version= s.authors = ["OneLogin LLC"] @@ -16,48 +14,13 @@ Gem::Specification.new do |s| "LICENSE", "README.rdoc" ] - s.files = [ - ".document", - ".gitignore", - "LICENSE", - "README.rdoc", - "Rakefile", - "VERSION", - "lib/onelogin/saml.rb", - "lib/onelogin/saml/authrequest.rb", - "lib/onelogin/saml/response.rb", - "lib/onelogin/saml/settings.rb", - "lib/onelogin/saml/validation_error.rb", - "lib/ruby-saml.rb", - "lib/xml_security.rb", - "ruby-saml.gemspec", - "test/certificates/certificate1", - "test/request_test.rb", - "test/response_test.rb", - "test/responses/adfs_response.xml.base64", - "test/responses/open_saml_response.xml", - "test/responses/response1.xml.base64", - "test/responses/response2.xml.base64", - "test/responses/response3.xml.base64", - "test/responses/response4.xml.base64", - "test/responses/response5.xml.base64", - "test/responses/simple_saml_php.xml", - "test/settings_test.rb", - "test/test_helper.rb", - "test/xml_security_test.rb" - ] + s.files = `git ls-files`.split("\n") s.homepage = %q{http://github.com/onelogin/ruby-saml} s.rdoc_options = ["--charset=UTF-8"] s.require_paths = ["lib"] s.rubygems_version = %q{1.3.7} s.summary = %q{SAML Ruby Tookit} - s.test_files = [ - "test/request_test.rb", - "test/response_test.rb", - "test/settings_test.rb", - "test/test_helper.rb", - "test/xml_security_test.rb" - ] + s.test_files = `git ls-files test/*`.split("\n") if s.respond_to? :specification_version then current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION diff --git a/test/response_test.rb b/test/response_test.rb index d541fa3c9..69a8406bd 100644 --- a/test/response_test.rb +++ b/test/response_test.rb @@ -170,6 +170,12 @@ class RubySamlTest < Test::Unit::TestCase end end - end + context "#issuer" do + should "return the issuer of the assertion" do + response = Onelogin::Saml::Response.new(response_document_2) + assert_equal "wibble", response.issuer + end + end + end end