ruby-saml-federazione-trentina 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +10 -0
- data/.travis.yml +5 -0
- data/Gemfile +12 -0
- data/LICENSE +19 -0
- data/README.md +126 -0
- data/Rakefile +41 -0
- data/lib/onelogin/ruby-saml/authrequest.rb +189 -0
- data/lib/onelogin/ruby-saml/coding.rb +34 -0
- data/lib/onelogin/ruby-saml/logging.rb +26 -0
- data/lib/onelogin/ruby-saml/logout_request.rb +126 -0
- data/lib/onelogin/ruby-saml/logout_response.rb +132 -0
- data/lib/onelogin/ruby-saml/metadata.rb +270 -0
- data/lib/onelogin/ruby-saml/request.rb +83 -0
- data/lib/onelogin/ruby-saml/response.rb +211 -0
- data/lib/onelogin/ruby-saml/settings.rb +29 -0
- data/lib/onelogin/ruby-saml/validation_error.rb +7 -0
- data/lib/onelogin/ruby-saml/version.rb +5 -0
- data/lib/ruby-saml.rb +11 -0
- data/lib/schemas/saml20assertion_schema.xsd +283 -0
- data/lib/schemas/saml20protocol_schema.xsd +302 -0
- data/lib/schemas/xenc_schema.xsd +146 -0
- data/lib/schemas/xmldsig_schema.xsd +318 -0
- data/lib/xml_security.rb +165 -0
- data/ruby-saml.gemspec +21 -0
- data/test/certificates/certificate1 +12 -0
- data/test/logoutrequest_test.rb +98 -0
- data/test/request_test.rb +53 -0
- data/test/response_test.rb +219 -0
- data/test/responses/adfs_response_sha1.xml +46 -0
- data/test/responses/adfs_response_sha256.xml +46 -0
- data/test/responses/adfs_response_sha384.xml +46 -0
- data/test/responses/adfs_response_sha512.xml +46 -0
- data/test/responses/no_signature_ns.xml +48 -0
- data/test/responses/open_saml_response.xml +56 -0
- data/test/responses/response1.xml.base64 +1 -0
- data/test/responses/response2.xml.base64 +79 -0
- data/test/responses/response3.xml.base64 +66 -0
- data/test/responses/response4.xml.base64 +93 -0
- data/test/responses/response5.xml.base64 +102 -0
- data/test/responses/response_with_ampersands.xml +139 -0
- data/test/responses/response_with_ampersands.xml.base64 +93 -0
- data/test/responses/simple_saml_php.xml +71 -0
- data/test/responses/wrapped_response_2.xml.base64 +150 -0
- data/test/settings_test.rb +43 -0
- data/test/test_helper.rb +66 -0
- data/test/xml_security_test.rb +123 -0
- metadata +155 -0
@@ -0,0 +1,26 @@
|
|
1
|
+
# Simplistic log class when we're running in Rails
|
2
|
+
module Onelogin
|
3
|
+
module Saml
|
4
|
+
class Logging
|
5
|
+
def self.debug(message)
|
6
|
+
return if !!ENV["ruby-saml/testing"]
|
7
|
+
|
8
|
+
if defined? Rails
|
9
|
+
Rails.logger.debug message
|
10
|
+
else
|
11
|
+
puts message
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.info(message)
|
16
|
+
return if !!ENV["ruby-saml/testing"]
|
17
|
+
|
18
|
+
if defined? Rails
|
19
|
+
Rails.logger.info message
|
20
|
+
else
|
21
|
+
puts message
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,126 @@
|
|
1
|
+
require 'uuid'
|
2
|
+
|
3
|
+
module Onelogin::Saml
|
4
|
+
class LogoutRequest
|
5
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
6
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
7
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
8
|
+
|
9
|
+
include Coding
|
10
|
+
include Request
|
11
|
+
attr_reader :transaction_id
|
12
|
+
attr_accessor :settings
|
13
|
+
|
14
|
+
def initialize( options = {} )
|
15
|
+
opt = { :request => nil, :settings => nil }.merge(options)
|
16
|
+
@settings = opt[:settings]
|
17
|
+
@issue_instant = Onelogin::Saml::LogoutRequest.timestamp
|
18
|
+
@request_params = Hash.new
|
19
|
+
# We need to generate a LogoutRequest to send to the IdP
|
20
|
+
if opt[:request].nil?
|
21
|
+
@transaction_id = UUID.new.generate
|
22
|
+
# The IdP sent us a LogoutRequest (IdP initiated SLO)
|
23
|
+
else
|
24
|
+
begin
|
25
|
+
@request = XMLSecurity::SignedDocument.new( decode( opt[:request] ))
|
26
|
+
raise if @request.nil?
|
27
|
+
raise if @request.root.nil?
|
28
|
+
raise if @request.root.namespace != PROTOCOL
|
29
|
+
rescue
|
30
|
+
@request = XMLSecurity::SignedDocument.new( inflate( decode( opt[:request] ) ) )
|
31
|
+
end
|
32
|
+
Logging.debug "LogoutRequest is: \n#{@request}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def create( options = {} )
|
37
|
+
opt = { :name_id => nil, :session_index => nil, :extra_parameters => nil }.merge(options)
|
38
|
+
return nil unless opt[:name_id]
|
39
|
+
|
40
|
+
@request = REXML::Document.new
|
41
|
+
@request.context[:attribute_quote] = :quote
|
42
|
+
|
43
|
+
|
44
|
+
root = @request.add_element "saml2p:LogoutRequest", { "xmlns:saml2p" => PROTOCOL }
|
45
|
+
root.attributes['ID'] = @transaction_id
|
46
|
+
root.attributes['IssueInstant'] = @issue_instant
|
47
|
+
root.attributes['Version'] = "2.0"
|
48
|
+
root.attributes['Destination'] = @settings.single_logout_destination
|
49
|
+
|
50
|
+
issuer = root.add_element "saml2:Issuer", { "xmlns:saml2" => ASSERTION }
|
51
|
+
issuer.attributes['Format'] = "urn:oasis:names:tc:SAML:2.0:nameid-format:entity"
|
52
|
+
#issuer.text = @settings.issuer
|
53
|
+
#per la federazione trentina qui ci vanno i metadati...
|
54
|
+
issuer.text = @settings.idp_metadata
|
55
|
+
|
56
|
+
name_id = root.add_element "saml2:NameID", { "xmlns:saml2" => ASSERTION }
|
57
|
+
name_id.attributes['Format'] = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
|
58
|
+
name_id.attributes['NameQualifier'] = @settings.idp_name_qualifier
|
59
|
+
name_id.text = opt[:name_id]
|
60
|
+
# I believe the rest of these are optional
|
61
|
+
if @settings && @settings.sp_name_qualifier
|
62
|
+
name_id.attributes["SPNameQualifier"] = @settings.sp_name_qualifier
|
63
|
+
end
|
64
|
+
if opt[:session_index]
|
65
|
+
session_index = root.add_element "saml2p:SessionIndex" #, { "xmlns:samlp" => PROTOCOL }
|
66
|
+
session_index.text = opt[:session_index]
|
67
|
+
end
|
68
|
+
Logging.debug "Created LogoutRequest: #{@request}"
|
69
|
+
meta = Metadata.new(@settings)
|
70
|
+
return meta.create_slo_request( to_s, opt[:extra_parameters] )
|
71
|
+
#action, content = binding_select("SingleLogoutService")
|
72
|
+
#Logging.debug "action: #{action} content: #{content}"
|
73
|
+
#return [action, content]
|
74
|
+
end
|
75
|
+
|
76
|
+
# function to return the created request as an XML document
|
77
|
+
def to_xml
|
78
|
+
text = ""
|
79
|
+
@request.write(text, 1)
|
80
|
+
return text
|
81
|
+
end
|
82
|
+
def to_s
|
83
|
+
@request.to_s
|
84
|
+
end
|
85
|
+
# Functions for pulling values out from an IdP initiated LogoutRequest
|
86
|
+
def name_id
|
87
|
+
element = REXML::XPath.first(@request, "/p:LogoutRequest/a:NameID", {
|
88
|
+
"p" => PROTOCOL, "a" => ASSERTION } )
|
89
|
+
return nil if element.nil?
|
90
|
+
# Can't seem to get this to work right...
|
91
|
+
#element.context[:compress_whitespace] = ["NameID"]
|
92
|
+
#element.context[:compress_whitespace] = :all
|
93
|
+
str = element.text.gsub(/^\s+/, "")
|
94
|
+
str.gsub!(/\s+$/, "")
|
95
|
+
return str
|
96
|
+
end
|
97
|
+
|
98
|
+
def transaction_id
|
99
|
+
return @transaction_id if @transaction_id
|
100
|
+
element = REXML::XPath.first(@request, "/p:LogoutRequest", {
|
101
|
+
"p" => PROTOCOL} )
|
102
|
+
return nil if element.nil?
|
103
|
+
return element.attributes["ID"]
|
104
|
+
end
|
105
|
+
def is_valid?
|
106
|
+
validate(soft = true)
|
107
|
+
end
|
108
|
+
|
109
|
+
def validate!
|
110
|
+
validate( soft = false )
|
111
|
+
end
|
112
|
+
def validate( soft = true )
|
113
|
+
return false if @request.nil?
|
114
|
+
return false if @request.validate(@settings, soft) == false
|
115
|
+
|
116
|
+
return true
|
117
|
+
|
118
|
+
end
|
119
|
+
private
|
120
|
+
|
121
|
+
def self.timestamp
|
122
|
+
Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
#encoding: utf-8
|
2
|
+
|
3
|
+
require "rexml/document"
|
4
|
+
|
5
|
+
module Onelogin
|
6
|
+
module Saml
|
7
|
+
class LogoutResponse
|
8
|
+
include Coding
|
9
|
+
include Request
|
10
|
+
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion"
|
11
|
+
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol"
|
12
|
+
DSIG = "http://www.w3.org/2000/09/xmldsig#"
|
13
|
+
|
14
|
+
def initialize( options = { } )
|
15
|
+
opt = { :response => nil, :settings => nil }.merge(options)
|
16
|
+
# We've recieved a LogoutResponse from the IdP
|
17
|
+
if opt[:response]
|
18
|
+
begin
|
19
|
+
@response = XMLSecurity::SignedDocument.new(decode( opt[:response] ))
|
20
|
+
# Check to see if we have a root tag using the "protocol" namespace.
|
21
|
+
# If not, it means this is deflated text and we need to raise to
|
22
|
+
# the rescue below
|
23
|
+
raise if @response.nil?
|
24
|
+
raise if @response.root.nil?
|
25
|
+
raise if @response.root.namespace != PROTOCOL
|
26
|
+
document
|
27
|
+
rescue
|
28
|
+
@response = XMLSecurity::SignedDocument.new( inflate(decode( opt[:response] ) ) )
|
29
|
+
end
|
30
|
+
end
|
31
|
+
# We plan to create() a new LogoutResponse
|
32
|
+
if opt[:settings]
|
33
|
+
@settings = opt[:settings]
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Create a LogoutResponse to to the IdP's LogoutRequest
|
38
|
+
# (For IdP initiated SLO)
|
39
|
+
def create( options )
|
40
|
+
opt = { :transaction_id => nil,
|
41
|
+
:in_response_to => nil,
|
42
|
+
:status => "urn:oasis:names:tc:SAML:2.0:status:Success",
|
43
|
+
:extra_parameters => nil }.merge(options)
|
44
|
+
return nil if opt[:transaction_id].nil?
|
45
|
+
@response = REXML::Document.new
|
46
|
+
@response.context[:attribute_quote] = :quote
|
47
|
+
uuid = "_" + UUID.new.generate
|
48
|
+
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
|
49
|
+
root = @response.add_element "saml2p:LogoutResponse", { "xmlns:saml2p" => PROTOCOL }
|
50
|
+
root.attributes['ID'] = uuid
|
51
|
+
root.attributes['IssueInstant'] = time
|
52
|
+
root.attributes['Version'] = "2.0"
|
53
|
+
# Just convenient naming to accept both names as InResponseTo
|
54
|
+
if opt[:transaction_id]
|
55
|
+
root.attributes['InResponseTo'] = opt[:transaction_id]
|
56
|
+
elsif opt[:in_response_to]
|
57
|
+
root.attributes['InResponseTo'] = opt[:in_response_to]
|
58
|
+
end
|
59
|
+
if opt[:status]
|
60
|
+
status = root.add_element "saml2p:Status"
|
61
|
+
status_code = status.add_element "saml2p:StatusCode", {
|
62
|
+
"Value" => opt[:status]
|
63
|
+
}
|
64
|
+
end
|
65
|
+
if @settings && @settings.issuer
|
66
|
+
issuer = root.add_element "saml:Issuer", {
|
67
|
+
"xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion"
|
68
|
+
}
|
69
|
+
issuer.text = @settings.issuer
|
70
|
+
end
|
71
|
+
meta = Metadata.new( @settings )
|
72
|
+
Logging.debug "Created LogoutResponse:\n#{@response}"
|
73
|
+
return meta.create_slo_response( to_s, opt[:extra_parameters] )
|
74
|
+
|
75
|
+
#root.attributes['Destination'] = action
|
76
|
+
|
77
|
+
end
|
78
|
+
# function to return the created request as an XML document
|
79
|
+
def to_xml
|
80
|
+
text = ""
|
81
|
+
@response.write(text, 1)
|
82
|
+
return text
|
83
|
+
end
|
84
|
+
def to_s
|
85
|
+
@response.to_s
|
86
|
+
end
|
87
|
+
|
88
|
+
def issuer
|
89
|
+
element = REXML::XPath.first(@response, "/p:LogoutResponse/a:Issuer", {
|
90
|
+
"p" => PROTOCOL, "a" => ASSERTION} )
|
91
|
+
return nil if element.nil?
|
92
|
+
element.text
|
93
|
+
end
|
94
|
+
|
95
|
+
def in_response_to
|
96
|
+
element = REXML::XPath.first(@response, "/p:LogoutResponse", {
|
97
|
+
"p" => PROTOCOL })
|
98
|
+
return nil if element.nil?
|
99
|
+
element.attributes["InResponseTo"]
|
100
|
+
end
|
101
|
+
|
102
|
+
def success?
|
103
|
+
element = REXML::XPath.first(@response, "/p:LogoutResponse/p:Status/p:StatusCode", {
|
104
|
+
"p" => PROTOCOL })
|
105
|
+
return false if element.nil?
|
106
|
+
element.attributes["Value"] == "urn:oasis:names:tc:SAML:2.0:status:Success"
|
107
|
+
|
108
|
+
end
|
109
|
+
def is_valid?
|
110
|
+
validate(soft = true)
|
111
|
+
end
|
112
|
+
|
113
|
+
def validate!
|
114
|
+
validate( soft = false )
|
115
|
+
end
|
116
|
+
def validate( soft = true )
|
117
|
+
return false if @response.nil?
|
118
|
+
# Skip validation with a failed response if we don't have settings
|
119
|
+
return false if @settings.nil?
|
120
|
+
return false if @response.validate(@settings, soft) == false
|
121
|
+
|
122
|
+
return true
|
123
|
+
|
124
|
+
end
|
125
|
+
|
126
|
+
protected
|
127
|
+
def document
|
128
|
+
REXML::Document.new(@response)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,270 @@
|
|
1
|
+
require "rexml/document"
|
2
|
+
require "rexml/xpath"
|
3
|
+
require "net/https"
|
4
|
+
require "uri"
|
5
|
+
require "digest/md5"
|
6
|
+
|
7
|
+
# Class to return SP metadata based on the settings requested.
|
8
|
+
# Return this XML in a controller, then give that URL to the the
|
9
|
+
# IdP administrator. The IdP will poll the URL and your settings
|
10
|
+
# will be updated automatically
|
11
|
+
module Onelogin
|
12
|
+
module Saml
|
13
|
+
class Metadata
|
14
|
+
include REXML
|
15
|
+
include Coding
|
16
|
+
# a few symbols for SAML class names
|
17
|
+
HTTP_POST = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
|
18
|
+
HTTP_GET = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
|
19
|
+
|
20
|
+
def initialize(settings=nil)
|
21
|
+
if settings
|
22
|
+
@settings = settings
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def generate(settings)
|
27
|
+
meta_doc = REXML::Document.new
|
28
|
+
root = meta_doc.add_element "md:EntityDescriptor", {
|
29
|
+
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata",
|
30
|
+
"xmlns:xml" => "http://www.w3.org/XML/1998/namespace",
|
31
|
+
"cacheDuration" => "P1M"
|
32
|
+
}
|
33
|
+
if settings.issuer != nil
|
34
|
+
root.attributes["entityID"] = settings.issuer
|
35
|
+
end
|
36
|
+
sp_sso = root.add_element "md:SPSSODescriptor", {
|
37
|
+
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
|
38
|
+
"WantAssertionsSigned" => "true"
|
39
|
+
|
40
|
+
}
|
41
|
+
name_identifier_formats = settings.name_identifier_format
|
42
|
+
if name_identifier_formats != nil
|
43
|
+
name_id = []
|
44
|
+
name_identifier_formats.each_with_index{ |format, index|
|
45
|
+
name_id[index] = sp_sso.add_element "md:NameIDFormat"
|
46
|
+
name_id[index].text = format
|
47
|
+
}
|
48
|
+
|
49
|
+
end
|
50
|
+
if settings.sp_cert != nil
|
51
|
+
keyDescriptor = sp_sso.add_element "md:KeyDescriptor", {
|
52
|
+
"use" => "signing"
|
53
|
+
}
|
54
|
+
keyInfo = keyDescriptor.add_element "ds:KeyInfo", {
|
55
|
+
"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"
|
56
|
+
}
|
57
|
+
x509Data = keyInfo.add_element "ds:X509Data"
|
58
|
+
x509Certificate = x509Data.add_element "ds:X509Certificate"
|
59
|
+
file = ""
|
60
|
+
File.foreach(settings.sp_cert){ |line|
|
61
|
+
file += line unless (line.include?("RSA PUBLIC KEY") || line.include?("CERTIFICATE"))
|
62
|
+
}
|
63
|
+
x509Certificate.text = file
|
64
|
+
end
|
65
|
+
if settings.assertion_consumer_service_url != nil
|
66
|
+
sp_sso.add_element "md:AssertionConsumerService", {
|
67
|
+
# Add this as a setting to create different bindings?
|
68
|
+
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
69
|
+
"Location" => settings.assertion_consumer_service_url,
|
70
|
+
"index" => "1"
|
71
|
+
}
|
72
|
+
end
|
73
|
+
if settings.single_logout_service_url != nil
|
74
|
+
sp_sso.add_element "md:SingleLogoutService", {
|
75
|
+
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
|
76
|
+
"Location" => settings.single_logout_service_url
|
77
|
+
}
|
78
|
+
sp_sso.add_element "md:SingleLogoutService", {
|
79
|
+
"Binding" => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
80
|
+
"Location" => settings.single_logout_service_url
|
81
|
+
}
|
82
|
+
end
|
83
|
+
meta_doc << REXML::XMLDecl.new(version='1.0', encoding='UTF-8')
|
84
|
+
ret = ""
|
85
|
+
# pretty print the XML so IdP administrators can easily see what the SP supports
|
86
|
+
meta_doc.write(ret, 1)
|
87
|
+
|
88
|
+
#Logging.debug "Generated metadata:\n#{ret}"
|
89
|
+
|
90
|
+
return ret
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
def create_sso_request(message, extra_parameters = {} )
|
95
|
+
build_message( :type => "SAMLRequest",
|
96
|
+
:service => "SingleSignOnService",
|
97
|
+
:message => message, :extra_parameters => extra_parameters)
|
98
|
+
end
|
99
|
+
def create_sso_response(message, extra_parameters = {} )
|
100
|
+
build_message( :type => "SAMLResponse",
|
101
|
+
:service => "SingleSignOnService",
|
102
|
+
:message => message, :extra_parameters => extra_parameters)
|
103
|
+
end
|
104
|
+
def create_slo_request(message, extra_parameters = {} )
|
105
|
+
build_message( :type => "SAMLRequest",
|
106
|
+
:service => "SingleLogoutService",
|
107
|
+
:message => message, :extra_parameters => extra_parameters)
|
108
|
+
end
|
109
|
+
def create_slo_response(message, extra_parameters = {} )
|
110
|
+
build_message( :type => "SAMLResponse",
|
111
|
+
:service => "SingleLogoutService",
|
112
|
+
:message => message, :extra_parameters => extra_parameters)
|
113
|
+
end
|
114
|
+
|
115
|
+
# Construct a SAML message using information in the IdP metadata.
|
116
|
+
# :type can be either "SAMLRequest" or "SAMLResponse"
|
117
|
+
# :service refers to the Binding method,
|
118
|
+
# either "SingleLogoutService" or "SingleSignOnService"
|
119
|
+
# :message is the SAML message itself (XML)
|
120
|
+
# I've provided easy to use wrapper functions above
|
121
|
+
def build_message( options = {} )
|
122
|
+
opt = { :type => nil, :service => nil, :message => nil, :extra_parameters => nil }.merge(options)
|
123
|
+
url = binding_select( opt[:service] )
|
124
|
+
return message_get( opt[:type], url, opt[:message], opt[:extra_parameters] )
|
125
|
+
end
|
126
|
+
|
127
|
+
# get the IdP metadata, and select the appropriate SSO binding
|
128
|
+
# that we can support. Currently this is HTTP-Redirect and HTTP-POST
|
129
|
+
# but more could be added in the future
|
130
|
+
def binding_select(service)
|
131
|
+
# first check if we're still using the old hard coded method for
|
132
|
+
# backwards compatability
|
133
|
+
if service == "SingleSignOnService" && @settings.idp_metadata == nil && @settings.idp_sso_target_url != nil
|
134
|
+
return @settings.idp_sso_target_url
|
135
|
+
end
|
136
|
+
if service == "SingleLogoutService" && @settings.idp_metadata == nil && @settings.idp_slo_target_url != nil
|
137
|
+
return @settings.idp_slo_target_url
|
138
|
+
end
|
139
|
+
|
140
|
+
meta_doc = get_idp_metadata
|
141
|
+
|
142
|
+
return nil unless meta_doc
|
143
|
+
# first try POST
|
144
|
+
sso_element = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor/md:#{service}[@Binding='#{HTTP_POST}']")
|
145
|
+
if !sso_element.nil?
|
146
|
+
@URL = sso_element.attributes["Location"]
|
147
|
+
#Logging.debug "binding_select: POST to #{@URL}"
|
148
|
+
return @URL
|
149
|
+
end
|
150
|
+
|
151
|
+
# next try GET
|
152
|
+
sso_element = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor/md:#{service}[@Binding='#{HTTP_GET}']")
|
153
|
+
if !sso_element.nil?
|
154
|
+
@URL = sso_element.attributes["Location"]
|
155
|
+
Logging.debug "binding_select: GET from #{@URL}"
|
156
|
+
return @URL
|
157
|
+
end
|
158
|
+
# other types we might want to add in the future: SOAP, Artifact
|
159
|
+
end
|
160
|
+
|
161
|
+
# Retrieve the remote IdP metadata from the URL or a cached copy
|
162
|
+
# returns a REXML document of the metadata
|
163
|
+
def get_idp_metadata
|
164
|
+
return false if @settings.idp_metadata.nil?
|
165
|
+
|
166
|
+
# Look up the metdata in cache first
|
167
|
+
id = Digest::MD5.hexdigest(@settings.idp_metadata)
|
168
|
+
|
169
|
+
uri = URI.parse(@settings.idp_metadata)
|
170
|
+
if uri.scheme == "http"
|
171
|
+
response = Net::HTTP.get_response(uri)
|
172
|
+
meta_text = response.body
|
173
|
+
elsif uri.scheme == "https"
|
174
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
175
|
+
http.use_ssl = true
|
176
|
+
# Most IdPs will probably use self signed certs
|
177
|
+
#http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
178
|
+
http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
179
|
+
get = Net::HTTP::Get.new(uri.request_uri)
|
180
|
+
response = http.request(get)
|
181
|
+
meta_text = response.body
|
182
|
+
end
|
183
|
+
testo_response = meta_text.sub!(' xmlns:xml="http://www.w3.org/XML/1998/namespace"', '')
|
184
|
+
doc = REXML::Document.new(testo_response)
|
185
|
+
extract_certificate(doc)
|
186
|
+
return doc
|
187
|
+
|
188
|
+
|
189
|
+
# lookup = @cache.read(id)
|
190
|
+
# if lookup != nil
|
191
|
+
# Logging.debug "IdP metadata cached lookup for #{@settings.idp_metadata}"
|
192
|
+
# doc = REXML::Document.new( lookup )
|
193
|
+
# extract_certificate( doc )
|
194
|
+
# return doc
|
195
|
+
# end
|
196
|
+
|
197
|
+
# Logging.debug "IdP metadata cache miss on #{@settings.idp_metadata}"
|
198
|
+
# # cache miss
|
199
|
+
# if File.exists?(@settings.idp_metadata)
|
200
|
+
# fp = File.open( @settings.idp_metadata, "r")
|
201
|
+
# meta_text = fp.read
|
202
|
+
# else
|
203
|
+
# uri = URI.parse(@settings.idp_metadata)
|
204
|
+
# if uri.scheme == "http"
|
205
|
+
# response = Net::HTTP.get_response(uri)
|
206
|
+
# meta_text = response.body
|
207
|
+
# elsif uri.scheme == "https"
|
208
|
+
# http = Net::HTTP.new(uri.host, uri.port)
|
209
|
+
# http.use_ssl = true
|
210
|
+
# # Most IdPs will probably use self signed certs
|
211
|
+
# #http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
212
|
+
# http.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
213
|
+
# get = Net::HTTP::Get.new(uri.request_uri)
|
214
|
+
# response = http.request(get)
|
215
|
+
# meta_text = response.body
|
216
|
+
# end
|
217
|
+
# end
|
218
|
+
# # Add it to the cache
|
219
|
+
# @cache.write(id, meta_text, @settings.idp_metadata_ttl )
|
220
|
+
# doc = REXML::Document.new( meta_text )
|
221
|
+
# extract_certificate(doc)
|
222
|
+
# return doc
|
223
|
+
end
|
224
|
+
|
225
|
+
def extract_certificate(meta_doc)
|
226
|
+
|
227
|
+
# pull out the x509 tag
|
228
|
+
x509 = REXML::XPath.first(meta_doc, "/md:EntityDescriptor/md:IDPSSODescriptor"+"/md:KeyDescriptor"+"/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
|
229
|
+
)
|
230
|
+
# If the IdP didn't specify the use attribute
|
231
|
+
if x509.nil?
|
232
|
+
x509 = REXML::XPath.first(meta_doc,
|
233
|
+
"/EntityDescriptor/IDPSSODescriptor" +
|
234
|
+
"/KeyDescriptor" +
|
235
|
+
"/ds:KeyInfo/ds:X509Data/ds:X509Certificate"
|
236
|
+
)
|
237
|
+
end
|
238
|
+
@settings.idp_cert = x509.text.gsub(/\n/, "").gsub(/\t/, "")
|
239
|
+
end
|
240
|
+
|
241
|
+
# construct the parameter list on the URL and return
|
242
|
+
def message_get( type, url, message, extra_parameters = {} )
|
243
|
+
|
244
|
+
params = Hash.new
|
245
|
+
if extra_parameters
|
246
|
+
params.merge!(extra_parameters)
|
247
|
+
end
|
248
|
+
# compress GET requests to try and stay under that 8KB request limit
|
249
|
+
|
250
|
+
#fa il deflate di samlrequest
|
251
|
+
params[type] = encode( deflate( message ) )
|
252
|
+
|
253
|
+
Logging.debug "#{type}=#{params[type]}"
|
254
|
+
|
255
|
+
uri = Addressable::URI.parse(url)
|
256
|
+
if uri.query_values == nil
|
257
|
+
uri.query_values = params
|
258
|
+
else
|
259
|
+
# solution to stevenwilkin's parameter merge
|
260
|
+
uri.query_values = params.merge(uri.query_values)
|
261
|
+
end
|
262
|
+
url = uri.to_s
|
263
|
+
#url = @URL + "?SAMLRequest=" + @request_params["SAMLRequest"]
|
264
|
+
Logging.debug "Sending to URL #{url}"
|
265
|
+
return url
|
266
|
+
end
|
267
|
+
|
268
|
+
end
|
269
|
+
end
|
270
|
+
end
|