diff --git a/README.md b/README.md index 48a8b1aa7..417832fba 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ def saml_settings settings.assertion_consumer_service_url = "http://#{request.host}/saml/finalize" settings.issuer = request.host - settings.idp_sso_target_url = "https://app.onelogin.com/saml/signon/#{OneLoginAppId}" + settings.idp_sso_target_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}" settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" # Optional for most SAML IdPs @@ -91,6 +91,33 @@ class SamlController < ApplicationController end end ``` +## Metadata Based Configuration + +The method above requires a little extra work to manually specify attributes about the IdP. (And your SP application) There's an easier method -- use a metadata exchange. Metadata is just an XML file that defines the capabilities of both the IdP and the SP application. It also contains the X.509 public +key certificates which add to the trusted relationship. The IdP administrator can also configure custom settings for an SP based on the metadata. + +Using ```idp_metadata_parser.parse_remote``` IdP metadata will be added to the settings withouth further ado. + +```ruby +def saml_settings + + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + # Returns OneLogin::RubySaml::Settings prepopulated with idp metadata + settings = idp_metadata_parser.parse_remote("https://example.com/auth/saml2/idp/metadata") + + settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume" + settings.issuer = request.host + settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" + # Optional for most SAML IdPs + settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport" + + settings +end +``` +The following attributes are set: + * id_sso_target_url + * idp_slo_target_url + * id_cert_fingerpint If are using saml:AttributeStatement to transfare metadata, like the user name, you can access all the attributes through response.attributes. It contains all the saml:AttributeStatement with its 'Name' as a indifferent key and the one saml:AttributeValue as value. diff --git a/lib/onelogin/ruby-saml/idp_metadata_parser.rb b/lib/onelogin/ruby-saml/idp_metadata_parser.rb index a87427676..afb4d60f3 100644 --- a/lib/onelogin/ruby-saml/idp_metadata_parser.rb +++ b/lib/onelogin/ruby-saml/idp_metadata_parser.rb @@ -16,6 +16,11 @@ class IdpMetadataParser attr_reader :document + def parse_remote(url, validate_cert = true) + idp_metadata = get_idp_metadata(url, validate_cert) + parse(idp_metadata) + end + def parse(idp_metadata) @document = REXML::Document.new(idp_metadata) @@ -29,6 +34,29 @@ def parse(idp_metadata) private + # Retrieve the remote IdP metadata from the URL or a cached copy + # # returns a REXML document of the metadata + def get_idp_metadata(url, validate_cert) + uri = URI.parse(url) + if uri.scheme == "http" + response = Net::HTTP.get_response(uri) + meta_text = response.body + elsif uri.scheme == "https" + http = Net::HTTP.new(uri.host, uri.port) + http.use_ssl = true + # Most IdPs will probably use self signed certs + if validate_cert + http.verify_mode = OpenSSL::SSL::VERIFY_PEER + else + http.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + get = Net::HTTP::Get.new(uri.request_uri) + response = http.request(get) + meta_text = response.body + end + meta_text + end + def single_signon_service_url node = REXML::XPath.first(document, "/md:EntityDescriptor/md:IDPSSODescriptor/md:SingleSignOnService/@Location", { "md" => METADATA }) node.value if node diff --git a/test/idp_metadata_parser_test.rb b/test/idp_metadata_parser_test.rb index daab729b3..2556e9e40 100644 --- a/test/idp_metadata_parser_test.rb +++ b/test/idp_metadata_parser_test.rb @@ -1,7 +1,13 @@ require File.expand_path(File.join(File.dirname(__FILE__), "test_helper")) +require 'net/http' +require 'net/https' class IdpMetadataParserTest < Test::Unit::TestCase + class MockResponse + attr_accessor :body + end + context "parsing an IdP descriptor file" do should "extract settings details from xml" do idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new @@ -14,4 +20,35 @@ class IdpMetadataParserTest < Test::Unit::TestCase end end + context "download and parse IdP descriptor file" do + setup do + mock_response = MockResponse.new + mock_response.body = idp_metadata + @url = "https://example.com" + uri = URI(@url) + + @http = Net::HTTP.new(uri.host, uri.port) + Net::HTTP.expects(:new).returns(@http) + @http.expects(:request).returns(mock_response) + end + + + should "extract settings from remote xml" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + settings = idp_metadata_parser.parse_remote(@url) + + assert_equal "https://example.hello.com/access/saml/login", settings.idp_sso_target_url + assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.idp_cert_fingerprint + assert_equal "https://example.hello.com/access/saml/logout", settings.idp_slo_target_url + assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode + end + + should "accept self signed certificate if insturcted" do + idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new + settings = idp_metadata_parser.parse_remote(@url, false) + + assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode + end + end + end