forked from SAML-Toolkits/ruby-saml
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
code cleanups, fixing paths and files to follow best practices/standa…
…rds, removing unneeded deps (jeweler)
- Loading branch information
Joe Rozner
committed
Apr 13, 2012
1 parent
bafc52d
commit 21538be
Showing
17 changed files
with
324 additions
and
369 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 = "[email protected]" | ||
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 | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
module Onelogin | ||
module Saml | ||
VERSION = '0.4.8' | ||
end | ||
end |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.