First push.(with ruby-saml 1.14.0)

This commit is contained in:
BoHung Chiu 2022-05-14 11:53:04 +08:00
commit c499eff45d
249 changed files with 18954 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
.bundle/
log/*.log
pkg/
test/dummy/db/*.sqlite3
test/dummy/db/*.sqlite3-journal
test/dummy/log/*.log
test/dummy/tmp/
test/dummy/.sass-cache

14
Gemfile Normal file
View File

@ -0,0 +1,14 @@
source "https://rubygems.org"
# Declare your gem's dependencies in sso_login_box.gemspec.
# Bundler will treat runtime dependencies like base dependencies, and
# development dependencies will be added by default to the :development group.
gemspec
# Declare any dependencies that are still in development here instead of in
# your gemspec. These might include edge Rails or gems from your path or
# Git. Remember to move these dependencies to your gemspec before releasing
# your gem to rubygems.org.
# To use debugger
# gem 'debugger'

20
MIT-LICENSE Normal file
View File

@ -0,0 +1,20 @@
Copyright 2021 BOHUNG CHIU
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

3
README.rdoc Normal file
View File

@ -0,0 +1,3 @@
= SsoLoginBox
This project rocks and uses MIT-LICENSE.

34
Rakefile Normal file
View File

@ -0,0 +1,34 @@
begin
require 'bundler/setup'
rescue LoadError
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
end
require 'rdoc/task'
RDoc::Task.new(:rdoc) do |rdoc|
rdoc.rdoc_dir = 'rdoc'
rdoc.title = 'SsoLoginBox'
rdoc.options << '--line-numbers'
rdoc.rdoc_files.include('README.rdoc')
rdoc.rdoc_files.include('lib/**/*.rb')
end
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
load 'rails/tasks/engine.rake'
Bundler::GemHelper.install_tasks
require 'rake/testtask'
Rake::TestTask.new(:test) do |t|
t.libs << 'lib'
t.libs << 'test'
t.pattern = 'test/**/*_test.rb'
t.verbose = false
end
task default: :test

View File

View File

@ -0,0 +1,125 @@
class SsoLoginBoxController < SessionsController
require 'openssl'
require 'onelogin/ruby-saml'
OpenSSL::SSL::VERIFY_PEER = OpenSSL::SSL::VERIFY_NONE
def sso_auth_page
session[:referer_url] = params[:referer_url]
puts ["session", session, session.to_hash]
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def sso_sign_in
puts ["SSO sign in"]
puts "------------------------------"
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {:settings => saml_settings, :allowed_clock_drift=> "60"})
# puts response.inspect
# response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
# response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
# response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
# response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doesn't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
# response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
attributes = response.attributes
puts ["attributes", attributes.inspect]
if true#["f", "s"].include?(attributes["AccountTypeCode"])
email = attributes["http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"]
member_name = attributes["ChineseName"]
sid = attributes["SEQ"]
matched_member = sid.present? ? MemberProfile.where(:sid=>sid).first : nil
user = nil
if matched_member
user = matched_member.user
else
matched_member = MemberProfile.where(:email=>email).first
if matched_member
user = matched_member.user
else
user = member_name.present? ? User.where("member_name.zh_tw"=> member_name).first : nil
end
end
if !user.nil?
session[:sso_token] = user.id
session[:user_id] = user.id
session[:login_referer] = nil
if params[:referer_url].present?
redirect_to URI.parse(params[:referer_url]).path
else
redirect_to admin_dashboards_path
end
else
@login_referer = session[:referer_url]
flash.now.alert = I18n.t("sso_login_box_for_ntu.user_not_in_database",:sid=>sid,:name=>member_name,:email=>email).gsub("\n","<br>").html_safe
render "new"
end
else
@login_referer = session[:referer_url]
flash.now.alert = I18n.t("sso_login_box_for_ntu.not_staff")
render "new"
end
else
puts ["errors", response.instance_variable_get(:@errors)]
@login_referer = session[:referer_url]
flash.now.alert = I18n.t("sso_login_box_for_ntu.sso_authorized_failure")
render "new"
end
end
def delete_session
reset_session
end
# Create a SP initiated SLO
def sp_logout_request
# LogoutRequest accepts plain browser requests w/o paramters
settings = saml_settings
if settings.idp_slo_service_url.nil?
delete_session
else
logout_request = OneLogin::RubySaml::Logoutrequest.new
if settings.name_identifier_value.nil?
settings.name_identifier_value = session[:user_id]
end
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
logged_user = session[:user_id]
delete_session
# Save the transaction_id to compare it with the response we get back
session[:transaction_id] = logout_request.uuid
session[:logged_out_user] = logged_user
relayState = "https://#{request.host}"
redirect_to(logout_request.create(settings, :RelayState => relayState))
end
end
private
def saml_settings
settings = OneLogin::RubySaml::Settings.new
request_host = request.host
settings.assertion_consumer_service_url = "https://#{request_host}/ntu_sso/response?referer_url=#{params[:referer_url]}"
settings.issuer = request_host
settings.idp_sso_target_url = "https://adfs.ntu.edu.tw/adfs/ls/"
# settings.idp_sso_target_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings.idp_slo_target_url = "https://adfs.ntu.edu.tw/adfs/ls/clearall.aspx?url=https://#{request_host}"
# settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.idp_cert_fingerprint = "34:65:21:EB:1C:F8:43:52:69:CC:93:B8:D0:6B:F7:5D:9D:09:9D:76"
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
# settings.security[:signature_method] = XMLSecurity::Document::SHA256
# 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"
# # or as an array
# settings.authn_context = [
# "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
# "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
# ]
# Optional bindings (defaults to Redirect for logout POST for ACS)
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings
end
end

12
bin/rails Normal file
View File

@ -0,0 +1,12 @@
#!/usr/bin/env ruby
# This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application.
ENGINE_ROOT = File.expand_path('../..', __FILE__)
ENGINE_PATH = File.expand_path('../../lib/sso_login_box/engine', __FILE__)
# Set up gems listed in the Gemfile.
ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
require 'rails/all'
require 'rails/engine/commands'

5
config/locales/en.yml Normal file
View File

@ -0,0 +1,5 @@
en:
sso_login_box_for_ncu:
user_not_in_database: "Name: %{name}\nEmail: %{email}\nStaff number: %{sid}\n not in database. Ask the administrator to help you create your account first and then try again."
sso_authorized_failure: "SSO Authorized failure!"
not_staff: "SSO only provide professor, Associate Professor and administration staff to login!"

5
config/locales/zh_tw.yml Normal file
View File

@ -0,0 +1,5 @@
zh_tw:
sso_login_box_for_ntu:
user_not_in_database: "姓名: %{name}\nEmail: %{email}\n教師職員編號: %{sid}\n在資料庫中不存在。請聯繫網站管理員創建該使用者之會員後再進行登入。"
sso_authorized_failure: "sso驗證錯誤!"
not_staff: "SSO只提供 教授 、 副教授 、 行政人員 進行登入!"

9
config/routes.rb Normal file
View File

@ -0,0 +1,9 @@
Rails.application.routes.draw do
locales = Site.first.in_use_locales rescue I18n.available_locales
scope "(:locale)", locale: Regexp.new(locales.join("|")) do
post 'ntu_sso/response', to: 'sso_login_box#sso_sign_in'
get 'ntu_sso', to: 'sso_login_box#sso_auth_page'
get 'ntu_sso/logout', to: 'sso_login_box#sp_logout_request'
end
end

7
lib/sso_login_box.rb Normal file
View File

@ -0,0 +1,7 @@
require "sso_login_box/engine"
module SsoLoginBox
def self.logout_url
"/ntu_sso/logout"
end
end

View File

@ -0,0 +1,11 @@
require "yaml"
module SsoLoginBox
class Engine < ::Rails::Engine
initializer "sso_login_box" do
OrbitApp.registration "SsoLoginBox", :type => "ModuleApp" do
base_url File.expand_path File.dirname(__FILE__)
set_keyword_contstraints ['/ntu_sso']
end
end
end
end

View File

@ -0,0 +1,14 @@
module SsoLoginBox::LoginTag
def self.show
"<style type=\"text/css\">
a.sso_login_button:focus {
outline: 0 !important;
}
</style>
<div class=\"pull-right\">
<a class=\"sso_login_button btn\" href=\"/ntu_sso?referer_url=#{(URI.encode(OrbitHelper.request.original_url.gsub('?','&')) rescue "")}\" style=\"border: 0.1em solid black;\">
NTU SSO Login
</a>
</div>"
end
end

View File

@ -0,0 +1,3 @@
module SsoLoginBox
VERSION = "0.0.1"
end

View File

@ -0,0 +1,4 @@
# desc "Explaining what the task does"
# task :sso_login_box do
# # Task goes here
# end

View File

@ -0,0 +1,5 @@
README.rdoc
lib/**/*.rb
bin/*
features/**/*.feature
LICENSE

14
ruby-saml-custom/.gitignore vendored Normal file
View File

@ -0,0 +1,14 @@
*.sw?
.DS_Store
coverage
rdoc
pkg
Gemfile.lock
gemfiles/*.lock
.idea/*
lib/Lib.iml
test/Test.iml
.rvmrc
*.gem
.bundle
*.patch

View File

@ -0,0 +1,275 @@
# Ruby SAML Changelog
### 1.14.0 (Feb 01, 2022)
* [#627](https://github.com/onelogin/ruby-saml/pull/627) Support escape downcasing for validating SLO Signatures of ADFS/Azure
* [#633](https://github.com/onelogin/ruby-saml/pull/633) Support ability to change ID prefix
* Make the uuid editable on the SAML Messages generated by the toolkit
* [#622](https://github.com/onelogin/ruby-saml/pull/622) Add security setting to more strictly enforce audience validation
### 1.13.0 (Sept 06, 2021)
* [#611](https://github.com/onelogin/ruby-saml/pull/601) Replace MAX_BYTE_SIZE constant with setting: message_max_bytesize
* [#605](https://github.com/onelogin/ruby-saml/pull/605) :allowed_clock_drift is now bidrectional
* [#614](https://github.com/onelogin/ruby-saml/pull/614) Support :name_id_format option for IdpMetadataParser
* [#611](https://github.com/onelogin/ruby-saml/pull/611) IdpMetadataParser should always set idp_cert_multi, even when there is only one cert
* [#610](https://github.com/onelogin/ruby-saml/pull/610) New IDP sso/slo binding params which deprecate :embed_sign
* [#602](https://github.com/onelogin/ruby-saml/pull/602) Refactor the OneLogin::RubySaml::Metadata class
* [#586](https://github.com/onelogin/ruby-saml/pull/586) Support milliseconds in cacheDuration parsing
* [#585](https://github.com/onelogin/ruby-saml/pull/585) Do not append " | " to StatusCode unnecessarily
* [#607](https://github.com/onelogin/ruby-saml/pull/607) Clean up
* Add warning about the use of IdpMetadataParser class and SSRF
* CI: Migrate from Travis to Github Actions
### 1.12.2 (Apr 08, 2021)
* [#575](https://github.com/onelogin/ruby-saml/pull/575) Fix SloLogoutresponse bug on LogoutRequest
### 1.12.1 (Apr 05, 2021)
* Fix XPath typo incompatible with Rexml 3.2.5
* Refactor GCM support
### 1.12.0 (Feb 18, 2021)
* Support AES-128-GCM, AES-192-GCM, and AES-256-GCM encryptions
* Parse & return SLO ResponseLocation in IDPMetadataParser & Settings
* Adding idp_sso_service_url and idp_slo_service_url settings
* [#536](https://github.com/onelogin/ruby-saml/pull/536) Adding feth method to be able retrieve attributes based on regex
* Reduce size of built gem by excluding the test folder
* Improve protection on Zlib deflate decompression bomb attack.
* Add ValidUntil and cacheDuration support on Metadata generator
* Add support for cacheDuration at the IdpMetadataParser
* Support customizable statusCode on generated LogoutResponse
* [#545](https://github.com/onelogin/ruby-saml/pull/545) More specific error messages for signature validation
* Support Process Transform
* Raise SettingError if invoking an action with no endpoint defined on the settings
* Made IdpMetadataParser more extensible for subclasses
*[#548](https://github.com/onelogin/ruby-saml/pull/548) Add :skip_audience option
* [#555](https://github.com/onelogin/ruby-saml/pull/555) Define 'soft' variable to prevent exception when doc cert is invalid
* Improve documentation
### 1.11.0 (Jul 24, 2019)
* Deprecate settings.issuer in favor of settings.sp_entity_id
* Add support for certification expiration
### 1.10.2 (Apr 29, 2019)
* Add valid until, accessor
* Fix Rubygem metadata that requested nokogiri <= 1.5.11
### 1.10.1 (Apr 08, 2019)
* Fix ruby 1.8.7 incompatibilities
### 1.10.0 (Mar 21, 2019)
* Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user to be authenticated
* Improves IdpMetadataParser to allow parse multiple IDPSSODescriptors
* Improves format_cert method to accept certs with /\x0d/
* Forces nokogiri >= 1.8.2 when possible
### 1.9.0 (Sept 03, 2018)
* [#458](https://github.com/onelogin/ruby-saml/pull/458) Remove ruby 2.4+ warnings
* Improve JRuby support
* [#465](https://github.com/onelogin/ruby-saml/pull/465) Extend Settings initialization with the new keep_security_attributes parameter
* Fix wrong message when SessionNotOnOrAfter expired
* [#471](https://github.com/onelogin/ruby-saml/pull/471) Allow for `allowed_clock_drift` to be set as a string
### 1.8.0 (April 23, 2018)
* [#437](https://github.com/onelogin/ruby-saml/issues/437) Creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState should not send empty RelayState URL param
* [#454](https://github.com/onelogin/ruby-saml/pull/454) Added Response available options
* [#453](https://github.com/onelogin/ruby-saml/pull/453) Raise a more descriptive exception if idp_sso_target_url is missing
* [#452](https://github.com/onelogin/ruby-saml/pull/452) Fix behavior of skip_conditions flag on Response
* [#449](https://github.com/onelogin/ruby-saml/pull/449) Add ability to skip authnstatement validation
* Clear cached values to be able to use IdpMetadataParser more than once
* Updated invalid audience error message
### 1.7.2 (Feb 28, 2018)
* [#446](https://github.com/onelogin/ruby-saml/pull/446) Normalize text returned by OneLogin::RubySaml::Utils.element_text
### 1.7.1 (Feb 28, 2018)
* [#444](https://github.com/onelogin/ruby-saml/pull/444) Fix audience validation for empty audience restriction
### 1.7.0 (Feb 27, 2018)
* Fix vulnerability CVE-2017-11428. Process text of nodes properly, ignoring comments
### 1.6.1 (January 15, 2018)
* [#428](https://github.com/onelogin/ruby-saml/issues/428) Fix a bug on IdPMetadataParser when parsing certificates
* [#426](https://github.com/onelogin/ruby-saml/pull/426) Ensure `Rails` responds to `logger`
### 1.6.0 (November 27, 2017)
* [#418](https://github.com/onelogin/ruby-saml/pull/418) Improve SAML message signature validation using original encoded parameters instead decoded in order to avoid conflicts (URL-encoding is not canonical, reported issues with ADFS)
* [#420](https://github.com/onelogin/ruby-saml/pull/420) Expose NameID Format on SloLogoutrequest
* [#423](https://github.com/onelogin/ruby-saml/pull/423) Allow format_cert to work with chained certificates
* [#422](https://github.com/onelogin/ruby-saml/pull/422) Use to_s for requested attribute value
### 1.5.0 (August 31, 2017)
* [#400](https://github.com/onelogin/ruby-saml/pull/400) When validating Signature use stored IdP certficate if Signature contains no info about Certificate
* [#402](https://github.com/onelogin/ruby-saml/pull/402) Fix validate_response_state method that rejected SAMLResponses when using idp_cert_multi and idp_cert and idp_cert_fingerprint were not provided.
* [#411](https://github.com/onelogin/ruby-saml/pull/411) Allow space in Base64 string
* [#407](https://github.com/onelogin/ruby-saml/issues/407) Improve IdpMetadataParser raising an ArgumentError when parser method receive a metadata string with no IDPSSODescriptor element.
* [#374](https://github.com/onelogin/ruby-saml/issues/374) Support more than one level of StatusCode
* [#405](https://github.com/onelogin/ruby-saml/pull/405) Support ADFS encrypted key (Accept KeyInfo nodes with no ds namespace)
### 1.4.3 (May 18, 2017)
* Added SubjectConfirmation Recipient validation
* [#393](https://github.com/onelogin/ruby-saml/pull/393) Implement IdpMetadataParser#parse_to_hash
* Adapt IdP XML metadata parser to take care of multiple IdP certificates and be able to inject the data obtained on the settings.
* Improve binding detection on idp metadata parser
* [#373](https://github.com/onelogin/ruby-saml/pull/373) Allow metadata to be retrieved from source containing data for multiple entities
* Be able to register future SP x509cert on the settings and publish it on SP metadata
* Be able to register more than 1 Identity Provider x509cert, linked with an specific use (signing or encryption.
* Improve regex to detect base64 encoded messages
* Fix binding configuration example in README.md
* Add Fix SLO request. Correct NameQualifier/SPNameQualifier values.
* Validate serial number as string to work around libxml2 limitation
* Propagate isRequired on md:RequestedAttribute when generating SP metadata
### 1.4.2 (January 11, 2017)
* Improve tests format
* Fix nokogiri requirements based on ruby version
* Only publish `KeyDescriptor[use="encryption"]` at SP metadata if `security[:want_assertions_encrypted]` is true
* Be able to skip destination validation
* Improved inResponse validation on SAMLResponses and LogoutResponses
* [#354](https://github.com/onelogin/ruby-saml/pull/354) Allow scheme and domain to match ignoring case
* [#363](https://github.com/onelogin/ruby-saml/pull/363) Add support for multiple requested attributes
### 1.4.1 (October 19, 2016)
* [#357](https://github.com/onelogin/ruby-saml/pull/357) Add EncryptedAttribute support. Improve decrypt method
* Allow multiple authn_context_decl_ref in settings
* Allow options[:settings] to be an hash for Settings overrides in IdpMetadataParser#parse
* Recover issuers method
### 1.4.0 (October 13, 2016)
* Several security improvements:
* Conditions element required and unique.
* AuthnStatement element required and unique.
* SPNameQualifier must math the SP EntityID
* Reject saml:Attribute element with same “Name” attribute
* Reject empty nameID
* Require Issuer element. (Must match IdP EntityID).
* Destination value can't be blank (if present must match ACS URL).
* Check that the EncryptedAssertion element only contains 1 Assertion element.
* [#335](https://github.com/onelogin/ruby-saml/pull/335) Explicitly parse as XML and fix setting of Nokogiri options.
* [#345](https://github.com/onelogin/ruby-saml/pull/345)Support multiple settings.auth_context
* More tests to prevent XML Signature Wrapping
* [#342](https://github.com/onelogin/ruby-saml/pull/342) Correct the usage of Mutex
* [352](https://github.com/onelogin/ruby-saml/pull/352) Support multiple AttributeStatement tags
### 1.3.1 (July 10, 2016)
* Fix response_test.rb of gem 1.3.0
* Add reference to Security Guidelines
* Update License
* [#334](https://github.com/onelogin/ruby-saml/pull/334) Keep API backward-compatibility on IdpMetadataParser fingerprint method.
### 1.3.0 (June 24, 2016)
* [Security Fix](https://github.com/onelogin/ruby-saml/commit/a571f52171e6bfd87db59822d1d9e8c38fb3b995) Add extra validations to prevent Signature wrapping attacks
* Fix XMLSecurity SHA256 and SHA512 uris
* [#326](https://github.com/onelogin/ruby-saml/pull/326) Fix Destination validation
### 1.2.0 (April 29, 2016)
* [#269](https://github.com/onelogin/ruby-saml/pull/269) Refactor error handling; allow collect error messages when soft=true (normal validation stop after find first error)
* [#289](https://github.com/onelogin/ruby-saml/pull/289) Remove uuid gem in favor of SecureRandom
* [#297](https://github.com/onelogin/ruby-saml/pull/297) Implement EncryptedKey RetrievalMethod support
* [#298](https://github.com/onelogin/ruby-saml/pull/298) IDP metadata parsing improved: binding parsing, fingerprint_algorithm support)
* [#299](https://github.com/onelogin/ruby-saml/pull/299) Make 'signing' at KeyDescriptor optional
* [#308](https://github.com/onelogin/ruby-saml/pull/308) Support name_id_format on SAMLResponse
* [#315](https://github.com/onelogin/ruby-saml/pull/315) Support for canonicalization with comments
* [#316](https://github.com/onelogin/ruby-saml/pull/316) Fix Misspelling of transation_id to transaction_id
* [#321](https://github.com/onelogin/ruby-saml/pull/321) Support Attribute Names on IDPSSODescriptor parser
* Changes on empty URI of Signature reference management
* [#320](https://github.com/onelogin/ruby-saml/pull/320) Dont mutate document to fix lack of reference URI
* [#306](https://github.com/onelogin/ruby-saml/pull/306) Support WantAssertionsSigned
### 1.1.2 (February 15, 2016)
* Improve signature validation. Add tests.
[#302](https://github.com/onelogin/ruby-saml/pull/302) Add Destination validation.
* [#292](https://github.com/onelogin/ruby-saml/pull/292) Improve the error message when validating the audience.
* [#287](https://github.com/onelogin/ruby-saml/pull/287) Keep the extracted certificate when parsing IdP metadata.
### 1.1.1 (November 10, 2015)
* [#275](https://github.com/onelogin/ruby-saml/pull/275) Fix a bug on signature validations that invalidates valid SAML messages.
### 1.1.0 (October 27, 2015)
* [#273](https://github.com/onelogin/ruby-saml/pull/273) Support SAMLResponse without ds:x509certificate
* [#270](https://github.com/onelogin/ruby-saml/pull/270) Allow SAML elements to come from any namespace (at decryption process)
* [#261](https://github.com/onelogin/ruby-saml/pull/261) Allow validate_subject_confirmation Response validation to be skipped
* [#258](https://github.com/onelogin/ruby-saml/pull/258) Fix allowed_clock_drift on the validate_session_expiration test
* [#256](https://github.com/onelogin/ruby-saml/pull/256) Separate the create_authentication_xml_doc in two methods.
* [#255](https://github.com/onelogin/ruby-saml/pull/255) Refactor validate signature.
* [#254](https://github.com/onelogin/ruby-saml/pull/254) Handle empty URI references
* [#251](https://github.com/onelogin/ruby-saml/pull/251) Support qualified and unqualified NameID in attributes
* [#234](https://github.com/onelogin/ruby-saml/pull/234) Add explicit support for JRuby
### 1.0.0 (June 30, 2015)
* [#247](https://github.com/onelogin/ruby-saml/pull/247) Avoid entity expansion (XEE attacks)
* [#246](https://github.com/onelogin/ruby-saml/pull/246) Fix bug generating Logout Response (issuer was at wrong order)
* [#243](https://github.com/onelogin/ruby-saml/issues/243) and [#244](https://github.com/onelogin/ruby-saml/issues/244) Fix metadata builder errors. Fix metadata xsd.
* [#241](https://github.com/onelogin/ruby-saml/pull/241) Add decrypt support (EncryptID and EncryptedAssertion). Improve compatibility with namespaces.
* [#240](https://github.com/onelogin/ruby-saml/pull/240) and [#238](https://github.com/onelogin/ruby-saml/pull/238) Improve test coverage and refactor.
* [#239](https://github.com/onelogin/ruby-saml/pull/239) Improve security: Add more validations to SAMLResponse, LogoutRequest and LogoutResponse. Refactor code and improve tests coverage.
* [#237](https://github.com/onelogin/ruby-saml/pull/237) Don't pretty print metadata by default.
* [#235](https://github.com/onelogin/ruby-saml/pull/235) Remove the soft parameter from validation methods. Now can be configured on the settings and each class read it and store as an attribute of the class. Adding some validations and refactor old ones.
* [#232](https://github.com/onelogin/ruby-saml/pull/232) Improve validations: Store the causes in the errors array, code refactor
* [#231](https://github.com/onelogin/ruby-saml/pull/231) Refactor HTTP-Redirect Sign method, Move test data to right folder
* [#226](https://github.com/onelogin/ruby-saml/pull/226) Ensure IdP certificate is formatted properly
* [#225](https://github.com/onelogin/ruby-saml/pull/225) Add documentation to several methods. Fix xpath injection on xml_security.rb
* [#223](https://github.com/onelogin/ruby-saml/pull/223) Allow logging to be delegated to an arbitrary Logger
* [#222](https://github.com/onelogin/ruby-saml/pull/222) No more silent failure fetching idp metadata (OneLogin::RubySaml::HttpError raised).
### 0.9.2 (Apr 28, 2015)
* [#216](https://github.com/onelogin/ruby-saml/pull/216) Add fingerprint algorithm support
* [#218](https://github.com/onelogin/ruby-saml/pull/218) Update README.md
* [#214](https://github.com/onelogin/ruby-saml/pull/214) Cleanup `SamlMessage` class
* [#213](https://github.com/onelogin/ruby-saml/pull/213) Add ability to sign metadata. (Improved)
* [#212](https://github.com/onelogin/ruby-saml/pull/212) Rename library entry point
* [#210](https://github.com/onelogin/ruby-saml/pull/210) Call assert in tests
* [#208](https://github.com/onelogin/ruby-saml/pull/208) Update tests and CI for Ruby 2.2.0
* [#205](https://github.com/onelogin/ruby-saml/pull/205) Allow requirement of single files
* [#204](https://github.com/onelogin/ruby-saml/pull/204) Require net/http library
* [#201](https://github.com/onelogin/ruby-saml/pull/201) Freeze and duplicate default security settings hash so that it doesn't get modified.
* [#200](https://github.com/onelogin/ruby-saml/pull/200) Set default SSL certificate store in Ruby 1.8.
* [#199](https://github.com/onelogin/ruby-saml/pull/199) Change Nokogiri's runtime dependency to fix support for Ruby 1.8.7.
* [#179](https://github.com/onelogin/ruby-saml/pull/179) Add support for setting the entity ID and name ID format when parsing metadata
* [#175](https://github.com/onelogin/ruby-saml/pull/175) Introduce thread safety to SAML schema validation
* [#171](https://github.com/onelogin/ruby-saml/pull/171) Fix inconsistent results with using regex matches in decode_raw_saml
### 0.9.1 (Feb 10, 2015)
* [#194](https://github.com/onelogin/ruby-saml/pull/194) Relax nokogiri gem requirements
* [#191](https://github.com/onelogin/ruby-saml/pull/191) Use Minitest instead of Test::Unit
### 0.9 (Jan 26, 2015)
* [#169](https://github.com/onelogin/ruby-saml/pull/169) WantAssertionSigned should be either true or false
* [#167](https://github.com/onelogin/ruby-saml/pull/167) (doc update) make unit of clock drift obvious
* [#160](https://github.com/onelogin/ruby-saml/pull/160) Extended solution for Attributes method [] can raise NoMethodError
* [#158](https://github.com/onelogin/ruby-saml/pull/1) Added ability to specify attribute services in metadata
* [#154](https://github.com/onelogin/ruby-saml/pull/154) Fix incorrect gem declaration statement
* [#152](https://github.com/onelogin/ruby-saml/pull/152) Fix the PR #99
* [#150](https://github.com/onelogin/ruby-saml/pull/150) Nokogiri already in gemspec
* [#147](https://github.com/onelogin/ruby-saml/pull/147) Fix LogoutResponse issuer validation and implement SAML Response issuer validation.
* [#144](https://github.com/onelogin/ruby-saml/pull/144) Fix DigestMethod lookup bug
* [#139](https://github.com/onelogin/ruby-saml/pull/139) Fixes handling of some soft and hard validation failures
* [#138](https://github.com/onelogin/ruby-saml/pull/138) Change logoutrequest.rb to UTC time
* [#136](https://github.com/onelogin/ruby-saml/pull/136) Remote idp metadata
* [#135](https://github.com/onelogin/ruby-saml/pull/135) Restored support for NIL as well as empty AttributeValues
* [#134](https://github.com/onelogin/ruby-saml/pull/134) explicitly require "onelogin/ruby-saml/logging"
* [#133](https://github.com/onelogin/ruby-saml/pull/133) Added license to gemspec
* [#132](https://github.com/onelogin/ruby-saml/pull/132) Support AttributeConsumingServiceIndex in AuthnRequest
* [#131](https://github.com/onelogin/ruby-saml/pull/131) Add ruby 2.1.1 to .travis.yml
* [#122](https://github.com/onelogin/ruby-saml/pull/122) Fixes #112 and #117 in a backwards compatible manner
* [#119](https://github.com/onelogin/ruby-saml/pull/119) Add support for extracting IdP details from metadata xml
### 0.8.2 (Jan 26, 2015)
* [#183](https://github.com/onelogin/ruby-saml/pull/183) Resolved a security vulnerability where string interpolation in a `REXML::XPath.first()` method call allowed for arbitrary code execution.
### 0.8.0 (Feb 21, 2014)
**IMPORTANT**: This release changed namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.
* [#111](https://github.com/onelogin/ruby-saml/pull/111) `Onelogin::` is `OneLogin::`
* [#108](https://github.com/onelogin/ruby-saml/pull/108) Change namespacing from `Onelogin::Saml` to `Onelogin::Rubysaml`
### 0.7.3 (Feb 20, 2014)
Updated gem dependencies to be compatible with Ruby 1.8.7-p374 and 1.9.3-p448. Removed unnecessary `canonix` gem dependency.
* [#107](https://github.com/onelogin/ruby-saml/pull/107) Relax nokogiri version requirement to >= 1.5.0
* [#105](https://github.com/onelogin/ruby-saml/pull/105) Lock Gem versions, fix to resolve possible namespace collision

6
ruby-saml-custom/Gemfile Normal file
View File

@ -0,0 +1,6 @@
#
# Please keep this file alphabetized and organized
#
source 'https://rubygems.org'
gemspec

23
ruby-saml-custom/LICENSE Normal file
View File

@ -0,0 +1,23 @@
Copyright (c) 2010-2016 OneLogin, Inc.
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

908
ruby-saml-custom/README.md Normal file
View File

@ -0,0 +1,908 @@
# Ruby SAML
[![Build Status](https://github.com/onelogin/ruby-saml/actions/workflows/test.yml/badge.svg?query=branch%3Amaster)](https://github.com/onelogin/ruby-saml/actions/workflows/test.yml?query=branch%3Amaster)
[![Coverage Status](https://coveralls.io/repos/onelogin/ruby-saml/badge.svg?branch=master)](https://coveralls.io/r/onelogin/ruby-saml?branch=master)
Ruby SAML minor and tiny versions may introduce breaking changes. Please read
[UPGRADING.md](UPGRADING.md) for guidance on upgrading to new Ruby SAML versions.
## Overview
The Ruby SAML library is for implementing the client side of a SAML authorization,
i.e. it provides a means for managing authorization initialization and confirmation
requests from identity providers.
SAML authorization is a two step process and you are expected to implement support for both.
We created a demo project for Rails 4 that uses the latest version of this library:
[ruby-saml-example](https://github.com/onelogin/ruby-saml-example)
### Supported Ruby Versions
The following Ruby versions are covered by CI testing:
* 2.1.x
* 2.2.x
* 2.3.x
* 2.4.x
* 2.5.x
* 2.6.x
* 2.7.x
* 3.0.x
* JRuby 9.1.x
* JRuby 9.2.x
* TruffleRuby (latest)
In addition, the following may work but are untested:
* 1.8.7
* 1.9.x
* 2.0.x
* JRuby 1.7.x
* JRuby 9.0.x
## Adding Features, Pull Requests
* Fork the repository
* Make your feature addition or bug fix
* Add tests for your new features. This is important so we don't break any features in a future version unintentionally.
* Ensure all tests pass by running `bundle exec rake test`.
* Do not change rakefile, version, or history.
* Open a pull request, following [this template](https://gist.github.com/Lordnibbler/11002759).
## Security Guidelines
If you believe you have discovered a security vulnerability in this gem, please report it
at https://www.onelogin.com/security with a description. We follow responsible disclosure
guidelines, and will work with you to quickly find a resolution.
### Security Warning
Some tools may incorrectly report ruby-saml is a potential security vulnerability.
ruby-saml depends on Nokogiri, and it's possible to use Nokogiri in a dangerous way
(by enabling its DTDLOAD option and disabling its NONET option).
This dangerous Nokogiri configuration, which is sometimes used by other components,
can create an XML External Entity (XXE) vulnerability if the XML data is not trusted.
However, ruby-saml never enables this dangerous Nokogiri configuration;
ruby-saml never enables DTDLOAD, and it never disables NONET.
The OneLogin::RubySaml::IdpMetadataParser class does not validate in any way the URL
that is introduced in order to be parsed.
Usually the same administrator that handles the Service Provider also sets the URL to
the IdP, which should be a trusted resource.
But there are other scenarios, like a SAAS app where the administrator of the app
delegates this functionality to other users. In this case, extra precaution should
be taken in order to validate such URL inputs and avoid attacks like SSRF.
## Getting Started
In order to use Ruby SAML you will need to install the gem (either manually or using Bundler),
and require the library in your Ruby application:
Using `Gemfile`
```ruby
# latest stable
gem 'ruby-saml', '~> 1.11.0'
# or track master for bleeding-edge
gem 'ruby-saml', :github => 'onelogin/ruby-saml'
```
Using RubyGems
```sh
gem install ruby-saml
```
You may require the entire Ruby SAML gem:
```ruby
require 'onelogin/ruby-saml'
```
or just the required components individually:
```ruby
require 'onelogin/ruby-saml/authrequest'
```
### Installation on Ruby 1.8.7
This gem uses Nokogiri as a dependency, which dropped support for Ruby 1.8.x in Nokogiri 1.6.
When installing this gem on Ruby 1.8.7, you will need to make sure a version of Nokogiri
prior to 1.6 is installed or specified if it hasn't been already.
Using `Gemfile`
```ruby
gem 'nokogiri', '~> 1.5.10'
```
Using RubyGems
```sh
gem install nokogiri --version '~> 1.5.10'
````
### Configuring Logging
When troubleshooting SAML integration issues, you will find it extremely helpful to examine the
output of this gem's business logic. By default, log messages are emitted to RAILS_DEFAULT_LOGGER
when the gem is used in a Rails context, and to STDOUT when the gem is used outside of Rails.
To override the default behavior and control the destination of log messages, provide
a ruby Logger object to the gem's logging singleton:
```ruby
OneLogin::RubySaml::Logging.logger = Logger.new('/var/log/ruby-saml.log')
```
## The Initialization Phase
This is the first request you will get from the identity provider. It will hit your application
at a specific URL that you've announced as your SAML initialization point. The response to
this initialization is a redirect back to the identity provider, which can look something
like this (ignore the saml_settings method call for now):
```ruby
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
```
If the SP knows who should be authenticated in the IdP, then can provide that info as follows:
```ruby
def init
request = OneLogin::RubySaml::Authrequest.new
saml_settings.name_identifier_value_requested = "testuser@example.com"
saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
redirect_to(request.create(saml_settings))
end
```
Once you've redirected back to the identity provider, it will ensure that the user has been
authorized and redirect back to your application for final consumption.
This can look something like this (the `authorize_success` and `authorize_failure`
methods are specific to your application):
```ruby
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :settings => saml_settings)
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
# authorize_success, log the user
session[:userid] = response.nameid
session[:attributes] = response.attributes
else
authorize_failure # This method shows an error message
# List of errors is available in response.errors array
end
end
```
In the above there are a few assumptions, one being that `response.nameid` is an email address.
This is all handled with how you specify the settings that are in play via the `saml_settings` method.
That could be implemented along the lines of this:
```
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
```
If the assertion of the SAMLResponse is not encrypted, you can initialize the Response
without the `:settings` parameter and set it later. If the SAMLResponse contains an encrypted
assertion, you need to provide the settings in the initialize method in order to obtain the
decrypted assertion, using the service provider private key in order to decrypt.
If you don't know what expect, always use the former (set the settings on initialize).
```ruby
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_entity_id = "https://app.onelogin.com/saml/metadata/#{OneLoginAppId}"
settings.idp_sso_service_url = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
settings.idp_sso_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings.idp_slo_service_url = "https://app.onelogin.com/trust/saml2/http-redirect/slo/#{OneLoginAppId}"
settings.idp_slo_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.idp_cert_fingerprint = OneLoginAppCertFingerPrint
settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
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"
# or as an array
settings.authn_context = [
"urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
"urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
]
# Optional bindings (defaults to Redirect for logout POST for ACS)
settings.single_logout_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
settings
end
```
The use of settings.issuer is deprecated in favour of settings.sp_entity_id since version 1.11.0
Some assertion validations can be skipped by passing parameters to `OneLogin::RubySaml::Response.new()`.
For example, you can skip the `AuthnStatement`, `Conditions`, `Recipient`, or the `SubjectConfirmation`
validations by initializing the response with different options:
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_authnstatement: true}) # skips AuthnStatement
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_conditions: true}) # skips conditions
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_subject_confirmation: true}) # skips subject confirmation
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_recipient_check: true}) # doesn't skip subject confirmation, but skips the recipient check which is a sub check of the subject_confirmation check
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], {skip_audience: true}) # skips audience check
```
All that's left is to wrap everything in a controller and reference it in the initialization and
consumption URLs in OneLogin. A full controller example could look like this:
```ruby
# This controller expects you to use the URLs /saml/init and /saml/consume in your OneLogin application.
class SamlController < ApplicationController
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
# We validate the SAML Response and check if the user already exists in the system
if response.is_valid?
# authorize_success, log the user
session[:userid] = response.nameid
session[:attributes] = response.attributes
else
authorize_failure # This method shows an error message
# List of errors is available in response.errors array
end
end
private
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
settings.sp_entity_id = "http://#{request.host}/saml/metadata"
settings.idp_sso_service_url = "https://app.onelogin.com/saml/signon/#{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
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
# Optional. Describe according to IdP specification (if supported) which attributes the SP desires to receive in SAMLResponse.
settings.attributes_index = 5
# Optional. Describe an attribute consuming service for support of additional attributes.
settings.attribute_consuming_service.configure do
service_name "Service"
service_index 5
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
end
settings
end
end
```
## Signature Validation
Ruby SAML allows different ways to validate the signature of the SAMLResponse:
- You can provide the IdP X.509 public certificate at the `idp_cert` setting.
- You can provide the IdP X.509 public certificate in fingerprint format using the
`idp_cert_fingerprint` setting parameter and additionally the `idp_cert_fingerprint_algorithm` parameter.
When validating the signature of redirect binding, the fingerprint is useless and the certificate
of the IdP is required in order to execute the validation. You can pass the option
`:relax_signature_validation` to `SloLogoutrequest` and `Logoutresponse` if want to avoid signature
validation if no certificate of the IdP is provided.
In production also we highly recommend to register on the settings the IdP certificate instead
of using the fingerprint method. The fingerprint, is a hash, so at the end is open to a collision
attack that can end on a signature validation bypass. Other SAML toolkits deprecated that mechanism,
we maintain it for compatibility and also to be used on test environment.
## Handling Multiple IdP Certificates
If the IdP metadata XML includes multiple certificates, you may specify the `idp_cert_multi`
parameter. When used, the `idp_cert` and `idp_cert_fingerprint` parameters are ignored.
This is useful in the following scenarios:
* The IdP uses different certificates for signing versus encryption.
* The IdP is undergoing a key rollover and is publishing the old and new certificates in parallel.
The `idp_cert_multi` must be a `Hash` as follows. The `:signing` and `:encryption` arrays below,
add the IdP X.509 public certificates which were published in the IdP metadata.
```ruby
{
:signing => [],
:encryption => []
}
```
## Metadata Based Configuration
The method above requires a little extra work to manually specify attributes about both the IdP and your SP application.
There's an easier method: use a metadata exchange. Metadata is 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 `IdpMetadataParser#parse_remote`, the IdP metadata will be added to the settings.
```ruby
def saml_settings
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
# Returns OneLogin::RubySaml::Settings pre-populated 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.sp_entity_id = "http://#{request.host}/saml/metadata"
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:
* idp_entity_id
* name_identifier_format
* idp_sso_service_url
* idp_slo_service_url
* idp_attribute_names
* idp_cert
* idp_cert_fingerprint
* idp_cert_multi
### Retrieve one Entity Descriptor when many exist in Metadata
If the Metadata contains several entities, the relevant Entity
Descriptor can be specified when retrieving the settings from the
IdpMetadataParser by its Entity Id value:
```ruby
validate_cert = true
settings = idp_metadata_parser.parse_remote(
"https://example.com/auth/saml2/idp/metadata",
validate_cert,
entity_id: "http//example.com/target/entity"
)
```
### Parsing Metadata into an Hash
The `OneLogin::RubySaml::IdpMetadataParser` also provides the methods `#parse_to_hash` and `#parse_remote_to_hash`.
Those return an Hash instead of a `Settings` object, which may be useful for configuring
[omniauth-saml](https://github.com/omniauth/omniauth-saml), for instance.
## Retrieving Attributes
If you are using `saml:AttributeStatement` to transfer data like the username, you can access all the attributes through `response.attributes`. It contains all the `saml:AttributeStatement`s with its 'Name' as an indifferent key and one or more `saml:AttributeValue`s as values. The value returned depends on the value of the
`single_value_compatibility` (when activated, only the first value is returned)
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
response.settings = saml_settings
response.attributes[:username]
```
Imagine this `saml:AttributeStatement`
```xml
<saml:AttributeStatement>
<saml:Attribute Name="uid">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">demo</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="another_value">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value1</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">value2</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="role">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role1</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="role">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role2</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="xs:string">role3</saml:AttributeValue>
</saml:Attribute>
<saml:Attribute Name="attribute_with_nil_value">
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
</saml:Attribute>
<saml:Attribute Name="attribute_with_nils_and_empty_strings">
<saml:AttributeValue/>
<saml:AttributeValue>valuePresent</saml:AttributeValue>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="true"/>
<saml:AttributeValue xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:nil="1"/>
</saml:Attribute>
<saml:Attribute Name="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname">
<saml:AttributeValue>usersName</saml:AttributeValue>
</saml:Attribute>
</saml:AttributeStatement>
```
```ruby
pp(response.attributes) # is an OneLogin::RubySaml::Attributes object
# => @attributes=
{"uid"=>["demo"],
"another_value"=>["value1", "value2"],
"role"=>["role1", "role2", "role3"],
"attribute_with_nil_value"=>[nil],
"attribute_with_nils_and_empty_strings"=>["", "valuePresent", nil, nil]
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"=>["usersName"]}>
# Active single_value_compatibility
OneLogin::RubySaml::Attributes.single_value_compatibility = true
pp(response.attributes[:uid])
# => "demo"
pp(response.attributes[:role])
# => "role1"
pp(response.attributes.single(:role))
# => "role1"
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => "role1"
pp(response.attributes[:attribute_with_nil_value])
# => nil
pp(response.attributes[:attribute_with_nils_and_empty_strings])
# => ""
pp(response.attributes[:not_exists])
# => nil
pp(response.attributes.single(:not_exists))
# => nil
pp(response.attributes.multi(:not_exists))
# => nil
pp(response.attributes.fetch(/givenname/))
# => "usersName"
# Deprecated single_value_compatibility
OneLogin::RubySaml::Attributes.single_value_compatibility = false
pp(response.attributes[:uid])
# => ["demo"]
pp(response.attributes[:role])
# => ["role1", "role2", "role3"]
pp(response.attributes.single(:role))
# => "role1"
pp(response.attributes.multi(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes.fetch(:role))
# => ["role1", "role2", "role3"]
pp(response.attributes[:attribute_with_nil_value])
# => [nil]
pp(response.attributes[:attribute_with_nils_and_empty_strings])
# => ["", "valuePresent", nil, nil]
pp(response.attributes[:not_exists])
# => nil
pp(response.attributes.single(:not_exists))
# => nil
pp(response.attributes.multi(:not_exists))
# => nil
pp(response.attributes.fetch(/givenname/))
# => ["usersName"]
```
The `saml:AuthnContextClassRef` of the AuthNRequest can be provided by `settings.authn_context`; possible values are described at [SAMLAuthnCxt]. The comparison method can be set using `settings.authn_context_comparison` parameter. Possible values include: 'exact', 'better', 'maximum' and 'minimum' (default value is 'exact').
To add a `saml:AuthnContextDeclRef`, define `settings.authn_context_decl_ref`.
In a SP-initiated flow, the SP can indicate to the IdP the subject that should be authenticated. This is done by defining the `settings.name_identifier_value_requested` before
building the authrequest object.
## Service Provider Metadata
To form a trusted pair relationship with the IdP, the SP (you) need to provide metadata XML
to the IdP for various good reasons. (Caching, certificate lookups, relaying party permissions, etc)
The class `OneLogin::RubySaml::Metadata` takes care of this by reading the Settings and returning XML. All you have to do is add a controller to return the data, then give this URL to the IdP administrator.
The metadata will be polled by the IdP every few minutes, so updating your settings should propagate
to the IdP settings.
```ruby
class SamlController < ApplicationController
# ... the rest of your controller definitions ...
def metadata
settings = Account.get_saml_settings
meta = OneLogin::RubySaml::Metadata.new
render :xml => meta.generate(settings), :content_type => "application/samlmetadata+xml"
end
end
```
You can add `ValidUntil` and `CacheDuration` to the SP Metadata XML using instead:
```ruby
# Valid until => 2 days from now
# Cache duration = 604800s = 1 week
valid_until = Time.now + 172800
cache_duration = 604800
meta.generate(settings, false, valid_until, cache_duration)
```
## Signing and Decryption
Ruby SAML supports the following functionality:
1. Signing your SP Metadata XML
2. Signing your SP SAML messages
3. Decrypting IdP Assertion messages upon receipt (EncryptedAssertion)
4. Verifying signatures on SAML messages and IdP Assertions
In order to use functions 1-3 above, you must first define your SP public certificate and private key:
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
```
Note that the same certificate (and its associated private key) are used to perform
all decryption and signing-related functions (1-4) above. Ruby SAML does not currently allow
to specify different certificates for each function.
You may also globally set the SP signature and digest method, to be used in SP signing (functions 1 and 2 above):
```ruby
settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
```
#### Signing SP Metadata
You may add a `<ds:Signature>` digital signature element to your SP Metadata XML using the following setting:
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.security[:metadata_signed] = true # Enable signature on Metadata
```
#### Signing SP SAML Messages
Ruby SAML supports SAML request signing. The Service Provider will sign the
request/responses with its private key. The Identity Provider will then validate the signature
of the received request/responses with the public X.509 cert of the Service Provider.
To enable, please first set your certificate and private key. This will add `<md:KeyDescriptor use="signing">`
to your SP Metadata XML, to be read by the IdP.
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
```
Next, you may specify the specific SP SAML messages you would like to sign:
```ruby
settings.security[:authn_requests_signed] = true # Enable signature on AuthNRequest
settings.security[:logout_requests_signed] = true # Enable signature on Logout Request
settings.security[:logout_responses_signed] = true # Enable signature on Logout Response
```
Signatures will be handled automatically for both `HTTP-Redirect` and `HTTP-Redirect` Binding.
Note that the RelayState parameter is used when creating the Signature on the `HTTP-Redirect` Binding.
Remember to provide it to the Signature builder if you are sending a `GET RelayState` parameter or the
signature validation process will fail at the Identity Provider.
#### Decrypting IdP SAML Assertions
Ruby SAML supports EncryptedAssertion. The Identity Provider will encrypt the Assertion with the
public cert of the Service Provider. The Service Provider will decrypt the EncryptedAssertion with its private key.
You may enable EncryptedAssertion as follows. This will add `<md:KeyDescriptor use="encrytion">` to your
SP Metadata XML, to be read by the IdP.
```ruby
settings.certificate = "CERTIFICATE TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.private_key = "PRIVATE KEY TEXT WITH BEGIN/END HEADER AND FOOTER"
settings.security[:want_assertions_encrypted] = true # Invalidate SAML messages without an EncryptedAssertion
```
#### Verifying Signature on IdP Assertions
You may require the IdP to sign its SAML Assertions using the following setting.
With will add `<md:SPSSODescriptor WantAssertionsSigned="true">` to your SP Metadata XML.
The signature will be checked against the `<md:KeyDescriptor use="signing">` element
present in the IdP's metadata.
```ruby
settings.security[:want_assertions_signed] = true # Require the IdP to sign its SAML Assertions
```
#### Certificate and Signature Validation
You may require SP and IdP certificates to be non-expired using the following settings:
```ruby
settings.security[:check_idp_cert_expiration] = true # Raise error if IdP X.509 cert is expired
settings.security[:check_sp_cert_expiration] = true # Raise error SP X.509 cert is expired
```
By default, Ruby SAML will raise a `OneLogin::RubySaml::ValidationError` if a signature or certificate
validation fails. You may disable such exceptions using the `settings.security[:soft]` parameter.
```ruby
settings.security[:soft] = true # Do not raise error on failed signature/certificate validations
```
#### Audience Validation
A service provider should only consider a SAML response valid if the IdP includes an <AudienceRestriction>
element containting an <Audience> element that uniquely identifies the service provider. Unless you specify
the `skip_audience` option, Ruby SAML will validate that each SAML response includes an <Audience> element
whose contents matches `settings.sp_entity_id`.
By default, Ruby SAML considers an <AudienceRestriction> element containing only empty <Audience> elements
to be valid. That means an otherwise valid SAML response with a condition like this would be valid:
```xml
<AudienceRestriction>
<Audience />
</AudienceRestriction>
```
You may enforce that an <AudienceRestriction> element containing only empty <Audience> elements
is invalid using the `settings.security[:strict_audience_validation]` parameter.
```ruby
settings.security[:strict_audience_validation] = true
```
#### Key Rollover
To update the SP X.509 certificate and private key without disruption of service, you may define the parameter
`settings.certificate_new`. This will publish the new SP certificate in your metadata so that your IdP counterparties
may cache it in preparation for rollover.
For example, if you to rollover from `CERT A` to `CERT B`. Before rollover, your settings should look as follows.
Both `CERT A` and `CERT B` will now appear in your SP metadata, however `CERT A` will still be used for signing
and encryption at this time.
```ruby
settings.certificate = "CERT A"
settings.private_key = "PRIVATE KEY FOR CERT A"
settings.certificate_new = "CERT B"
```
After the IdP has cached `CERT B`, you may then change your settings as follows:
```ruby
settings.certificate = "CERT B"
settings.private_key = "PRIVATE KEY FOR CERT B"
```
## Single Log Out
Ruby SAML supports SP-initiated Single Logout and IdP-Initiated Single Logout.
Here is an example that we could add to our previous controller to generate and send a SAML Logout Request to the IdP:
```ruby
# Create a SP initiated SLO
def sp_logout_request
# LogoutRequest accepts plain browser requests w/o paramters
settings = saml_settings
if settings.idp_slo_service_url.nil?
logger.info "SLO IdP Endpoint not found in settings, executing then a normal logout'"
delete_session
else
logout_request = OneLogin::RubySaml::Logoutrequest.new
logger.info "New SP SLO for userid '#{session[:userid]}' transactionid '#{logout_request.uuid}'"
if settings.name_identifier_value.nil?
settings.name_identifier_value = session[:userid]
end
# Ensure user is logged out before redirect to IdP, in case anything goes wrong during single logout process (as recommended by saml2int [SDP-SP34])
logged_user = session[:userid]
logger.info "Delete session for '#{session[:userid]}'"
delete_session
# Save the transaction_id to compare it with the response we get back
session[:transaction_id] = logout_request.uuid
session[:logged_out_user] = logged_user
relayState = url_for(controller: 'saml', action: 'index')
redirect_to(logout_request.create(settings, :RelayState => relayState))
end
end
```
This method processes the SAML Logout Response sent by the IdP as the reply of the SAML Logout Request:
```ruby
# After sending an SP initiated LogoutRequest to the IdP, we need to accept
# the LogoutResponse, verify it, then actually delete our session.
def process_logout_response
settings = Account.get_saml_settings
if session.has_key? :transaction_id
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings, :matches_request_id => session[:transaction_id])
else
logout_response = OneLogin::RubySaml::Logoutresponse.new(params[:SAMLResponse], settings)
end
logger.info "LogoutResponse is: #{logout_response.to_s}"
# Validate the SAML Logout Response
if not logout_response.validate
logger.error "The SAML Logout Response is invalid"
else
# Actually log out this session
logger.info "SLO completed for '#{session[:logged_out_user]}'"
delete_session
end
end
# Delete a user's session.
def delete_session
session[:userid] = nil
session[:attributes] = nil
session[:transaction_id] = nil
session[:logged_out_user] = nil
end
```
Here is an example that we could add to our previous controller to process a SAML Logout Request from the IdP and reply with a SAML Logout Response to the IdP:
```ruby
# Method to handle IdP initiated logouts
def idp_logout_request
settings = Account.get_saml_settings
# ADFS URL-Encodes SAML data as lowercase, and the toolkit by default uses
# uppercase. Turn it True for ADFS compatibility on signature verification
settings.security[:lowercase_url_encoding] = true
logout_request = OneLogin::RubySaml::SloLogoutrequest.new(
params[:SAMLRequest], settings: settings
)
if !logout_request.is_valid?
logger.error "IdP initiated LogoutRequest was not valid!"
return render :inline => logger.error
end
logger.info "IdP initiated Logout for #{logout_request.name_id}"
# Actually log out this session
delete_session
# Generate a response to the IdP.
logout_request_id = logout_request.id
logout_response = OneLogin::RubySaml::SloLogoutresponse.new.create(settings, logout_request_id, nil, :RelayState => params[:RelayState])
redirect_to logout_response
end
```
All the mentioned methods could be handled in a unique view:
```ruby
# Trigger SP and IdP initiated Logout requests
def logout
# If we're given a logout request, handle it in the IdP logout initiated method
if params[:SAMLRequest]
return idp_logout_request
# We've been given a response back from the IdP, process it
elsif params[:SAMLResponse]
return process_logout_response
# Initiate SLO (send Logout Request)
else
return sp_logout_request
end
end
```
## Clock Drift
Server clocks tend to drift naturally. If during validation of the response you get the error "Current time is earlier than NotBefore condition", this may be due to clock differences between your system and that of the Identity Provider.
First, ensure that both systems synchronize their clocks, using for example the industry standard [Network Time Protocol (NTP)](http://en.wikipedia.org/wiki/Network_Time_Protocol).
Even then you may experience intermittent issues, as the clock of the Identity Provider may drift slightly ahead of your system clocks. To allow for a small amount of clock drift, you can initialize the response by passing in an option named `:allowed_clock_drift`. Its value must be given in a number (and/or fraction) of seconds. The value given is added to the current time at which the response is validated before it's tested against the `NotBefore` assertion. For example:
```ruby
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], :allowed_clock_drift => 1.second)
```
Make sure to keep the value as comfortably small as possible to keep security risks to a minimum.
## Deflation Limit
To protect against decompression bombs (a form of DoS attack), SAML messages are limited to 250,000 bytes by default.
Sometimes legitimate SAML messages will exceed this limit,
for example due to custom claims like including groups a user is a member of.
If you want to customize this limit, you need to provide a different setting when initializing the response object.
Example:
```ruby
def consume
response = OneLogin::RubySaml::Response.new(params[:SAMLResponse], { settings: saml_settings })
...
end
private
def saml_settings
OneLogin::RubySaml::Settings.new(message_max_bytesize: 500_000)
end
```
## Attribute Service
To request attributes from the IdP the SP needs to provide an attribute service within it's metadata and reference the index in the assertion.
```ruby
settings = OneLogin::RubySaml::Settings.new
settings.attributes_index = 5
settings.attribute_consuming_service.configure do
service_name "Service"
service_index 5
add_attribute :name => "Name", :name_format => "Name Format", :friendly_name => "Friendly Name"
add_attribute :name => "Another Attribute", :name_format => "Name Format", :friendly_name => "Friendly Name", :attribute_value => "Attribute Value"
end
```
The `attribute_value` option additionally accepts an array of possible values.
## Custom Metadata Fields
Some IdPs may require to add SPs to add additional fields (Organization, ContactPerson, etc.)
into the SP metadata. This can be achieved by extending the `OneLogin::RubySaml::Metadata`
class and overriding the `#add_extras` method as per the following example:
```ruby
class MyMetadata < OneLogin::RubySaml::Metadata
def add_extras(root, _settings)
org = root.add_element("md:Organization")
org.add_element("md:OrganizationName", 'xml:lang' => "en-US").text = 'ACME Inc.'
org.add_element("md:OrganizationDisplayName", 'xml:lang' => "en-US").text = 'ACME'
org.add_element("md:OrganizationURL", 'xml:lang' => "en-US").text = 'https://www.acme.com'
cp = root.add_element("md:ContactPerson", 'contactType' => 'technical')
cp.add_element("md:GivenName").text = 'ACME SAML Team'
cp.add_element("md:EmailAddress").text = 'saml@acme.com'
end
end
# Output XML with custom metadata
MyMetadata.new.generate(settings)
```

27
ruby-saml-custom/Rakefile Normal file
View File

@ -0,0 +1,27 @@
require 'rubygems'
require 'rake'
#not being used yet.
require 'rake/testtask'
Rake::TestTask.new(:test) do |test|
test.libs << 'lib' << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
begin
require 'rcov/rcovtask'
Rcov::RcovTask.new do |test|
test.libs << 'test'
test.pattern = 'test/**/*_test.rb'
test.verbose = true
end
rescue LoadError
task :rcov do
abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
end
end
task :test
task :default => :test

View File

@ -0,0 +1,149 @@
# Ruby SAML Migration Guide
## Updating from 1.12.x to 1.13.0
Version `1.13.0` adds `settings.idp_sso_service_binding` and `settings.idp_slo_service_binding`, and
deprecates `settings.security[:embed_sign]`. If specified, new binding parameters will be used in place of `:embed_sign`
to determine how to handle SAML message signing (`HTTP-POST` embeds signature and `HTTP-Redirect` does not.)
In addition, the `IdpMetadataParser#parse`, `#parse_to_hash` and `#parse_to_array` methods now retrieve
`idp_sso_service_binding` and `idp_slo_service_binding`.
Lastly, for convenience you may now use the Symbol aliases `:post` and `:redirect` for any `settings.*_binding` parameter.
## Upgrading from 1.11.x to 1.12.0
Version `1.12.0` adds support for gcm algorithm and
change/adds specific error messages for signature validations
`idp_sso_target_url` and `idp_slo_target_url` attributes of the Settings class deprecated
in favor of `idp_sso_service_url` and `idp_slo_service_url`. The `IdpMetadataParser#parse`,
`#parse_to_hash` and `#parse_to_array` methods now retrieve SSO URL and SLO URL endpoints with
`idp_sso_service_url` and `idp_slo_service_url` (previously `idp_sso_target_url` and
`idp_slo_target_url` respectively).
## Upgrading from 1.10.x to 1.11.0
Version `1.11.0` deprecates the use of `settings.issuer` in favour of `settings.sp_entity_id`.
There are two new security settings: `settings.security[:check_idp_cert_expiration]` and
`settings.security[:check_sp_cert_expiration]` (both false by default) that check if the
IdP or SP X.509 certificate has expired, respectively.
Version `1.10.2` includes the `valid_until` attribute in parsed IdP metadata.
Version `1.10.1` improves Ruby 1.8.7 support.
## Upgrading from 1.9.0 to 1.10.0
Version `1.10.0` improves IdpMetadataParser to allow parse multiple IDPSSODescriptor,
Add Subject support on AuthNRequest to allow SPs provide info to the IdP about the user
to be authenticated and updates the format_cert method to accept certs with /\x0d/
## Upgrading from 1.8.0 to 1.9.0
Version `1.9.0` better supports Ruby 2.4+ and JRuby 9.2.0.0. `Settings` initialization
now has a second parameter, `keep_security_settings` (default: false), which saves security
settings attributes that are not explicitly overridden, if set to true.
## Upgrading from 1.7.x to 1.8.0
On Version `1.8.0`, creating AuthRequests/LogoutRequests/LogoutResponses with nil RelayState
param will not generate a URL with an empty RelayState parameter anymore. It also changes
the invalid audience error message.
## Upgrading from 1.6.0 to 1.7.0
Version `1.7.0` is a recommended update for all Ruby SAML users as it includes a fix for
the [CVE-2017-11428](https://www.cvedetails.com/cve/CVE-2017-11428/) vulnerability.
## Upgrading from 1.5.0 to 1.6.0
Version `1.6.0` changes the preferred way to construct instances of `Logoutresponse` and
`SloLogoutrequest`. Previously the _SAMLResponse_, _RelayState_, and _SigAlg_ parameters
of these message types were provided via the constructor's `options[:get_params]` parameter.
Unfortunately this can result in incompatibility with other SAML implementations; signatures
are specified to be computed based on the _sender's_ URI-encoding of the message, which can
differ from that of Ruby SAML. In particular, Ruby SAML's URI-encoding does not match that
of Microsoft ADFS, so messages from ADFS can fail signature validation.
The new preferred way to provide _SAMLResponse_, _RelayState_, and _SigAlg_ is via the
`options[:raw_get_params]` parameter. For example:
```ruby
# In this example `query_params` is assumed to contain decoded query parameters,
# and `raw_query_params` is assumed to contain encoded query parameters as sent by the IDP.
settings = {
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.soft = false
}
options = {
get_params: {
"Signature" => query_params["Signature"],
},
raw_get_params: {
"SAMLRequest" => raw_query_params["SAMLRequest"],
"SigAlg" => raw_query_params["SigAlg"],
"RelayState" => raw_query_params["RelayState"],
},
}
slo_logout_request = OneLogin::RubySaml::SloLogoutrequest.new(query_params["SAMLRequest"], settings, options)
raise "Invalid Logout Request" unless slo_logout_request.is_valid?
```
The old form is still supported for backward compatibility, but all Ruby SAML users
should prefer `options[:raw_get_params]` where possible to ensure compatibility with
other SAML implementations.
## Upgrading from 1.4.2 to 1.4.3
Version `1.4.3` introduces Recipient validation of SubjectConfirmation elements.
The 'Recipient' value is compared with the settings.assertion_consumer_service_url
value.
If you want to skip that validation, add the :skip_recipient_check option to the
initialize method of the Response object.
Parsing metadata that contains more than one certificate will propagate the
idp_cert_multi property rather than idp_cert. See [signature validation
section](#signature-validation) for details.
## Upgrading from 1.3.x to 1.4.x
Version `1.4.0` is a recommended update for all Ruby SAML users as it includes security improvements.
## Upgrading from 1.2.x to 1.3.x
Version `1.3.0` is a recommended update for all Ruby SAML users as it includes security fixes.
It adds security improvements in order to prevent Signature wrapping attacks.
[CVE-2016-5697](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2016-5697)
## Upgrading from 1.1.x to 1.2.x
Version `1.2` adds IDP metadata parsing improvements, uuid deprecation in favour of SecureRandom,
refactor error handling and some minor improvements.
There is no compatibility issue detected.
For more details, please review [CHANGELOG.md](CHANGELOG.md).
## Upgrading from 1.0.x to 1.1.x
Version `1.1` adds some improvements on signature validation and solves some namespace conflicts.
## Upgrading from 0.9.x to 1.0.x
Version `1.0` is a recommended update for all Ruby SAML users as it includes security fixes.
Version `1.0` adds security improvements like entity expansion limitation, more SAML message validations, and other important improvements like decrypt support.
### Important Changes
Please note the `get_idp_metadata` method raises an exception when it is not able to fetch the idp metadata, so review your integration if you are using this functionality.
## Upgrading from 0.8.x to 0.9.x
Version `0.9` adds many new features and improvements.
## Upgrading from 0.7.x to 0.8.x
Version `0.8.x` changes the namespace of the gem from `OneLogin::Saml` to `OneLogin::RubySaml`. Please update your implementations of the gem accordingly.

View File

@ -0,0 +1,5 @@
source 'https://rubygems.org'
gem "nokogiri", "~> 1.5.10"
gemspec :path => "../"

View File

@ -0,0 +1,17 @@
require 'onelogin/ruby-saml/logging'
require 'onelogin/ruby-saml/saml_message'
require 'onelogin/ruby-saml/authrequest'
require 'onelogin/ruby-saml/logoutrequest'
require 'onelogin/ruby-saml/logoutresponse'
require 'onelogin/ruby-saml/attributes'
require 'onelogin/ruby-saml/slo_logoutrequest'
require 'onelogin/ruby-saml/slo_logoutresponse'
require 'onelogin/ruby-saml/response'
require 'onelogin/ruby-saml/settings'
require 'onelogin/ruby-saml/attribute_service'
require 'onelogin/ruby-saml/http_error'
require 'onelogin/ruby-saml/validation_error'
require 'onelogin/ruby-saml/metadata'
require 'onelogin/ruby-saml/idp_metadata_parser'
require 'onelogin/ruby-saml/utils'
require 'onelogin/ruby-saml/version'

View File

@ -0,0 +1,57 @@
module OneLogin
module RubySaml
# SAML2 AttributeService. Auxiliary class to build the AttributeService of the SP Metadata
#
class AttributeService
attr_reader :attributes
attr_reader :name
attr_reader :index
# Initializes the AttributeService, set the index value as 1 and an empty array as attributes
#
def initialize
@index = "1"
@attributes = []
end
def configure(&block)
instance_eval(&block)
end
# @return [Boolean] True if the AttributeService object has been initialized and set with the required values
# (has attributes and a name)
def configured?
@attributes.length > 0 && !@name.nil?
end
# Set a name to the service
# @param name [String] The service name
#
def service_name(name)
@name = name
end
# Set an index to the service
# @param index [Integer] An index
#
def service_index(index)
@index = index
end
# Add an AttributeService
# @param options [Hash] AttributeService option values
# add_attribute(
# :name => "Name",
# :name_format => "Name Format",
# :index => 1,
# :friendly_name => "Friendly Name",
# :attribute_value => "Attribute Value"
# )
#
def add_attribute(options={})
attributes << options
end
end
end
end

View File

@ -0,0 +1,151 @@
module OneLogin
module RubySaml
# SAML2 Attributes. Parse the Attributes from the AttributeStatement of the SAML Response.
#
class Attributes
include Enumerable
attr_reader :attributes
# By default Attributes#[] is backwards compatible and
# returns only the first value for the attribute
# Setting this to `false` returns all values for an attribute
@@single_value_compatibility = true
# @return [Boolean] Get current status of backwards compatibility mode.
#
def self.single_value_compatibility
@@single_value_compatibility
end
# Sets the backwards compatibility mode on/off.
# @param value [Boolean]
#
def self.single_value_compatibility=(value)
@@single_value_compatibility = value
end
# @param attrs [Hash] The +attrs+ must be a Hash with attribute names as keys and **arrays** as values:
# Attributes.new({
# 'name' => ['value1', 'value2'],
# 'mail' => ['value1'],
# })
#
def initialize(attrs = {})
@attributes = attrs
end
# Iterate over all attributes
#
def each
attributes.each{|name, values| yield name, values}
end
# Test attribute presence by name
# @param name [String] The attribute name to be checked
#
def include?(name)
attributes.has_key?(canonize_name(name))
end
# Return first value for an attribute
# @param name [String] The attribute name
# @return [String] The value (First occurrence)
#
def single(name)
attributes[canonize_name(name)].first if include?(name)
end
# Return all values for an attribute
# @param name [String] The attribute name
# @return [Array] Values of the attribute
#
def multi(name)
attributes[canonize_name(name)]
end
# Retrieve attribute value(s)
# @param name [String] The attribute name
# @return [String|Array] Depending on the single value compatibility status this returns:
# - First value if single_value_compatibility = true
# response.attributes['mail'] # => 'user@example.com'
# - All values if single_value_compatibility = false
# response.attributes['mail'] # => ['user@example.com','user@example.net']
#
def [](name)
self.class.single_value_compatibility ? single(canonize_name(name)) : multi(canonize_name(name))
end
# @return [Hash] Return all attributes as a hash
#
def all
attributes
end
# @param name [String] The attribute name
# @param values [Array] The values
#
def set(name, values)
attributes[canonize_name(name)] = values
end
alias_method :[]=, :set
# @param name [String] The attribute name
# @param values [Array] The values
#
def add(name, values = [])
attributes[canonize_name(name)] ||= []
attributes[canonize_name(name)] += Array(values)
end
# Make comparable to another Attributes collection based on attributes
# @param other [Attributes] An Attributes object to compare with
# @return [Boolean] True if are contains the same attributes and values
#
def ==(other)
if other.is_a?(Attributes)
all == other.all
else
super
end
end
# Fetch attribute value using name or regex
# @param name [String|Regexp] The attribute name
# @return [String|Array] Depending on the single value compatibility status this returns:
# - First value if single_value_compatibility = true
# response.attributes['mail'] # => 'user@example.com'
# - All values if single_value_compatibility = false
# response.attributes['mail'] # => ['user@example.com','user@example.net']
#
def fetch(name)
attributes.each_key do |attribute_key|
if name.is_a?(Regexp)
if name.respond_to? :match?
return self[attribute_key] if name.match?(attribute_key)
else
return self[attribute_key] if name.match(attribute_key)
end
elsif canonize_name(name) == canonize_name(attribute_key)
return self[attribute_key]
end
end
nil
end
protected
# stringifies all names so both 'email' and :email return the same result
# @param name [String] The attribute name
# @return [String] stringified name
#
def canonize_name(name)
name.to_s
end
end
end
end

View File

@ -0,0 +1,193 @@
require "rexml/document"
require "onelogin/ruby-saml/logging"
require "onelogin/ruby-saml/saml_message"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/setting_error"
# Only supports SAML 2.0
module OneLogin
module RubySaml
include REXML
# SAML2 Authentication. AuthNRequest (SSO SP initiated, Builder)
#
class Authrequest < SamlMessage
# AuthNRequest ID
attr_accessor :uuid
# Initializes the AuthNRequest. An Authrequest Object that is an extension of the SamlMessage class.
# Asigns an ID, a random uuid.
#
def initialize
@uuid = OneLogin::RubySaml::Utils.uuid
end
def request_id
@uuid
end
# Creates the AuthNRequest string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [String] AuthNRequest string that includes the SAMLRequest
#
def create(settings, params = {})
params = create_params(settings, params)
params_prefix = (settings.idp_sso_service_url =~ /\?/) ? '&' : '?'
saml_request = CGI.escape(params.delete("SAMLRequest"))
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
params.each_pair do |key, value|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
end
raise SettingError.new "Invalid settings, idp_sso_service_url is not set!" if settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
@login_url = settings.idp_sso_service_url + request_params
end
# Creates the Get parameters for the request.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [Hash] Parameters
#
def create_params(settings, params={})
# The method expects :RelayState but sometimes we get 'RelayState' instead.
# Based on the HashWithIndifferentAccess value in Rails we could experience
# conflicts so this line will solve them.
relay_state = params[:RelayState] || params['RelayState']
if relay_state.nil?
params.delete(:RelayState)
params.delete('RelayState')
end
request_doc = create_authentication_xml_doc(settings)
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
request = ""
request_doc.write(request)
Logging.debug "Created AuthnRequest: #{request}"
request = deflate(request) if settings.compress_request
base64_request = encode(request)
request_params = {"SAMLRequest" => base64_request}
if settings.idp_sso_service_binding == Utils::BINDINGS[:redirect] && settings.security[:authn_requests_signed] && settings.private_key
params['SigAlg'] = settings.security[:signature_method]
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
params.each_pair do |key, value|
request_params[key] = value.to_s
end
request_params
end
# Creates the SAMLRequest String.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @return [String] The SAMLRequest String.
#
def create_authentication_xml_doc(settings)
document = create_xml_document(settings)
sign_document(document, settings)
end
def create_xml_document(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
request_doc = XMLSecurity::Document.new
request_doc.uuid = uuid
root = request_doc.add_element "samlp:AuthnRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
root.attributes['ID'] = uuid
root.attributes['IssueInstant'] = time
root.attributes['Version'] = "2.0"
root.attributes['Destination'] = settings.idp_sso_service_url unless settings.idp_sso_service_url.nil? or settings.idp_sso_service_url.empty?
root.attributes['IsPassive'] = settings.passive unless settings.passive.nil?
root.attributes['ProtocolBinding'] = settings.protocol_binding unless settings.protocol_binding.nil?
root.attributes["AttributeConsumingServiceIndex"] = settings.attributes_index unless settings.attributes_index.nil?
root.attributes['ForceAuthn'] = settings.force_authn unless settings.force_authn.nil?
# Conditionally defined elements based on settings
if settings.assertion_consumer_service_url != nil
root.attributes["AssertionConsumerServiceURL"] = settings.assertion_consumer_service_url
end
if settings.sp_entity_id != nil
issuer = root.add_element "saml:Issuer"
issuer.text = settings.sp_entity_id
end
if settings.name_identifier_value_requested != nil
subject = root.add_element "saml:Subject"
nameid = subject.add_element "saml:NameID"
nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
nameid.text = settings.name_identifier_value_requested
subject_confirmation = subject.add_element "saml:SubjectConfirmation"
subject_confirmation.attributes['Method'] = "urn:oasis:names:tc:SAML:2.0:cm:bearer"
end
if settings.name_identifier_format != nil
root.add_element "samlp:NameIDPolicy", {
# Might want to make AllowCreate a setting?
"AllowCreate" => "true",
"Format" => settings.name_identifier_format
}
end
if settings.authn_context || settings.authn_context_decl_ref
if settings.authn_context_comparison != nil
comparison = settings.authn_context_comparison
else
comparison = 'exact'
end
requested_context = root.add_element "samlp:RequestedAuthnContext", {
"Comparison" => comparison,
}
if settings.authn_context != nil
authn_contexts_class_ref = settings.authn_context.is_a?(Array) ? settings.authn_context : [settings.authn_context]
authn_contexts_class_ref.each do |authn_context_class_ref|
class_ref = requested_context.add_element "saml:AuthnContextClassRef"
class_ref.text = authn_context_class_ref
end
end
if settings.authn_context_decl_ref != nil
authn_contexts_decl_refs = settings.authn_context_decl_ref.is_a?(Array) ? settings.authn_context_decl_ref : [settings.authn_context_decl_ref]
authn_contexts_decl_refs.each do |authn_context_decl_ref|
decl_ref = requested_context.add_element "saml:AuthnContextDeclRef"
decl_ref.text = authn_context_decl_ref
end
end
end
request_doc
end
def sign_document(document, settings)
if settings.idp_sso_service_binding == Utils::BINDINGS[:post] && settings.security[:authn_requests_signed] && settings.private_key && settings.certificate
private_key = settings.get_sp_key
cert = settings.get_sp_cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end
document
end
end
end
end

View File

@ -0,0 +1,27 @@
require "onelogin/ruby-saml/validation_error"
module OneLogin
module RubySaml
module ErrorHandling
attr_accessor :errors
# Append the cause to the errors array, and based on the value of soft, return false or raise
# an exception. soft_override is provided as a means of overriding the object's notion of
# soft for just this invocation.
def append_error(error_msg, soft_override = nil)
@errors << error_msg
unless soft_override.nil? ? soft : soft_override
raise ValidationError.new(error_msg)
end
false
end
# Reset the errors array
def reset_errors!
@errors = []
end
end
end
end

View File

@ -0,0 +1,7 @@
module OneLogin
module RubySaml
class HttpError < StandardError
end
end
end

View File

@ -0,0 +1,473 @@
require "base64"
require "net/http"
require "net/https"
require "rexml/document"
require "rexml/xpath"
# Only supports SAML 2.0
module OneLogin
module RubySaml
include REXML
# Auxiliary class to retrieve and parse the Identity Provider Metadata
#
# This class does not validate in any way the URL that is introduced,
# make sure to validate it properly before use it in a parse_remote method.
# Read the `Security warning` section of the README.md file to get more info
#
class IdpMetadataParser
module SamlMetadata
module Vocabulary
METADATA = "urn:oasis:names:tc:SAML:2.0:metadata".freeze
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
NAME_FORMAT = "urn:oasis:names:tc:SAML:2.0:attrname-format:*".freeze
SAML_ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
end
NAMESPACE = {
"md" => Vocabulary::METADATA,
"NameFormat" => Vocabulary::NAME_FORMAT,
"saml" => Vocabulary::SAML_ASSERTION,
"ds" => Vocabulary::DSIG
}.freeze
end
include SamlMetadata::Vocabulary
attr_reader :document
attr_reader :response
attr_reader :options
# fetch IdP descriptors from a metadata document
def self.get_idps(metadata_document, only_entity_id=nil)
path = "//md:EntityDescriptor#{only_entity_id && '[@entityID="' + only_entity_id + '"]'}/md:IDPSSODescriptor"
REXML::XPath.match(
metadata_document,
path,
SamlMetadata::NAMESPACE
)
end
# Parse the Identity Provider metadata and update the settings with the
# IdP values
#
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param options [Hash] options used for parsing the metadata and the returned Settings instance
# @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [OneLogin::RubySaml::Settings]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote(url, validate_cert = true, options = {})
idp_metadata = get_idp_metadata(url, validate_cert)
parse(idp_metadata, options)
end
# Parse the Identity Provider metadata and return the results as Hash
#
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param options [Hash] options used for parsing the metadata
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [Hash]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote_to_hash(url, validate_cert = true, options = {})
parse_remote_to_array(url, validate_cert, options)[0]
end
# Parse all Identity Provider metadata and return the results as Array
#
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
#
# @param options [Hash] options used for parsing the metadata
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [Array<Hash>]
#
# @raise [HttpError] Failure to fetch remote IdP metadata
def parse_remote_to_array(url, validate_cert = true, options = {})
idp_metadata = get_idp_metadata(url, validate_cert)
parse_to_array(idp_metadata, options)
end
# Parse the Identity Provider metadata and update the settings with the IdP values
#
# @param idp_metadata [String]
#
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object or an hash for Settings overrides
# @option options [OneLogin::RubySaml::Settings, Hash] :settings the OneLogin::RubySaml::Settings object which gets the parsed metadata merged into or an hash for Settings overrides.
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [OneLogin::RubySaml::Settings]
def parse(idp_metadata, options = {})
parsed_metadata = parse_to_hash(idp_metadata, options)
unless parsed_metadata[:cache_duration].nil?
cache_valid_until_timestamp = OneLogin::RubySaml::Utils.parse_duration(parsed_metadata[:cache_duration])
unless cache_valid_until_timestamp.nil?
if parsed_metadata[:valid_until].nil? || cache_valid_until_timestamp < Time.parse(parsed_metadata[:valid_until], Time.now.utc).to_i
parsed_metadata[:valid_until] = Time.at(cache_valid_until_timestamp).utc.strftime("%Y-%m-%dT%H:%M:%SZ")
end
end
end
# Remove the cache_duration because on the settings
# we only gonna suppot valid_until
parsed_metadata.delete(:cache_duration)
settings = options[:settings]
if settings.nil?
OneLogin::RubySaml::Settings.new(parsed_metadata)
elsif settings.is_a?(Hash)
OneLogin::RubySaml::Settings.new(settings.merge(parsed_metadata))
else
merge_parsed_metadata_into(settings, parsed_metadata)
end
end
# Parse the Identity Provider metadata and return the results as Hash
#
# @param idp_metadata [String]
#
# @param options [Hash] options used for parsing the metadata and the returned Settings instance
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, the first entity descriptor is used.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [Hash]
def parse_to_hash(idp_metadata, options = {})
parse_to_array(idp_metadata, options)[0]
end
# Parse all Identity Provider metadata and return the results as Array
#
# @param idp_metadata [String]
#
# @param options [Hash] options used for parsing the metadata and the returned Settings instance
# @option options [String, nil] :entity_id when this is given, the entity descriptor for this ID is used. When omitted, all found IdPs are returned.
# @option options [String, Array<String>, nil] :sso_binding an ordered list of bindings to detect the single signon URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :slo_binding an ordered list of bindings to detect the single logout URL. The first binding in the list that is included in the metadata will be used.
# @option options [String, Array<String>, nil] :name_id_format an ordered list of NameIDFormats to detect a desired value. The first NameIDFormat in the list that is included in the metadata will be used.
#
# @return [Array<Hash>]
def parse_to_array(idp_metadata, options = {})
parse_to_idp_metadata_array(idp_metadata, options).map { |idp_md| idp_md.to_hash(options) }
end
def parse_to_idp_metadata_array(idp_metadata, options = {})
@document = REXML::Document.new(idp_metadata)
@options = options
idpsso_descriptors = self.class.get_idps(@document, options[:entity_id])
if !idpsso_descriptors.any?
raise ArgumentError.new("idp_metadata must contain an IDPSSODescriptor element")
end
idpsso_descriptors.map {|id| IdpMetadata.new(id, id.parent.attributes["entityID"])}
end
private
# Retrieve the remote IdP metadata from the URL or a cached copy.
# @param url [String] Url where the XML of the Identity Provider Metadata is published.
# @param validate_cert [Boolean] If true and the URL is HTTPs, the cert of the domain is checked.
# @return [REXML::document] Parsed XML IdP metadata
# @raise [HttpError] Failure to fetch remote IdP metadata
def get_idp_metadata(url, validate_cert)
uri = URI.parse(url)
raise ArgumentError.new("url must begin with http or https") unless /^https?/ =~ uri.scheme
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme == "https"
http.use_ssl = true
# Most IdPs will probably use self signed certs
http.verify_mode = validate_cert ? OpenSSL::SSL::VERIFY_PEER : OpenSSL::SSL::VERIFY_NONE
# Net::HTTP in Ruby 1.8 did not set the default certificate store
# automatically when VERIFY_PEER was specified.
if RUBY_VERSION < '1.9' && !http.ca_file && !http.ca_path && !http.cert_store
http.cert_store = OpenSSL::SSL::SSLContext::DEFAULT_CERT_STORE
end
end
get = Net::HTTP::Get.new(uri.request_uri)
get.basic_auth uri.user, uri.password if uri.user
@response = http.request(get)
return response.body if response.is_a? Net::HTTPSuccess
raise OneLogin::RubySaml::HttpError.new(
"Failed to fetch idp metadata: #{response.code}: #{response.message}"
)
end
class IdpMetadata
attr_reader :idpsso_descriptor, :entity_id
def initialize(idpsso_descriptor, entity_id)
@idpsso_descriptor = idpsso_descriptor
@entity_id = entity_id
end
def to_hash(options = {})
sso_binding = options[:sso_binding]
slo_binding = options[:slo_binding]
{
:idp_entity_id => @entity_id,
:name_identifier_format => idp_name_id_format(options[:name_id_format]),
:idp_sso_service_url => single_signon_service_url(sso_binding),
:idp_sso_service_binding => single_signon_service_binding(sso_binding),
:idp_slo_service_url => single_logout_service_url(slo_binding),
:idp_slo_service_binding => single_logout_service_binding(slo_binding),
:idp_slo_response_service_url => single_logout_response_service_url(slo_binding),
:idp_attribute_names => attribute_names,
:idp_cert => nil,
:idp_cert_fingerprint => nil,
:idp_cert_multi => nil,
:valid_until => valid_until,
:cache_duration => cache_duration,
}.tap do |response_hash|
merge_certificates_into(response_hash) unless certificates.nil?
end
end
# @return [String|nil] 'validUntil' attribute of metadata
#
def valid_until
root = @idpsso_descriptor.root
root.attributes['validUntil'] if root && root.attributes
end
# @return [String|nil] 'cacheDuration' attribute of metadata
#
def cache_duration
root = @idpsso_descriptor.root
root.attributes['cacheDuration'] if root && root.attributes
end
# @param name_id_priority [String|Array<String>] The prioritized list of NameIDFormat values to select. Will select first value if nil.
# @return [String|nil] IdP NameIDFormat value if exists
#
def idp_name_id_format(name_id_priority = nil)
nodes = REXML::XPath.match(
@idpsso_descriptor,
"md:NameIDFormat",
SamlMetadata::NAMESPACE
)
first_ranked_text(nodes, name_id_priority)
end
# @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
# @return [String|nil] SingleSignOnService binding if exists
#
def single_signon_service_binding(binding_priority = nil)
nodes = REXML::XPath.match(
@idpsso_descriptor,
"md:SingleSignOnService/@Binding",
SamlMetadata::NAMESPACE
)
first_ranked_value(nodes, binding_priority)
end
# @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
# @return [String|nil] SingleLogoutService binding if exists
#
def single_logout_service_binding(binding_priority = nil)
nodes = REXML::XPath.match(
@idpsso_descriptor,
"md:SingleLogoutService/@Binding",
SamlMetadata::NAMESPACE
)
first_ranked_value(nodes, binding_priority)
end
# @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
# @return [String|nil] SingleSignOnService endpoint if exists
#
def single_signon_service_url(binding_priority = nil)
binding = single_signon_service_binding(binding_priority)
return if binding.nil?
node = REXML::XPath.first(
@idpsso_descriptor,
"md:SingleSignOnService[@Binding=\"#{binding}\"]/@Location",
SamlMetadata::NAMESPACE
)
node.value if node
end
# @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
# @return [String|nil] SingleLogoutService endpoint if exists
#
def single_logout_service_url(binding_priority = nil)
binding = single_logout_service_binding(binding_priority)
return if binding.nil?
node = REXML::XPath.first(
@idpsso_descriptor,
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@Location",
SamlMetadata::NAMESPACE
)
node.value if node
end
# @param binding_priority [String|Array<String>] The prioritized list of Binding values to select. Will select first value if nil.
# @return [String|nil] SingleLogoutService response url if exists
#
def single_logout_response_service_url(binding_priority = nil)
binding = single_logout_service_binding(binding_priority)
return if binding.nil?
node = REXML::XPath.first(
@idpsso_descriptor,
"md:SingleLogoutService[@Binding=\"#{binding}\"]/@ResponseLocation",
SamlMetadata::NAMESPACE
)
node.value if node
end
# @return [String|nil] Unformatted Certificate if exists
#
def certificates
@certificates ||= begin
signing_nodes = REXML::XPath.match(
@idpsso_descriptor,
"md:KeyDescriptor[not(contains(@use, 'encryption'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
SamlMetadata::NAMESPACE
)
encryption_nodes = REXML::XPath.match(
@idpsso_descriptor,
"md:KeyDescriptor[not(contains(@use, 'signing'))]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
SamlMetadata::NAMESPACE
)
return nil if signing_nodes.empty? && encryption_nodes.empty?
certs = {}
unless signing_nodes.empty?
certs['signing'] = []
signing_nodes.each do |cert_node|
certs['signing'] << Utils.element_text(cert_node)
end
end
unless encryption_nodes.empty?
certs['encryption'] = []
encryption_nodes.each do |cert_node|
certs['encryption'] << Utils.element_text(cert_node)
end
end
certs
end
end
# @return [String|nil] the fingerpint of the X509Certificate if it exists
#
def fingerprint(certificate, fingerprint_algorithm = XMLSecurity::Document::SHA1)
@fingerprint ||= begin
return unless certificate
cert = OpenSSL::X509::Certificate.new(Base64.decode64(certificate))
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(fingerprint_algorithm).new
fingerprint_alg.hexdigest(cert.to_der).upcase.scan(/../).join(":")
end
end
# @return [Array] the names of all SAML attributes if any exist
#
def attribute_names
nodes = REXML::XPath.match(
@idpsso_descriptor ,
"saml:Attribute/@Name",
SamlMetadata::NAMESPACE
)
nodes.map(&:value)
end
def merge_certificates_into(parsed_metadata)
if (certificates.size == 1 &&
(certificates_has_one('signing') || certificates_has_one('encryption'))) ||
(certificates_has_one('signing') && certificates_has_one('encryption') &&
certificates["signing"][0] == certificates["encryption"][0])
if certificates.key?("signing")
parsed_metadata[:idp_cert] = certificates["signing"][0]
parsed_metadata[:idp_cert_fingerprint] = fingerprint(
parsed_metadata[:idp_cert],
parsed_metadata[:idp_cert_fingerprint_algorithm]
)
else
parsed_metadata[:idp_cert] = certificates["encryption"][0]
parsed_metadata[:idp_cert_fingerprint] = fingerprint(
parsed_metadata[:idp_cert],
parsed_metadata[:idp_cert_fingerprint_algorithm]
)
end
end
# symbolize keys of certificates and pass it on
parsed_metadata[:idp_cert_multi] = Hash[certificates.map { |k, v| [k.to_sym, v] }]
end
def certificates_has_one(key)
certificates.key?(key) && certificates[key].size == 1
end
private
def first_ranked_text(nodes, priority = nil)
return unless nodes.any?
priority = Array(priority)
if priority.any?
values = nodes.map(&:text)
priority.detect { |candidate| values.include?(candidate) }
else
nodes.first.text
end
end
def first_ranked_value(nodes, priority = nil)
return unless nodes.any?
priority = Array(priority)
if priority.any?
values = nodes.map(&:value)
priority.detect { |candidate| values.include?(candidate) }
else
nodes.first.value
end
end
end
def merge_parsed_metadata_into(settings, parsed_metadata)
parsed_metadata.each do |key, value|
settings.send("#{key}=".to_sym, value)
end
settings
end
end
end
end

View File

@ -0,0 +1,33 @@
require 'logger'
# Simplistic log class when we're running in Rails
module OneLogin
module RubySaml
class Logging
DEFAULT_LOGGER = ::Logger.new(STDOUT)
def self.logger
@logger ||= begin
(defined?(::Rails) && Rails.respond_to?(:logger) && Rails.logger) ||
DEFAULT_LOGGER
end
end
def self.logger=(logger)
@logger = logger
end
def self.debug(message)
return if !!ENV["ruby-saml/testing"]
logger.debug message
end
def self.info(message)
return if !!ENV["ruby-saml/testing"]
logger.info message
end
end
end
end

View File

@ -0,0 +1,151 @@
require "onelogin/ruby-saml/logging"
require "onelogin/ruby-saml/saml_message"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/setting_error"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Logout Request (SLO SP initiated, Builder)
#
class Logoutrequest < SamlMessage
# Logout Request ID
attr_accessor :uuid
# Initializes the Logout Request. A Logoutrequest Object that is an extension of the SamlMessage class.
# Asigns an ID, a random uuid.
#
def initialize
@uuid = OneLogin::RubySaml::Utils.uuid
end
def request_id
@uuid
end
# Creates the Logout Request string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [String] Logout Request string that includes the SAMLRequest
#
def create(settings, params={})
params = create_params(settings, params)
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
saml_request = CGI.escape(params.delete("SAMLRequest"))
request_params = "#{params_prefix}SAMLRequest=#{saml_request}"
params.each_pair do |key, value|
request_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
end
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
@logout_url = settings.idp_slo_service_url + request_params
end
# Creates the Get parameters for the logout request.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @return [Hash] Parameters
#
def create_params(settings, params={})
# The method expects :RelayState but sometimes we get 'RelayState' instead.
# Based on the HashWithIndifferentAccess value in Rails we could experience
# conflicts so this line will solve them.
relay_state = params[:RelayState] || params['RelayState']
if relay_state.nil?
params.delete(:RelayState)
params.delete('RelayState')
end
request_doc = create_logout_request_xml_doc(settings)
request_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
request = ""
request_doc.write(request)
Logging.debug "Created SLO Logout Request: #{request}"
request = deflate(request) if settings.compress_request
base64_request = encode(request)
request_params = {"SAMLRequest" => base64_request}
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_requests_signed] && settings.private_key
params['SigAlg'] = settings.security[:signature_method]
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLRequest',
:data => base64_request,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
params.each_pair do |key, value|
request_params[key] = value.to_s
end
request_params
end
# Creates the SAMLRequest String.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @return [String] The SAMLRequest String.
#
def create_logout_request_xml_doc(settings)
document = create_xml_document(settings)
sign_document(document, settings)
end
def create_xml_document(settings)
time = Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ")
request_doc = XMLSecurity::Document.new
request_doc.uuid = uuid
root = request_doc.add_element "samlp:LogoutRequest", { "xmlns:samlp" => "urn:oasis:names:tc:SAML:2.0:protocol", "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
root.attributes['ID'] = uuid
root.attributes['IssueInstant'] = time
root.attributes['Version'] = "2.0"
root.attributes['Destination'] = settings.idp_slo_service_url unless settings.idp_slo_service_url.nil? or settings.idp_slo_service_url.empty?
if settings.sp_entity_id
issuer = root.add_element "saml:Issuer"
issuer.text = settings.sp_entity_id
end
nameid = root.add_element "saml:NameID"
if settings.name_identifier_value
nameid.attributes['NameQualifier'] = settings.idp_name_qualifier if settings.idp_name_qualifier
nameid.attributes['SPNameQualifier'] = settings.sp_name_qualifier if settings.sp_name_qualifier
nameid.attributes['Format'] = settings.name_identifier_format if settings.name_identifier_format
nameid.text = settings.name_identifier_value
else
# If no NameID is present in the settings we generate one
nameid.text = OneLogin::RubySaml::Utils.uuid
nameid.attributes['Format'] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient'
end
if settings.sessionindex
sessionindex = root.add_element "samlp:SessionIndex"
sessionindex.text = settings.sessionindex
end
request_doc
end
def sign_document(document, settings)
# embed signature
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.security[:logout_requests_signed] && settings.private_key && settings.certificate
private_key = settings.get_sp_key
cert = settings.get_sp_cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end
document
end
end
end
end

View File

@ -0,0 +1,281 @@
require "xml_security"
require "onelogin/ruby-saml/saml_message"
require "time"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Logout Response (SLO IdP initiated, Parser)
#
class Logoutresponse < SamlMessage
include ErrorHandling
# OneLogin::RubySaml::Settings Toolkit settings
attr_accessor :settings
attr_reader :document
attr_reader :response
attr_reader :options
attr_accessor :soft
# Constructs the Logout Response. A Logout Response Object that is an extension of the SamlMessage class.
# @param response [String] A UUEncoded logout response from the IdP.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param options [Hash] Extra parameters.
# :matches_request_id It will validate that the logout response matches the ID of the request.
# :get_params GET Parameters, including the SAMLResponse
# :relax_signature_validation to accept signatures if no idp certificate registered on settings
#
# @raise [ArgumentError] if response is nil
#
def initialize(response, settings = nil, options = {})
@errors = []
raise ArgumentError.new("Logoutresponse cannot be nil") if response.nil?
@settings = settings
if settings.nil? || settings.soft.nil?
@soft = true
else
@soft = settings.soft
end
@options = options
@response = decode_raw_saml(response, settings)
@document = XMLSecurity::SignedDocument.new(@response)
end
def response_id
id(document)
end
# Checks if the Status has the "Success" code
# @return [Boolean] True if the StatusCode is Sucess
# @raise [ValidationError] if soft == false and validation fails
#
def success?
return status_code == "urn:oasis:names:tc:SAML:2.0:status:Success"
end
# @return [String|nil] Gets the InResponseTo attribute from the Logout Response if exists.
#
def in_response_to
@in_response_to ||= begin
node = REXML::XPath.first(
document,
"/p:LogoutResponse",
{ "p" => PROTOCOL }
)
node.nil? ? nil : node.attributes['InResponseTo']
end
end
# @return [String] Gets the Issuer from the Logout Response.
#
def issuer
@issuer ||= begin
node = REXML::XPath.first(
document,
"/p:LogoutResponse/a:Issuer",
{ "p" => PROTOCOL, "a" => ASSERTION }
)
Utils.element_text(node)
end
end
# @return [String] Gets the StatusCode from a Logout Response.
#
def status_code
@status_code ||= begin
node = REXML::XPath.first(document, "/p:LogoutResponse/p:Status/p:StatusCode", { "p" => PROTOCOL })
node.nil? ? nil : node.attributes["Value"]
end
end
def status_message
@status_message ||= begin
node = REXML::XPath.first(
document,
"/p:LogoutResponse/p:Status/p:StatusMessage",
{ "p" => PROTOCOL }
)
Utils.element_text(node)
end
end
# Aux function to validate the Logout Response
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
# @return [Boolean] TRUE if the SAML Response is valid
# @raise [ValidationError] if soft == false and validation fails
#
def validate(collect_errors = false)
reset_errors!
validations = [
:valid_state?,
:validate_success_status,
:validate_structure,
:valid_in_response_to?,
:valid_issuer?,
:validate_signature
]
if collect_errors
validations.each { |validation| send(validation) }
@errors.empty?
else
validations.all? { |validation| send(validation) }
end
end
private
# Validates the Status of the Logout Response
# If fails, the error is added to the errors array, including the StatusCode returned and the Status Message.
# @return [Boolean] True if the Logout Response contains a Success code, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_success_status
return true if success?
error_msg = 'The status code of the Logout Response was not Success'
status_error_msg = OneLogin::RubySaml::Utils.status_error_msg(error_msg, status_code, status_message)
append_error(status_error_msg)
end
# Validates the Logout Response against the specified schema.
# @return [Boolean] True if the XML is valid, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_structure
unless valid_saml?(document, soft)
return append_error("Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd")
end
true
end
# Validates that the Logout Response provided in the initialization is not empty,
# also check that the setting and the IdP cert were also provided
# @return [Boolean] True if the required info is found, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def valid_state?
return append_error("Blank logout response") if response.empty?
return append_error("No settings on logout response") if settings.nil?
return append_error("No sp_entity_id in settings of the logout response") if settings.sp_entity_id.nil?
if settings.idp_cert_fingerprint.nil? && settings.idp_cert.nil? && settings.idp_cert_multi.nil?
return append_error("No fingerprint or certificate on settings of the logout response")
end
true
end
# Validates if a provided :matches_request_id matchs the inResponseTo value.
# @param soft [String|nil] request_id The ID of the Logout Request sent by this SP to the IdP (if was sent any)
# @return [Boolean] True if there is no request_id or it match, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def valid_in_response_to?
return true unless options.has_key? :matches_request_id
return true if options[:matches_request_id].nil?
return true unless options[:matches_request_id] != in_response_to
error_msg = "The InResponseTo of the Logout Response: #{in_response_to}, does not match the ID of the Logout Request sent by the SP: #{options[:matches_request_id]}"
append_error(error_msg)
end
# Validates the Issuer of the Logout Response
# @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def valid_issuer?
return true if settings.idp_entity_id.nil? || issuer.nil?
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
return append_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>")
end
true
end
# Validates the Signature if it exists and the GET parameters are provided
# @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_signature
return true unless !options.nil?
return true unless options.has_key? :get_params
return true unless options[:get_params].has_key? 'Signature'
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
end
idp_cert = settings.get_idp_cert
idp_certs = settings.get_idp_cert_multi
if idp_cert.nil? && (idp_certs.nil? || idp_certs[:signing].empty?)
return options.has_key? :relax_signature_validation
end
query_string = OneLogin::RubySaml::Utils.build_query_from_raw_parts(
:type => 'SAMLResponse',
:raw_data => options[:raw_get_params]['SAMLResponse'],
:raw_relay_state => options[:raw_get_params]['RelayState'],
:raw_sig_alg => options[:raw_get_params]['SigAlg']
)
expired = false
if idp_certs.nil? || idp_certs[:signing].empty?
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => idp_cert,
:sig_alg => options[:get_params]['SigAlg'],
:signature => options[:get_params]['Signature'],
:query_string => query_string
)
if valid && settings.security[:check_idp_cert_expiration]
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
expired = true
end
end
else
valid = false
idp_certs[:signing].each do |signing_idp_cert|
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => signing_idp_cert,
:sig_alg => options[:get_params]['SigAlg'],
:signature => options[:get_params]['Signature'],
:query_string => query_string
)
if valid
if settings.security[:check_idp_cert_expiration]
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
expired = true
end
end
break
end
end
end
if expired
error_msg = "IdP x509 certificate expired"
return append_error(error_msg)
end
unless valid
error_msg = "Invalid Signature on Logout Response"
return append_error(error_msg)
end
true
end
end
end
end

View File

@ -0,0 +1,177 @@
require "uri"
require "onelogin/ruby-saml/logging"
require "onelogin/ruby-saml/utils"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Metadata. XML Metadata Builder
#
class Metadata
# Return SP metadata based on the settings.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param pretty_print [Boolean] Pretty print or not the response
# (No pretty print if you gonna validate the signature)
# @param valid_until [DateTime] Metadata's valid time
# @param cache_duration [Integer] Duration of the cache in seconds
# @return [String] XML Metadata of the Service Provider
#
def generate(settings, pretty_print=false, valid_until=nil, cache_duration=nil)
meta_doc = XMLSecurity::Document.new
add_xml_declaration(meta_doc)
root = add_root_element(meta_doc, settings, valid_until, cache_duration)
sp_sso = add_sp_sso_element(root, settings)
add_sp_certificates(sp_sso, settings)
add_sp_service_elements(sp_sso, settings)
add_extras(root, settings)
embed_signature(meta_doc, settings)
output_xml(meta_doc, pretty_print)
end
protected
def add_xml_declaration(meta_doc)
meta_doc << REXML::XMLDecl.new('1.0', 'UTF-8')
end
def add_root_element(meta_doc, settings, valid_until, cache_duration)
namespaces = {
"xmlns:md" => "urn:oasis:names:tc:SAML:2.0:metadata"
}
if settings.attribute_consuming_service.configured?
namespaces["xmlns:saml"] = "urn:oasis:names:tc:SAML:2.0:assertion"
end
root = meta_doc.add_element("md:EntityDescriptor", namespaces)
root.attributes["ID"] = OneLogin::RubySaml::Utils.uuid
root.attributes["entityID"] = settings.sp_entity_id if settings.sp_entity_id
root.attributes["validUntil"] = valid_until.strftime('%Y-%m-%dT%H:%M:%S%z') if valid_until
root.attributes["cacheDuration"] = "PT" + cache_duration.to_s + "S" if cache_duration
root
end
def add_sp_sso_element(root, settings)
root.add_element "md:SPSSODescriptor", {
"protocolSupportEnumeration" => "urn:oasis:names:tc:SAML:2.0:protocol",
"AuthnRequestsSigned" => settings.security[:authn_requests_signed],
"WantAssertionsSigned" => settings.security[:want_assertions_signed],
}
end
# Add KeyDescriptor if messages will be signed / encrypted
# with SP certificate, and new SP certificate if any
def add_sp_certificates(sp_sso, settings)
cert = settings.get_sp_cert
cert_new = settings.get_sp_cert_new
for sp_cert in [cert, cert_new]
if sp_cert
cert_text = Base64.encode64(sp_cert.to_der).gsub("\n", '')
kd = sp_sso.add_element "md:KeyDescriptor", { "use" => "signing" }
ki = kd.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
xd = ki.add_element "ds:X509Data"
xc = xd.add_element "ds:X509Certificate"
xc.text = cert_text
if settings.security[:want_assertions_encrypted]
kd2 = sp_sso.add_element "md:KeyDescriptor", { "use" => "encryption" }
ki2 = kd2.add_element "ds:KeyInfo", {"xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#"}
xd2 = ki2.add_element "ds:X509Data"
xc2 = xd2.add_element "ds:X509Certificate"
xc2.text = cert_text
end
end
end
sp_sso
end
def add_sp_service_elements(sp_sso, settings)
if settings.single_logout_service_url
sp_sso.add_element "md:SingleLogoutService", {
"Binding" => settings.single_logout_service_binding,
"Location" => settings.single_logout_service_url,
"ResponseLocation" => settings.single_logout_service_url
}
end
if settings.name_identifier_format
nameid = sp_sso.add_element "md:NameIDFormat"
nameid.text = settings.name_identifier_format
end
if settings.assertion_consumer_service_url
sp_sso.add_element "md:AssertionConsumerService", {
"Binding" => settings.assertion_consumer_service_binding,
"Location" => settings.assertion_consumer_service_url,
"isDefault" => true,
"index" => 0
}
end
if settings.attribute_consuming_service.configured?
sp_acs = sp_sso.add_element "md:AttributeConsumingService", {
"isDefault" => "true",
"index" => settings.attribute_consuming_service.index
}
srv_name = sp_acs.add_element "md:ServiceName", {
"xml:lang" => "en"
}
srv_name.text = settings.attribute_consuming_service.name
settings.attribute_consuming_service.attributes.each do |attribute|
sp_req_attr = sp_acs.add_element "md:RequestedAttribute", {
"NameFormat" => attribute[:name_format],
"Name" => attribute[:name],
"FriendlyName" => attribute[:friendly_name],
"isRequired" => attribute[:is_required] || false
}
unless attribute[:attribute_value].nil?
Array(attribute[:attribute_value]).each do |value|
sp_attr_val = sp_req_attr.add_element "saml:AttributeValue"
sp_attr_val.text = value.to_s
end
end
end
end
# With OpenSSO, it might be required to also include
# <md:RoleDescriptor xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:query="urn:oasis:names:tc:SAML:metadata:ext:query" xsi:type="query:AttributeQueryDescriptorType" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
# <md:XACMLAuthzDecisionQueryDescriptor WantAssertionsSigned="false" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"/>
sp_sso
end
# can be overridden in subclass
def add_extras(root, _settings)
root
end
def embed_signature(meta_doc, settings)
return unless settings.security[:metadata_signed]
private_key = settings.get_sp_key
cert = settings.get_sp_cert
return unless private_key && cert
meta_doc.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end
def output_xml(meta_doc, pretty_print)
ret = ''
# pretty print the XML so IdP administrators can easily see what the SP supports
if pretty_print
meta_doc.write(ret, 1)
else
ret = meta_doc.to_s
end
ret
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,161 @@
require 'cgi'
require 'zlib'
require 'base64'
require 'nokogiri'
require 'rexml/document'
require 'rexml/xpath'
require 'thread'
require "onelogin/ruby-saml/error_handling"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Message
#
class SamlMessage
include REXML
ASSERTION = "urn:oasis:names:tc:SAML:2.0:assertion".freeze
PROTOCOL = "urn:oasis:names:tc:SAML:2.0:protocol".freeze
BASE64_FORMAT = %r(\A([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?\Z)
@@mutex = Mutex.new
# @return [Nokogiri::XML::Schema] Gets the schema object of the SAML 2.0 Protocol schema
#
def self.schema
@@mutex.synchronize do
Dir.chdir(File.expand_path("../../../schemas", __FILE__)) do
::Nokogiri::XML::Schema(File.read("saml-schema-protocol-2.0.xsd"))
end
end
end
# @return [String|nil] Gets the Version attribute from the SAML Message if exists.
#
def version(document)
@version ||= begin
node = REXML::XPath.first(
document,
"/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
{ "p" => PROTOCOL }
)
node.nil? ? nil : node.attributes['Version']
end
end
# @return [String|nil] Gets the ID attribute from the SAML Message if exists.
#
def id(document)
@id ||= begin
node = REXML::XPath.first(
document,
"/p:AuthnRequest | /p:Response | /p:LogoutResponse | /p:LogoutRequest",
{ "p" => PROTOCOL }
)
node.nil? ? nil : node.attributes['ID']
end
end
# Validates the SAML Message against the specified schema.
# @param document [REXML::Document] The message that will be validated
# @param soft [Boolean] soft Enable or Disable the soft mode (In order to raise exceptions when the message is invalid or not)
# @return [Boolean] True if the XML is valid, otherwise False, if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def valid_saml?(document, soft = true)
begin
xml = Nokogiri::XML(document.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
rescue Exception => error
return false if soft
raise ValidationError.new("XML load failed: #{error.message}")
end
SamlMessage.schema.validate(xml).map do |schema_error|
return false if soft
raise ValidationError.new("#{schema_error.message}\n\n#{xml.to_s}")
end
end
private
# Base64 decode and try also to inflate a SAML Message
# @param saml [String] The deflated and encoded SAML Message
# @return [String] The plain SAML Message
#
def decode_raw_saml(saml, settings = nil)
return saml unless base64_encoded?(saml)
settings = OneLogin::RubySaml::Settings.new if settings.nil?
if saml.bytesize > settings.message_max_bytesize
raise ValidationError.new("Encoded SAML Message exceeds " + settings.message_max_bytesize.to_s + " bytes, so was rejected")
end
decoded = decode(saml)
begin
inflate(decoded)
rescue
decoded
end
end
# Deflate, base64 encode and url-encode a SAML Message (To be used in the HTTP-redirect binding)
# @param saml [String] The plain SAML Message
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @return [String] The deflated and encoded SAML Message (encoded if the compression is requested)
#
def encode_raw_saml(saml, settings)
saml = deflate(saml) if settings.compress_request
CGI.escape(encode(saml))
end
# Base 64 decode method
# @param string [String] The string message
# @return [String] The decoded string
#
def decode(string)
Base64.decode64(string)
end
# Base 64 encode method
# @param string [String] The string
# @return [String] The encoded string
#
def encode(string)
if Base64.respond_to?('strict_encode64')
Base64.strict_encode64(string)
else
Base64.encode64(string).gsub(/\n/, "")
end
end
# Check if a string is base64 encoded
# @param string [String] string to check the encoding of
# @return [true, false] whether or not the string is base64 encoded
#
def base64_encoded?(string)
!!string.gsub(/[\r\n]|\\r|\\n|\s/, "").match(BASE64_FORMAT)
end
# Inflate method
# @param deflated [String] The string
# @return [String] The inflated string
#
def inflate(deflated)
Zlib::Inflate.new(-Zlib::MAX_WBITS).inflate(deflated)
end
# Deflate method
# @param inflated [String] The string
# @return [String] The deflated string
#
def deflate(inflated)
Zlib::Deflate.deflate(inflated, 9)[2..-5]
end
end
end
end

View File

@ -0,0 +1,6 @@
module OneLogin
module RubySaml
class SettingError < StandardError
end
end
end

View File

@ -0,0 +1,290 @@
require "xml_security"
require "onelogin/ruby-saml/attribute_service"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/validation_error"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Toolkit Settings
#
class Settings
def initialize(overrides = {}, keep_security_attributes = false)
if keep_security_attributes
security_attributes = overrides.delete(:security) || {}
config = DEFAULTS.merge(overrides)
config[:security] = DEFAULTS[:security].merge(security_attributes)
else
config = DEFAULTS.merge(overrides)
end
config.each do |k,v|
acc = "#{k.to_s}=".to_sym
if respond_to? acc
value = v.is_a?(Hash) ? v.dup : v
send(acc, value)
end
end
@attribute_consuming_service = AttributeService.new
end
# IdP Data
attr_accessor :idp_entity_id
attr_writer :idp_sso_service_url
attr_writer :idp_slo_service_url
attr_accessor :idp_slo_response_service_url
attr_accessor :idp_cert
attr_accessor :idp_cert_fingerprint
attr_accessor :idp_cert_fingerprint_algorithm
attr_accessor :idp_cert_multi
attr_accessor :idp_attribute_names
attr_accessor :idp_name_qualifier
attr_accessor :valid_until
# SP Data
attr_writer :sp_entity_id
attr_accessor :assertion_consumer_service_url
attr_reader :assertion_consumer_service_binding
attr_writer :single_logout_service_url
attr_accessor :sp_name_qualifier
attr_accessor :name_identifier_format
attr_accessor :name_identifier_value
attr_accessor :name_identifier_value_requested
attr_accessor :sessionindex
attr_accessor :compress_request
attr_accessor :compress_response
attr_accessor :double_quote_xml_attribute_values
attr_accessor :message_max_bytesize
attr_accessor :passive
attr_reader :protocol_binding
attr_accessor :attributes_index
attr_accessor :force_authn
attr_accessor :certificate
attr_accessor :certificate_new
attr_accessor :private_key
attr_accessor :authn_context
attr_accessor :authn_context_comparison
attr_accessor :authn_context_decl_ref
attr_reader :attribute_consuming_service
# Work-flow
attr_accessor :security
attr_accessor :soft
# Deprecated
attr_accessor :assertion_consumer_logout_service_url
attr_reader :assertion_consumer_logout_service_binding
attr_accessor :issuer
attr_accessor :idp_sso_target_url
attr_accessor :idp_slo_target_url
# @return [String] IdP Single Sign On Service URL
#
def idp_sso_service_url
@idp_sso_service_url || @idp_sso_target_url
end
# @return [String] IdP Single Logout Service URL
#
def idp_slo_service_url
@idp_slo_service_url || @idp_slo_target_url
end
# @return [String] IdP Single Sign On Service Binding
#
def idp_sso_service_binding
@idp_sso_service_binding || idp_binding_from_embed_sign
end
# Setter for IdP Single Sign On Service Binding
# @param value [String, Symbol].
#
def idp_sso_service_binding=(value)
@idp_sso_service_binding = get_binding(value)
end
# @return [String] IdP Single Logout Service Binding
#
def idp_slo_service_binding
@idp_slo_service_binding || idp_binding_from_embed_sign
end
# Setter for IdP Single Logout Service Binding
# @param value [String, Symbol].
#
def idp_slo_service_binding=(value)
@idp_slo_service_binding = get_binding(value)
end
# @return [String] SP Entity ID
#
def sp_entity_id
@sp_entity_id || @issuer
end
# Setter for SP Protocol Binding
# @param value [String, Symbol].
#
def protocol_binding=(value)
@protocol_binding = get_binding(value)
end
# Setter for SP Assertion Consumer Service Binding
# @param value [String, Symbol].
#
def assertion_consumer_service_binding=(value)
@assertion_consumer_service_binding = get_binding(value)
end
# @return [String] Single Logout Service URL.
#
def single_logout_service_url
@single_logout_service_url || @assertion_consumer_logout_service_url
end
# @return [String] Single Logout Service Binding.
#
def single_logout_service_binding
@single_logout_service_binding || @assertion_consumer_logout_service_binding
end
# Setter for Single Logout Service Binding.
#
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
# @param value [String, Symbol]
#
def single_logout_service_binding=(value)
@single_logout_service_binding = get_binding(value)
end
# @deprecated Setter for legacy Single Logout Service Binding parameter.
#
# (Currently we only support "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect")
# @param value [String, Symbol]
#
def assertion_consumer_logout_service_binding=(value)
@assertion_consumer_logout_service_binding = get_binding(value)
end
# Calculates the fingerprint of the IdP x509 certificate.
# @return [String] The fingerprint
#
def get_fingerprint
idp_cert_fingerprint || begin
idp_cert = get_idp_cert
if idp_cert
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(idp_cert_fingerprint_algorithm).new
fingerprint_alg.hexdigest(idp_cert.to_der).upcase.scan(/../).join(":")
end
end
end
# @return [OpenSSL::X509::Certificate|nil] Build the IdP certificate from the settings (previously format it)
#
def get_idp_cert
return nil if idp_cert.nil? || idp_cert.empty?
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
OpenSSL::X509::Certificate.new(formatted_cert)
end
# @return [Hash with 2 arrays of OpenSSL::X509::Certificate] Build multiple IdP certificates from the settings.
#
def get_idp_cert_multi
return nil if idp_cert_multi.nil? || idp_cert_multi.empty?
raise ArgumentError.new("Invalid value for idp_cert_multi") if not idp_cert_multi.is_a?(Hash)
certs = {:signing => [], :encryption => [] }
if idp_cert_multi.key?(:signing) and not idp_cert_multi[:signing].empty?
idp_cert_multi[:signing].each do |idp_cert|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
certs[:signing].push(OpenSSL::X509::Certificate.new(formatted_cert))
end
end
if idp_cert_multi.key?(:encryption) and not idp_cert_multi[:encryption].empty?
idp_cert_multi[:encryption].each do |idp_cert|
formatted_cert = OneLogin::RubySaml::Utils.format_cert(idp_cert)
certs[:encryption].push(OpenSSL::X509::Certificate.new(formatted_cert))
end
end
certs
end
# @return [OpenSSL::X509::Certificate|nil] Build the SP certificate from the settings (previously format it)
#
def get_sp_cert
return nil if certificate.nil? || certificate.empty?
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate)
cert = OpenSSL::X509::Certificate.new(formatted_cert)
if security[:check_sp_cert_expiration]
if OneLogin::RubySaml::Utils.is_cert_expired(cert)
raise OneLogin::RubySaml::ValidationError.new("The SP certificate expired.")
end
end
cert
end
# @return [OpenSSL::X509::Certificate|nil] Build the New SP certificate from the settings (previously format it)
#
def get_sp_cert_new
return nil if certificate_new.nil? || certificate_new.empty?
formatted_cert = OneLogin::RubySaml::Utils.format_cert(certificate_new)
OpenSSL::X509::Certificate.new(formatted_cert)
end
# @return [OpenSSL::PKey::RSA] Build the SP private from the settings (previously format it)
#
def get_sp_key
return nil if private_key.nil? || private_key.empty?
formatted_private_key = OneLogin::RubySaml::Utils.format_private_key(private_key)
OpenSSL::PKey::RSA.new(formatted_private_key)
end
private
def idp_binding_from_embed_sign
security[:embed_sign] ? Utils::BINDINGS[:post] : Utils::BINDINGS[:redirect]
end
def get_binding(value)
return unless value
Utils::BINDINGS[value.to_sym] || value
end
DEFAULTS = {
:assertion_consumer_service_binding => Utils::BINDINGS[:post],
:single_logout_service_binding => Utils::BINDINGS[:redirect],
:idp_cert_fingerprint_algorithm => XMLSecurity::Document::SHA1,
:compress_request => true,
:compress_response => true,
:message_max_bytesize => 250000,
:soft => true,
:double_quote_xml_attribute_values => false,
:security => {
:authn_requests_signed => false,
:logout_requests_signed => false,
:logout_responses_signed => false,
:want_assertions_signed => false,
:want_assertions_encrypted => false,
:want_name_id => false,
:metadata_signed => false,
:embed_sign => false, # Deprecated
:digest_method => XMLSecurity::Document::SHA1,
:signature_method => XMLSecurity::Document::RSA_SHA1,
:check_idp_cert_expiration => false,
:check_sp_cert_expiration => false,
:strict_audience_validation => false,
:lowercase_url_encoding => false
}.freeze
}.freeze
end
end
end

View File

@ -0,0 +1,316 @@
require 'zlib'
require 'time'
require 'nokogiri'
require "onelogin/ruby-saml/saml_message"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Logout Request (SLO IdP initiated, Parser)
#
class SloLogoutrequest < SamlMessage
include ErrorHandling
# OneLogin::RubySaml::Settings Toolkit settings
attr_accessor :settings
attr_reader :document
attr_reader :request
attr_reader :options
attr_accessor :soft
# Constructs the Logout Request. A Logout Request Object that is an extension of the SamlMessage class.
# @param request [String] A UUEncoded Logout Request from the IdP.
# @param options [Hash] :settings to provide the OneLogin::RubySaml::Settings object
# Or :allowed_clock_drift for the logout request validation process to allow a clock drift when checking dates with
# Or :relax_signature_validation to accept signatures if no idp certificate registered on settings
#
# @raise [ArgumentError] If Request is nil
#
def initialize(request, options = {})
raise ArgumentError.new("Request cannot be nil") if request.nil?
@errors = []
@options = options
@soft = true
unless options[:settings].nil?
@settings = options[:settings]
unless @settings.soft.nil?
@soft = @settings.soft
end
end
@request = decode_raw_saml(request, settings)
@document = REXML::Document.new(@request)
end
def request_id
id(document)
end
# Validates the Logout Request with the default values (soft = true)
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating.
# @return [Boolean] TRUE if the Logout Request is valid
#
def is_valid?(collect_errors = false)
validate(collect_errors)
end
# @return [String] Gets the NameID of the Logout Request.
#
def name_id
@name_id ||= begin
node = REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
Utils.element_text(node)
end
end
alias_method :nameid, :name_id
# @return [String] Gets the NameID Format of the Logout Request.
#
def name_id_format
@name_id_node ||= REXML::XPath.first(document, "/p:LogoutRequest/a:NameID", { "p" => PROTOCOL, "a" => ASSERTION })
@name_id_format ||=
if @name_id_node && @name_id_node.attribute("Format")
@name_id_node.attribute("Format").value
end
end
alias_method :nameid_format, :name_id_format
# @return [String|nil] Gets the ID attribute from the Logout Request. if exists.
#
def id
super(document)
end
# @return [String] Gets the Issuer from the Logout Request.
#
def issuer
@issuer ||= begin
node = REXML::XPath.first(
document,
"/p:LogoutRequest/a:Issuer",
{ "p" => PROTOCOL, "a" => ASSERTION }
)
Utils.element_text(node)
end
end
# @return [Time|nil] Gets the NotOnOrAfter Attribute value if exists.
#
def not_on_or_after
@not_on_or_after ||= begin
node = REXML::XPath.first(
document,
"/p:LogoutRequest",
{ "p" => PROTOCOL }
)
if node && node.attributes["NotOnOrAfter"]
Time.parse(node.attributes["NotOnOrAfter"])
end
end
end
# @return [Array] Gets the SessionIndex if exists (Supported multiple values). Empty Array if none found
#
def session_indexes
nodes = REXML::XPath.match(
document,
"/p:LogoutRequest/p:SessionIndex",
{ "p" => PROTOCOL }
)
nodes.map { |node| Utils.element_text(node) }
end
private
# returns the allowed clock drift on timing validation
# @return [Float]
def allowed_clock_drift
options[:allowed_clock_drift].to_f.abs + Float::EPSILON
end
# Hard aux function to validate the Logout Request
# @param collect_errors [Boolean] Stop validation when first error appears or keep validating. (if soft=true)
# @return [Boolean] TRUE if the Logout Request is valid
# @raise [ValidationError] if soft == false and validation fails
#
def validate(collect_errors = false)
reset_errors!
validations = [
:validate_request_state,
:validate_id,
:validate_version,
:validate_structure,
:validate_not_on_or_after,
:validate_issuer,
:validate_signature
]
if collect_errors
validations.each { |validation| send(validation) }
@errors.empty?
else
validations.all? { |validation| send(validation) }
end
end
# Validates that the Logout Request contains an ID
# If fails, the error is added to the errors array.
# @return [Boolean] True if the Logout Request contains an ID, otherwise returns False
#
def validate_id
unless id
return append_error("Missing ID attribute on Logout Request")
end
true
end
# Validates the SAML version (2.0)
# If fails, the error is added to the errors array.
# @return [Boolean] True if the Logout Request is 2.0, otherwise returns False
#
def validate_version
unless version(document) == "2.0"
return append_error("Unsupported SAML version")
end
true
end
# Validates the time. (If the logout request was initialized with the :allowed_clock_drift
# option, the timing validations are relaxed by the allowed_clock_drift value)
# If fails, the error is added to the errors array
# @return [Boolean] True if satisfies the conditions, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_not_on_or_after
now = Time.now.utc
if not_on_or_after && now >= (not_on_or_after + allowed_clock_drift)
return append_error("Current time is on or after NotOnOrAfter (#{now} >= #{not_on_or_after}#{" + #{allowed_clock_drift.ceil}s" if allowed_clock_drift > 0})")
end
true
end
# Validates the Logout Request against the specified schema.
# @return [Boolean] True if the XML is valid, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_structure
unless valid_saml?(document, soft)
return append_error("Invalid SAML Logout Request. Not match the saml-schema-protocol-2.0.xsd")
end
true
end
# Validates that the Logout Request provided in the initialization is not empty,
# @return [Boolean] True if the required info is found, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_request_state
return append_error("Blank logout request") if request.nil? || request.empty?
true
end
# Validates the Issuer of the Logout Request
# If fails, the error is added to the errors array
# @return [Boolean] True if the Issuer matchs the IdP entityId, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_issuer
return true if settings.nil? || settings.idp_entity_id.nil? || issuer.nil?
unless OneLogin::RubySaml::Utils.uri_match?(issuer, settings.idp_entity_id)
return append_error("Doesn't match the issuer, expected: <#{settings.idp_entity_id}>, but was: <#{issuer}>")
end
true
end
# Validates the Signature if exists and GET parameters are provided
# @return [Boolean] True if not contains a Signature or if the Signature is valid, otherwise False if soft=True
# @raise [ValidationError] if soft == false and validation fails
#
def validate_signature
return true if options.nil?
return true unless options.has_key? :get_params
return true unless options[:get_params].has_key? 'Signature'
options[:raw_get_params] = OneLogin::RubySaml::Utils.prepare_raw_get_params(options[:raw_get_params], options[:get_params], settings.security[:lowercase_url_encoding])
if options[:get_params]['SigAlg'].nil? && !options[:raw_get_params]['SigAlg'].nil?
options[:get_params]['SigAlg'] = CGI.unescape(options[:raw_get_params]['SigAlg'])
end
idp_cert = settings.get_idp_cert
idp_certs = settings.get_idp_cert_multi
if idp_cert.nil? && (idp_certs.nil? || idp_certs[:signing].empty?)
return options.has_key? :relax_signature_validation
end
query_string = OneLogin::RubySaml::Utils.build_query_from_raw_parts(
:type => 'SAMLRequest',
:raw_data => options[:raw_get_params]['SAMLRequest'],
:raw_relay_state => options[:raw_get_params]['RelayState'],
:raw_sig_alg => options[:raw_get_params]['SigAlg']
)
expired = false
if idp_certs.nil? || idp_certs[:signing].empty?
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => idp_cert,
:sig_alg => options[:get_params]['SigAlg'],
:signature => options[:get_params]['Signature'],
:query_string => query_string
)
if valid && settings.security[:check_idp_cert_expiration]
if OneLogin::RubySaml::Utils.is_cert_expired(idp_cert)
expired = true
end
end
else
valid = false
idp_certs[:signing].each do |signing_idp_cert|
valid = OneLogin::RubySaml::Utils.verify_signature(
:cert => signing_idp_cert,
:sig_alg => options[:get_params]['SigAlg'],
:signature => options[:get_params]['Signature'],
:query_string => query_string
)
if valid
if settings.security[:check_idp_cert_expiration]
if OneLogin::RubySaml::Utils.is_cert_expired(signing_idp_cert)
expired = true
end
end
break
end
end
end
if expired
error_msg = "IdP x509 certificate expired"
return append_error(error_msg)
end
unless valid
return append_error("Invalid Signature on Logout Request")
end
true
end
end
end
end

View File

@ -0,0 +1,164 @@
require "onelogin/ruby-saml/logging"
require "onelogin/ruby-saml/saml_message"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/setting_error"
# Only supports SAML 2.0
module OneLogin
module RubySaml
# SAML2 Logout Response (SLO SP initiated, Parser)
#
class SloLogoutresponse < SamlMessage
# Logout Response ID
attr_accessor :uuid
# Initializes the Logout Response. A SloLogoutresponse Object that is an extension of the SamlMessage class.
# Asigns an ID, a random uuid.
#
def initialize
@uuid = OneLogin::RubySaml::Utils.uuid
end
def response_id
@uuid
end
# Creates the Logout Response string.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
# @return [String] Logout Request string that includes the SAMLRequest
#
def create(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
params = create_params(settings, request_id, logout_message, params, logout_status_code)
params_prefix = (settings.idp_slo_service_url =~ /\?/) ? '&' : '?'
url = settings.idp_slo_response_service_url || settings.idp_slo_service_url
saml_response = CGI.escape(params.delete("SAMLResponse"))
response_params = "#{params_prefix}SAMLResponse=#{saml_response}"
params.each_pair do |key, value|
response_params << "&#{key.to_s}=#{CGI.escape(value.to_s)}"
end
raise SettingError.new "Invalid settings, idp_slo_service_url is not set!" if url.nil? or url.empty?
@logout_url = url + response_params
end
# Creates the Get parameters for the logout response.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
# @param params [Hash] Some extra parameters to be added in the GET for example the RelayState
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
# @return [Hash] Parameters
#
def create_params(settings, request_id = nil, logout_message = nil, params = {}, logout_status_code = nil)
# The method expects :RelayState but sometimes we get 'RelayState' instead.
# Based on the HashWithIndifferentAccess value in Rails we could experience
# conflicts so this line will solve them.
relay_state = params[:RelayState] || params['RelayState']
if relay_state.nil?
params.delete(:RelayState)
params.delete('RelayState')
end
response_doc = create_logout_response_xml_doc(settings, request_id, logout_message, logout_status_code)
response_doc.context[:attribute_quote] = :quote if settings.double_quote_xml_attribute_values
response = ""
response_doc.write(response)
Logging.debug "Created SLO Logout Response: #{response}"
response = deflate(response) if settings.compress_response
base64_response = encode(response)
response_params = {"SAMLResponse" => base64_response}
if settings.idp_slo_service_binding == Utils::BINDINGS[:redirect] && settings.security[:logout_responses_signed] && settings.private_key
params['SigAlg'] = settings.security[:signature_method]
url_string = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLResponse',
:data => base64_response,
:relay_state => relay_state,
:sig_alg => params['SigAlg']
)
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, url_string)
params['Signature'] = encode(signature)
end
params.each_pair do |key, value|
response_params[key] = value.to_s
end
response_params
end
# Creates the SAMLResponse String.
# @param settings [OneLogin::RubySaml::Settings|nil] Toolkit settings
# @param request_id [String] The ID of the LogoutRequest sent by this SP to the IdP. That ID will be placed as the InResponseTo in the logout response
# @param logout_message [String] The Message to be placed as StatusMessage in the logout response
# @param logout_status_code [String] The StatusCode to be placed as StatusMessage in the logout response
# @return [String] The SAMLResponse String.
#
def create_logout_response_xml_doc(settings, request_id = nil, logout_message = nil, logout_status_code = nil)
document = create_xml_document(settings, request_id, logout_message, logout_status_code)
sign_document(document, settings)
end
def create_xml_document(settings, request_id = nil, logout_message = nil, status_code = nil)
time = Time.now.utc.strftime('%Y-%m-%dT%H:%M:%SZ')
response_doc = XMLSecurity::Document.new
response_doc.uuid = uuid
destination = settings.idp_slo_response_service_url || settings.idp_slo_service_url
root = response_doc.add_element 'samlp:LogoutResponse', { 'xmlns:samlp' => 'urn:oasis:names:tc:SAML:2.0:protocol', "xmlns:saml" => "urn:oasis:names:tc:SAML:2.0:assertion" }
root.attributes['ID'] = uuid
root.attributes['IssueInstant'] = time
root.attributes['Version'] = '2.0'
root.attributes['InResponseTo'] = request_id unless request_id.nil?
root.attributes['Destination'] = destination unless destination.nil? or destination.empty?
if settings.sp_entity_id != nil
issuer = root.add_element "saml:Issuer"
issuer.text = settings.sp_entity_id
end
# add status
status = root.add_element 'samlp:Status'
# status code
status_code ||= 'urn:oasis:names:tc:SAML:2.0:status:Success'
status_code_elem = status.add_element 'samlp:StatusCode'
status_code_elem.attributes['Value'] = status_code
# status message
logout_message ||= 'Successfully Signed Out'
status_message = status.add_element 'samlp:StatusMessage'
status_message.text = logout_message
response_doc
end
def sign_document(document, settings)
# embed signature
if settings.idp_slo_service_binding == Utils::BINDINGS[:post] && settings.private_key && settings.certificate
private_key = settings.get_sp_key
cert = settings.get_sp_cert
document.sign_document(private_key, cert, settings.security[:signature_method], settings.security[:digest_method])
end
document
end
end
end
end

View File

@ -0,0 +1,389 @@
if RUBY_VERSION < '1.9'
require 'uuid'
else
require 'securerandom'
end
require "openssl"
module OneLogin
module RubySaml
# SAML2 Auxiliary class
#
class Utils
@@uuid_generator = UUID.new if RUBY_VERSION < '1.9'
BINDINGS = { :post => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST".freeze,
:redirect => "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect".freeze }.freeze
DSIG = "http://www.w3.org/2000/09/xmldsig#".freeze
XENC = "http://www.w3.org/2001/04/xmlenc#".freeze
DURATION_FORMAT = %r(^
(-?)P # 1: Duration sign
(?:
(?:(\d+)Y)? # 2: Years
(?:(\d+)M)? # 3: Months
(?:(\d+)D)? # 4: Days
(?:T
(?:(\d+)H)? # 5: Hours
(?:(\d+)M)? # 6: Minutes
(?:(\d+(?:[.,]\d+)?)S)? # 7: Seconds
)?
|
(\d+)W # 8: Weeks
)
$)x.freeze
UUID_PREFIX = '_'
# Checks if the x509 cert provided is expired
#
# @param cert [Certificate] The x509 certificate
#
def self.is_cert_expired(cert)
if cert.is_a?(String)
cert = OpenSSL::X509::Certificate.new(cert)
end
return cert.not_after < Time.now
end
# Interprets a ISO8601 duration value relative to a given timestamp.
#
# @param duration [String] The duration, as a string.
# @param timestamp [Integer] The unix timestamp we should apply the
# duration to. Optional, default to the
# current time.
#
# @return [Integer] The new timestamp, after the duration is applied.
#
def self.parse_duration(duration, timestamp=Time.now.utc)
return nil if RUBY_VERSION < '1.9' # 1.8.7 not supported
matches = duration.match(DURATION_FORMAT)
if matches.nil?
raise Exception.new("Invalid ISO 8601 duration")
end
sign = matches[1] == '-' ? -1 : 1
durYears, durMonths, durDays, durHours, durMinutes, durSeconds, durWeeks =
matches[2..8].map { |match| match ? sign * match.tr(',', '.').to_f : 0.0 }
initial_datetime = Time.at(timestamp).utc.to_datetime
final_datetime = initial_datetime.next_year(durYears)
final_datetime = final_datetime.next_month(durMonths)
final_datetime = final_datetime.next_day((7*durWeeks) + durDays)
final_timestamp = final_datetime.to_time.utc.to_i + (durHours * 3600) + (durMinutes * 60) + durSeconds
return final_timestamp
end
# Return a properly formatted x509 certificate
#
# @param cert [String] The original certificate
# @return [String] The formatted certificate
#
def self.format_cert(cert)
# don't try to format an encoded certificate or if is empty or nil
if cert.respond_to?(:ascii_only?)
return cert if cert.nil? || cert.empty? || !cert.ascii_only?
else
return cert if cert.nil? || cert.empty? || cert.match(/\x0d/)
end
if cert.scan(/BEGIN CERTIFICATE/).length > 1
formatted_cert = []
cert.scan(/-{5}BEGIN CERTIFICATE-{5}[\n\r]?.*?-{5}END CERTIFICATE-{5}[\n\r]?/m) {|c|
formatted_cert << format_cert(c)
}
formatted_cert.join("\n")
else
cert = cert.gsub(/\-{5}\s?(BEGIN|END) CERTIFICATE\s?\-{5}/, "")
cert = cert.gsub(/\r/, "")
cert = cert.gsub(/\n/, "")
cert = cert.gsub(/\s/, "")
cert = cert.scan(/.{1,64}/)
cert = cert.join("\n")
"-----BEGIN CERTIFICATE-----\n#{cert}\n-----END CERTIFICATE-----"
end
end
# Return a properly formatted private key
#
# @param key [String] The original private key
# @return [String] The formatted private key
#
def self.format_private_key(key)
# don't try to format an encoded private key or if is empty
return key if key.nil? || key.empty? || key.match(/\x0d/)
# is this an rsa key?
rsa_key = key.match("RSA PRIVATE KEY")
key = key.gsub(/\-{5}\s?(BEGIN|END)( RSA)? PRIVATE KEY\s?\-{5}/, "")
key = key.gsub(/\n/, "")
key = key.gsub(/\r/, "")
key = key.gsub(/\s/, "")
key = key.scan(/.{1,64}/)
key = key.join("\n")
key_label = rsa_key ? "RSA PRIVATE KEY" : "PRIVATE KEY"
"-----BEGIN #{key_label}-----\n#{key}\n-----END #{key_label}-----"
end
# Build the Query String signature that will be used in the HTTP-Redirect binding
# to generate the Signature
# @param params [Hash] Parameters to build the Query String
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
# @option params [String] :data Base64 encoded SAMLRequest or SAMLResponse
# @option params [String] :relay_state The RelayState parameter
# @option params [String] :sig_alg The SigAlg parameter
# @return [String] The Query String
#
def self.build_query(params)
type, data, relay_state, sig_alg = [:type, :data, :relay_state, :sig_alg].map { |k| params[k]}
url_string = "#{type}=#{CGI.escape(data)}"
url_string << "&RelayState=#{CGI.escape(relay_state)}" if relay_state
url_string << "&SigAlg=#{CGI.escape(sig_alg)}"
end
# Reconstruct a canonical query string from raw URI-encoded parts, to be used in verifying a signature
#
# @param params [Hash] Parameters to build the Query String
# @option params [String] :type 'SAMLRequest' or 'SAMLResponse'
# @option params [String] :raw_data URI-encoded, base64 encoded SAMLRequest or SAMLResponse, as sent by IDP
# @option params [String] :raw_relay_state URI-encoded RelayState parameter, as sent by IDP
# @option params [String] :raw_sig_alg URI-encoded SigAlg parameter, as sent by IDP
# @return [String] The Query String
#
def self.build_query_from_raw_parts(params)
type, raw_data, raw_relay_state, raw_sig_alg = [:type, :raw_data, :raw_relay_state, :raw_sig_alg].map { |k| params[k]}
url_string = "#{type}=#{raw_data}"
url_string << "&RelayState=#{raw_relay_state}" if raw_relay_state
url_string << "&SigAlg=#{raw_sig_alg}"
end
# Prepare raw GET parameters (build them from normal parameters
# if not provided).
#
# @param rawparams [Hash] Raw GET Parameters
# @param params [Hash] GET Parameters
# @param lowercase_url_encoding [bool] Lowercase URL Encoding (For ADFS urlencode compatiblity)
# @return [Hash] New raw parameters
#
def self.prepare_raw_get_params(rawparams, params, lowercase_url_encoding=false)
rawparams ||= {}
if rawparams['SAMLRequest'].nil? && !params['SAMLRequest'].nil?
rawparams['SAMLRequest'] = escape_request_param(params['SAMLRequest'], lowercase_url_encoding)
end
if rawparams['SAMLResponse'].nil? && !params['SAMLResponse'].nil?
rawparams['SAMLResponse'] = escape_request_param(params['SAMLResponse'], lowercase_url_encoding)
end
if rawparams['RelayState'].nil? && !params['RelayState'].nil?
rawparams['RelayState'] = escape_request_param(params['RelayState'], lowercase_url_encoding)
end
if rawparams['SigAlg'].nil? && !params['SigAlg'].nil?
rawparams['SigAlg'] = escape_request_param(params['SigAlg'], lowercase_url_encoding)
end
rawparams
end
def self.escape_request_param(param, lowercase_url_encoding)
CGI.escape(param).tap do |escaped|
next unless lowercase_url_encoding
escaped.gsub!(/%[A-Fa-f0-9]{2}/) { |match| match.downcase }
end
end
# Validate the Signature parameter sent on the HTTP-Redirect binding
# @param params [Hash] Parameters to be used in the validation process
# @option params [OpenSSL::X509::Certificate] cert The Identity provider public certtificate
# @option params [String] sig_alg The SigAlg parameter
# @option params [String] signature The Signature parameter (base64 encoded)
# @option params [String] query_string The full GET Query String to be compared
# @return [Boolean] True if the Signature is valid, False otherwise
#
def self.verify_signature(params)
cert, sig_alg, signature, query_string = [:cert, :sig_alg, :signature, :query_string].map { |k| params[k]}
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(sig_alg)
return cert.public_key.verify(signature_algorithm.new, Base64.decode64(signature), query_string)
end
# Build the status error message
# @param status_code [String] StatusCode value
# @param status_message [Strig] StatusMessage value
# @return [String] The status error message
def self.status_error_msg(error_msg, raw_status_code = nil, status_message = nil)
unless raw_status_code.nil?
if raw_status_code.include? "|"
status_codes = raw_status_code.split(' | ')
values = status_codes.collect do |status_code|
status_code.split(':').last
end
printable_code = values.join(" => ")
else
printable_code = raw_status_code.split(':').last
end
error_msg << ', was ' + printable_code
end
unless status_message.nil?
error_msg << ' -> ' + status_message
end
error_msg
end
# Obtains the decrypted string from an Encrypted node element in XML
# @param encrypted_node [REXML::Element] The Encrypted element
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
# @return [String] The decrypted data
def self.decrypt_data(encrypted_node, private_key)
encrypt_data = REXML::XPath.first(
encrypted_node,
"./xenc:EncryptedData",
{ 'xenc' => XENC }
)
symmetric_key = retrieve_symmetric_key(encrypt_data, private_key)
cipher_value = REXML::XPath.first(
encrypt_data,
"./xenc:CipherData/xenc:CipherValue",
{ 'xenc' => XENC }
)
node = Base64.decode64(element_text(cipher_value))
encrypt_method = REXML::XPath.first(
encrypt_data,
"./xenc:EncryptionMethod",
{ 'xenc' => XENC }
)
algorithm = encrypt_method.attributes['Algorithm']
retrieve_plaintext(node, symmetric_key, algorithm)
end
# Obtains the symmetric key from the EncryptedData element
# @param encrypt_data [REXML::Element] The EncryptedData element
# @param private_key [OpenSSL::PKey::RSA] The Service provider private key
# @return [String] The symmetric key
def self.retrieve_symmetric_key(encrypt_data, private_key)
encrypted_key = REXML::XPath.first(
encrypt_data,
"./ds:KeyInfo/xenc:EncryptedKey | ./KeyInfo/xenc:EncryptedKey | //xenc:EncryptedKey[@Id=$id]",
{ "ds" => DSIG, "xenc" => XENC },
{ "id" => self.retrieve_symetric_key_reference(encrypt_data) }
)
encrypted_symmetric_key_element = REXML::XPath.first(
encrypted_key,
"./xenc:CipherData/xenc:CipherValue",
"xenc" => XENC
)
cipher_text = Base64.decode64(element_text(encrypted_symmetric_key_element))
encrypt_method = REXML::XPath.first(
encrypted_key,
"./xenc:EncryptionMethod",
"xenc" => XENC
)
algorithm = encrypt_method.attributes['Algorithm']
retrieve_plaintext(cipher_text, private_key, algorithm)
end
def self.retrieve_symetric_key_reference(encrypt_data)
REXML::XPath.first(
encrypt_data,
"substring-after(./ds:KeyInfo/ds:RetrievalMethod/@URI, '#')",
{ "ds" => DSIG }
)
end
# Obtains the deciphered text
# @param cipher_text [String] The ciphered text
# @param symmetric_key [String] The symetric key used to encrypt the text
# @param algorithm [String] The encrypted algorithm
# @return [String] The deciphered text
def self.retrieve_plaintext(cipher_text, symmetric_key, algorithm)
case algorithm
when 'http://www.w3.org/2001/04/xmlenc#tripledes-cbc' then cipher = OpenSSL::Cipher.new('DES-EDE3-CBC').decrypt
when 'http://www.w3.org/2001/04/xmlenc#aes128-cbc' then cipher = OpenSSL::Cipher.new('AES-128-CBC').decrypt
when 'http://www.w3.org/2001/04/xmlenc#aes192-cbc' then cipher = OpenSSL::Cipher.new('AES-192-CBC').decrypt
when 'http://www.w3.org/2001/04/xmlenc#aes256-cbc' then cipher = OpenSSL::Cipher.new('AES-256-CBC').decrypt
when 'http://www.w3.org/2009/xmlenc11#aes128-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(128, :GCM).decrypt
when 'http://www.w3.org/2009/xmlenc11#aes192-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(192, :GCM).decrypt
when 'http://www.w3.org/2009/xmlenc11#aes256-gcm' then auth_cipher = OpenSSL::Cipher::AES.new(256, :GCM).decrypt
when 'http://www.w3.org/2001/04/xmlenc#rsa-1_5' then rsa = symmetric_key
when 'http://www.w3.org/2001/04/xmlenc#rsa-oaep-mgf1p' then oaep = symmetric_key
end
if cipher
iv_len = cipher.iv_len
data = cipher_text[iv_len..-1]
cipher.padding, cipher.key, cipher.iv = 0, symmetric_key, cipher_text[0..iv_len-1]
assertion_plaintext = cipher.update(data)
assertion_plaintext << cipher.final
elsif auth_cipher
iv_len, text_len, tag_len = auth_cipher.iv_len, cipher_text.length, 16
data = cipher_text[iv_len..text_len-1-tag_len]
auth_cipher.padding = 0
auth_cipher.key = symmetric_key
auth_cipher.iv = cipher_text[0..iv_len-1]
auth_cipher.auth_data = ''
auth_cipher.auth_tag = cipher_text[text_len-tag_len..-1]
assertion_plaintext = auth_cipher.update(data)
assertion_plaintext << auth_cipher.final
elsif rsa
rsa.private_decrypt(cipher_text)
elsif oaep
oaep.private_decrypt(cipher_text, OpenSSL::PKey::RSA::PKCS1_OAEP_PADDING)
else
cipher_text
end
end
def self.set_prefix(value)
UUID_PREFIX.replace value
end
def self.uuid
"#{UUID_PREFIX}" + (RUBY_VERSION < '1.9' ? "#{@@uuid_generator.generate}" : "#{SecureRandom.uuid}")
end
# Given two strings, attempt to match them as URIs using Rails' parse method. If they can be parsed,
# then the fully-qualified domain name and the host should performa a case-insensitive match, per the
# RFC for URIs. If Rails can not parse the string in to URL pieces, return a boolean match of the
# two strings. This maintains the previous functionality.
# @return [Boolean]
def self.uri_match?(destination_url, settings_url)
dest_uri = URI.parse(destination_url)
acs_uri = URI.parse(settings_url)
if dest_uri.scheme.nil? || acs_uri.scheme.nil? || dest_uri.host.nil? || acs_uri.host.nil?
raise URI::InvalidURIError
else
dest_uri.scheme.downcase == acs_uri.scheme.downcase &&
dest_uri.host.downcase == acs_uri.host.downcase &&
dest_uri.path == acs_uri.path &&
dest_uri.query == acs_uri.query
end
rescue URI::InvalidURIError
original_uri_match?(destination_url, settings_url)
end
# If Rails' URI.parse can't match to valid URL, default back to the original matching service.
# @return [Boolean]
def self.original_uri_match?(destination_url, settings_url)
destination_url == settings_url
end
# Given a REXML::Element instance, return the concatenation of all child text nodes. Assumes
# that there all children other than text nodes can be ignored (e.g. comments). If nil is
# passed, nil will be returned.
def self.element_text(element)
element.texts.map(&:value).join if element
end
end
end
end

View File

@ -0,0 +1,7 @@
module OneLogin
module RubySaml
class ValidationError < StandardError
end
end
end

View File

@ -0,0 +1,5 @@
module OneLogin
module RubySaml
VERSION = '1.14.0'
end
end

View File

@ -0,0 +1 @@
require 'onelogin/ruby-saml'

View File

@ -0,0 +1,283 @@
<?xml version="1.0" encoding="US-ASCII"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<import namespace="http://www.w3.org/2000/09/xmldsig#"
schemaLocation="xmldsig-core-schema.xsd"/>
<import namespace="http://www.w3.org/2001/04/xmlenc#"
schemaLocation="xenc-schema.xsd"/>
<annotation>
<documentation>
Document identifier: saml-schema-assertion-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V1.0 (November, 2002):
Initial Standard Schema.
V1.1 (September, 2003):
Updates within the same V1.0 namespace.
V2.0 (March, 2005):
New assertion schema for SAML V2.0 namespace.
</documentation>
</annotation>
<attributeGroup name="IDNameQualifiers">
<attribute name="NameQualifier" type="string" use="optional"/>
<attribute name="SPNameQualifier" type="string" use="optional"/>
</attributeGroup>
<element name="BaseID" type="saml:BaseIDAbstractType"/>
<complexType name="BaseIDAbstractType" abstract="true">
<attributeGroup ref="saml:IDNameQualifiers"/>
</complexType>
<element name="NameID" type="saml:NameIDType"/>
<complexType name="NameIDType">
<simpleContent>
<extension base="string">
<attributeGroup ref="saml:IDNameQualifiers"/>
<attribute name="Format" type="anyURI" use="optional"/>
<attribute name="SPProvidedID" type="string" use="optional"/>
</extension>
</simpleContent>
</complexType>
<complexType name="EncryptedElementType">
<sequence>
<element ref="xenc:EncryptedData"/>
<element ref="xenc:EncryptedKey" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="EncryptedID" type="saml:EncryptedElementType"/>
<element name="Issuer" type="saml:NameIDType"/>
<element name="AssertionIDRef" type="NCName"/>
<element name="AssertionURIRef" type="anyURI"/>
<element name="Assertion" type="saml:AssertionType"/>
<complexType name="AssertionType">
<sequence>
<element ref="saml:Issuer"/>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="saml:Subject" minOccurs="0"/>
<element ref="saml:Conditions" minOccurs="0"/>
<element ref="saml:Advice" minOccurs="0"/>
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="saml:Statement"/>
<element ref="saml:AuthnStatement"/>
<element ref="saml:AuthzDecisionStatement"/>
<element ref="saml:AttributeStatement"/>
</choice>
</sequence>
<attribute name="Version" type="string" use="required"/>
<attribute name="ID" type="ID" use="required"/>
<attribute name="IssueInstant" type="dateTime" use="required"/>
</complexType>
<element name="Subject" type="saml:SubjectType"/>
<complexType name="SubjectType">
<choice>
<sequence>
<choice>
<element ref="saml:BaseID"/>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
<element ref="saml:SubjectConfirmation" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<element ref="saml:SubjectConfirmation" maxOccurs="unbounded"/>
</choice>
</complexType>
<element name="SubjectConfirmation" type="saml:SubjectConfirmationType"/>
<complexType name="SubjectConfirmationType">
<sequence>
<choice minOccurs="0">
<element ref="saml:BaseID"/>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
<element ref="saml:SubjectConfirmationData" minOccurs="0"/>
</sequence>
<attribute name="Method" type="anyURI" use="required"/>
</complexType>
<element name="SubjectConfirmationData" type="saml:SubjectConfirmationDataType"/>
<complexType name="SubjectConfirmationDataType" mixed="true">
<complexContent>
<restriction base="anyType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="NotBefore" type="dateTime" use="optional"/>
<attribute name="NotOnOrAfter" type="dateTime" use="optional"/>
<attribute name="Recipient" type="anyURI" use="optional"/>
<attribute name="InResponseTo" type="NCName" use="optional"/>
<attribute name="Address" type="string" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</restriction>
</complexContent>
</complexType>
<complexType name="KeyInfoConfirmationDataType" mixed="false">
<complexContent>
<restriction base="saml:SubjectConfirmationDataType">
<sequence>
<element ref="ds:KeyInfo" maxOccurs="unbounded"/>
</sequence>
</restriction>
</complexContent>
</complexType>
<element name="Conditions" type="saml:ConditionsType"/>
<complexType name="ConditionsType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="saml:Condition"/>
<element ref="saml:AudienceRestriction"/>
<element ref="saml:OneTimeUse"/>
<element ref="saml:ProxyRestriction"/>
</choice>
<attribute name="NotBefore" type="dateTime" use="optional"/>
<attribute name="NotOnOrAfter" type="dateTime" use="optional"/>
</complexType>
<element name="Condition" type="saml:ConditionAbstractType"/>
<complexType name="ConditionAbstractType" abstract="true"/>
<element name="AudienceRestriction" type="saml:AudienceRestrictionType"/>
<complexType name="AudienceRestrictionType">
<complexContent>
<extension base="saml:ConditionAbstractType">
<sequence>
<element ref="saml:Audience" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="Audience" type="anyURI"/>
<element name="OneTimeUse" type="saml:OneTimeUseType" />
<complexType name="OneTimeUseType">
<complexContent>
<extension base="saml:ConditionAbstractType"/>
</complexContent>
</complexType>
<element name="ProxyRestriction" type="saml:ProxyRestrictionType"/>
<complexType name="ProxyRestrictionType">
<complexContent>
<extension base="saml:ConditionAbstractType">
<sequence>
<element ref="saml:Audience" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Count" type="nonNegativeInteger" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="Advice" type="saml:AdviceType"/>
<complexType name="AdviceType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="saml:AssertionIDRef"/>
<element ref="saml:AssertionURIRef"/>
<element ref="saml:Assertion"/>
<element ref="saml:EncryptedAssertion"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="EncryptedAssertion" type="saml:EncryptedElementType"/>
<element name="Statement" type="saml:StatementAbstractType"/>
<complexType name="StatementAbstractType" abstract="true"/>
<element name="AuthnStatement" type="saml:AuthnStatementType"/>
<complexType name="AuthnStatementType">
<complexContent>
<extension base="saml:StatementAbstractType">
<sequence>
<element ref="saml:SubjectLocality" minOccurs="0"/>
<element ref="saml:AuthnContext"/>
</sequence>
<attribute name="AuthnInstant" type="dateTime" use="required"/>
<attribute name="SessionIndex" type="string" use="optional"/>
<attribute name="SessionNotOnOrAfter" type="dateTime" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="SubjectLocality" type="saml:SubjectLocalityType"/>
<complexType name="SubjectLocalityType">
<attribute name="Address" type="string" use="optional"/>
<attribute name="DNSName" type="string" use="optional"/>
</complexType>
<element name="AuthnContext" type="saml:AuthnContextType"/>
<complexType name="AuthnContextType">
<sequence>
<choice>
<sequence>
<element ref="saml:AuthnContextClassRef"/>
<choice minOccurs="0">
<element ref="saml:AuthnContextDecl"/>
<element ref="saml:AuthnContextDeclRef"/>
</choice>
</sequence>
<choice>
<element ref="saml:AuthnContextDecl"/>
<element ref="saml:AuthnContextDeclRef"/>
</choice>
</choice>
<element ref="saml:AuthenticatingAuthority" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="AuthnContextClassRef" type="anyURI"/>
<element name="AuthnContextDeclRef" type="anyURI"/>
<element name="AuthnContextDecl" type="anyType"/>
<element name="AuthenticatingAuthority" type="anyURI"/>
<element name="AuthzDecisionStatement" type="saml:AuthzDecisionStatementType"/>
<complexType name="AuthzDecisionStatementType">
<complexContent>
<extension base="saml:StatementAbstractType">
<sequence>
<element ref="saml:Action" maxOccurs="unbounded"/>
<element ref="saml:Evidence" minOccurs="0"/>
</sequence>
<attribute name="Resource" type="anyURI" use="required"/>
<attribute name="Decision" type="saml:DecisionType" use="required"/>
</extension>
</complexContent>
</complexType>
<simpleType name="DecisionType">
<restriction base="string">
<enumeration value="Permit"/>
<enumeration value="Deny"/>
<enumeration value="Indeterminate"/>
</restriction>
</simpleType>
<element name="Action" type="saml:ActionType"/>
<complexType name="ActionType">
<simpleContent>
<extension base="string">
<attribute name="Namespace" type="anyURI" use="required"/>
</extension>
</simpleContent>
</complexType>
<element name="Evidence" type="saml:EvidenceType"/>
<complexType name="EvidenceType">
<choice maxOccurs="unbounded">
<element ref="saml:AssertionIDRef"/>
<element ref="saml:AssertionURIRef"/>
<element ref="saml:Assertion"/>
<element ref="saml:EncryptedAssertion"/>
</choice>
</complexType>
<element name="AttributeStatement" type="saml:AttributeStatementType"/>
<complexType name="AttributeStatementType">
<complexContent>
<extension base="saml:StatementAbstractType">
<choice maxOccurs="unbounded">
<element ref="saml:Attribute"/>
<element ref="saml:EncryptedAttribute"/>
</choice>
</extension>
</complexContent>
</complexType>
<element name="Attribute" type="saml:AttributeType"/>
<complexType name="AttributeType">
<sequence>
<element ref="saml:AttributeValue" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Name" type="string" use="required"/>
<attribute name="NameFormat" type="anyURI" use="optional"/>
<attribute name="FriendlyName" type="string" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<element name="AttributeValue" type="anyType" nillable="true"/>
<element name="EncryptedAttribute" type="saml:EncryptedElementType"/>
</schema>

View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
targetNamespace="urn:oasis:names:tc:SAML:2.0:ac"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns="urn:oasis:names:tc:SAML:2.0:ac"
blockDefault="substitution"
version="2.0">
<xs:annotation>
<xs:documentation>
Document identifier: saml-schema-authn-context-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V2.0 (March, 2005):
New core authentication context schema for SAML V2.0.
This is just an include of all types from the schema
referred to in the include statement below.
</xs:documentation>
</xs:annotation>
<xs:include schemaLocation="saml-schema-authn-context-types-2.0.xsd"/>
</xs:schema>

View File

@ -0,0 +1,821 @@
<?xml version="1.0" encoding="UTF-8"?>
<xs:schema
xmlns:xs="http://www.w3.org/2001/XMLSchema"
elementFormDefault="qualified"
version="2.0">
<xs:annotation>
<xs:documentation>
Document identifier: saml-schema-authn-context-types-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V2.0 (March, 2005):
New core authentication context schema types for SAML V2.0.
</xs:documentation>
</xs:annotation>
<xs:element name="AuthenticationContextDeclaration" type="AuthnContextDeclarationBaseType">
<xs:annotation>
<xs:documentation>
A particular assertion on an identity
provider's part with respect to the authentication
context associated with an authentication assertion.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Identification" type="IdentificationType">
<xs:annotation>
<xs:documentation>
Refers to those characteristics that describe the
processes and mechanisms
the Authentication Authority uses to initially create
an association between a Principal
and the identity (or name) by which the Principal will
be known
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PhysicalVerification">
<xs:annotation>
<xs:documentation>
This element indicates that identification has been
performed in a physical
face-to-face meeting with the principal and not in an
online manner.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="credentialLevel">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="primary"/>
<xs:enumeration value="secondary"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="WrittenConsent" type="ExtensionOnlyType"/>
<xs:element name="TechnicalProtection" type="TechnicalProtectionBaseType">
<xs:annotation>
<xs:documentation>
Refers to those characterstics that describe how the
'secret' (the knowledge or possession
of which allows the Principal to authenticate to the
Authentication Authority) is kept secure
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SecretKeyProtection" type="SecretKeyProtectionType">
<xs:annotation>
<xs:documentation>
This element indicates the types and strengths of
facilities
of a UA used to protect a shared secret key from
unauthorized access and/or use.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PrivateKeyProtection" type="PrivateKeyProtectionType">
<xs:annotation>
<xs:documentation>
This element indicates the types and strengths of
facilities
of a UA used to protect a private key from
unauthorized access and/or use.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="KeyActivation" type="KeyActivationType">
<xs:annotation>
<xs:documentation>The actions that must be performed
before the private key can be used. </xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="KeySharing" type="KeySharingType">
<xs:annotation>
<xs:documentation>Whether or not the private key is shared
with the certificate authority.</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="KeyStorage" type="KeyStorageType">
<xs:annotation>
<xs:documentation>
In which medium is the key stored.
memory - the key is stored in memory.
smartcard - the key is stored in a smartcard.
token - the key is stored in a hardware token.
MobileDevice - the key is stored in a mobile device.
MobileAuthCard - the key is stored in a mobile
authentication card.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SubscriberLineNumber" type="ExtensionOnlyType"/>
<xs:element name="UserSuffix" type="ExtensionOnlyType"/>
<xs:element name="Password" type="PasswordType">
<xs:annotation>
<xs:documentation>
This element indicates that a password (or passphrase)
has been used to
authenticate the Principal to a remote system.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ActivationPin" type="ActivationPinType">
<xs:annotation>
<xs:documentation>
This element indicates that a Pin (Personal
Identification Number) has been used to authenticate the Principal to
some local system in order to activate a key.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Token" type="TokenType">
<xs:annotation>
<xs:documentation>
This element indicates that a hardware or software
token is used
as a method of identifying the Principal.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="TimeSyncToken" type="TimeSyncTokenType">
<xs:annotation>
<xs:documentation>
This element indicates that a time synchronization
token is used to identify the Principal. hardware -
the time synchonization
token has been implemented in hardware. software - the
time synchronization
token has been implemented in software. SeedLength -
the length, in bits, of the
random seed used in the time synchronization token.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Smartcard" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that a smartcard is used to
identity the Principal.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Length" type="LengthType">
<xs:annotation>
<xs:documentation>
This element indicates the minimum and/or maximum
ASCII length of the password which is enforced (by the UA or the
IdP). In other words, this is the minimum and/or maximum number of
ASCII characters required to represent a valid password.
min - the minimum number of ASCII characters required
in a valid password, as enforced by the UA or the IdP.
max - the maximum number of ASCII characters required
in a valid password, as enforced by the UA or the IdP.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ActivationLimit" type="ActivationLimitType">
<xs:annotation>
<xs:documentation>
This element indicates the length of time for which an
PIN-based authentication is valid.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Generation">
<xs:annotation>
<xs:documentation>
Indicates whether the password was chosen by the
Principal or auto-supplied by the Authentication Authority.
principalchosen - the Principal is allowed to choose
the value of the password. This is true even if
the initial password is chosen at random by the UA or
the IdP and the Principal is then free to change
the password.
automatic - the password is chosen by the UA or the
IdP to be cryptographically strong in some sense,
or to satisfy certain password rules, and that the
Principal is not free to change it or to choose a new password.
</xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:attribute name="mechanism" use="required">
<xs:simpleType>
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="principalchosen"/>
<xs:enumeration value="automatic"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="AuthnMethod" type="AuthnMethodBaseType">
<xs:annotation>
<xs:documentation>
Refers to those characteristics that define the
mechanisms by which the Principal authenticates to the Authentication
Authority.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PrincipalAuthenticationMechanism" type="PrincipalAuthenticationMechanismType">
<xs:annotation>
<xs:documentation>
The method that a Principal employs to perform
authentication to local system components.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="Authenticator" type="AuthenticatorBaseType">
<xs:annotation>
<xs:documentation>
The method applied to validate a principal's
authentication across a network
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ComplexAuthenticator" type="ComplexAuthenticatorType">
<xs:annotation>
<xs:documentation>
Supports Authenticators with nested combinations of
additional complexity.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PreviousSession" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
Indicates that the Principal has been strongly
authenticated in a previous session during which the IdP has set a
cookie in the UA. During the present session the Principal has only
been authenticated by the UA returning the cookie to the IdP.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ResumeSession" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
Rather like PreviousSession but using stronger
security. A secret that was established in a previous session with
the Authentication Authority has been cached by the local system and
is now re-used (e.g. a Master Secret is used to derive new session
keys in TLS, SSL, WTLS).
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ZeroKnowledge" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Principal has been
authenticated by a zero knowledge technique as specified in ISO/IEC
9798-5.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SharedSecretChallengeResponse" type="SharedSecretChallengeResponseType"/>
<xs:complexType name="SharedSecretChallengeResponseType">
<xs:annotation>
<xs:documentation>
This element indicates that the Principal has been
authenticated by a challenge-response protocol utilizing shared secret
keys and symmetric cryptography.
</xs:documentation>
</xs:annotation>
<xs:sequence>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="method" type="xs:anyURI" use="optional"/>
</xs:complexType>
<xs:element name="DigSig" type="PublicKeyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Principal has been
authenticated by a mechanism which involves the Principal computing a
digital signature over at least challenge data provided by the IdP.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="AsymmetricDecryption" type="PublicKeyType">
<xs:annotation>
<xs:documentation>
The local system has a private key but it is used
in decryption mode, rather than signature mode. For example, the
Authentication Authority generates a secret and encrypts it using the
local system's public key: the local system then proves it has
decrypted the secret.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="AsymmetricKeyAgreement" type="PublicKeyType">
<xs:annotation>
<xs:documentation>
The local system has a private key and uses it for
shared secret key agreement with the Authentication Authority (e.g.
via Diffie Helman).
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="PublicKeyType">
<xs:sequence>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="keyValidation" use="optional"/>
</xs:complexType>
<xs:element name="IPAddress" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Principal has been
authenticated through connection from a particular IP address.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SharedSecretDynamicPlaintext" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
The local system and Authentication Authority
share a secret key. The local system uses this to encrypt a
randomised string to pass to the Authentication Authority.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="AuthenticatorTransportProtocol" type="AuthenticatorTransportProtocolType">
<xs:annotation>
<xs:documentation>
The protocol across which Authenticator information is
transferred to an Authentication Authority verifier.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="HTTP" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Authenticator has been
transmitted using bare HTTP utilizing no additional security
protocols.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="IPSec" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Authenticator has been
transmitted using a transport mechanism protected by an IPSEC session.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="WTLS" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Authenticator has been
transmitted using a transport mechanism protected by a WTLS session.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="MobileNetworkNoEncryption" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Authenticator has been
transmitted solely across a mobile network using no additional
security mechanism.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="MobileNetworkRadioEncryption" type="ExtensionOnlyType"/>
<xs:element name="MobileNetworkEndToEndEncryption" type="ExtensionOnlyType"/>
<xs:element name="SSL" type="ExtensionOnlyType">
<xs:annotation>
<xs:documentation>
This element indicates that the Authenticator has been
transmitted using a transport mechnanism protected by an SSL or TLS
session.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="PSTN" type="ExtensionOnlyType"/>
<xs:element name="ISDN" type="ExtensionOnlyType"/>
<xs:element name="ADSL" type="ExtensionOnlyType"/>
<xs:element name="OperationalProtection" type="OperationalProtectionType">
<xs:annotation>
<xs:documentation>
Refers to those characteristics that describe
procedural security controls employed by the Authentication Authority.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="SecurityAudit" type="SecurityAuditType"/>
<xs:element name="SwitchAudit" type="ExtensionOnlyType"/>
<xs:element name="DeactivationCallCenter" type="ExtensionOnlyType"/>
<xs:element name="GoverningAgreements" type="GoverningAgreementsType">
<xs:annotation>
<xs:documentation>
Provides a mechanism for linking to external (likely
human readable) documents in which additional business agreements,
(e.g. liability constraints, obligations, etc) can be placed.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="GoverningAgreementRef" type="GoverningAgreementRefType"/>
<xs:simpleType name="nymType">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="anonymity"/>
<xs:enumeration value="verinymity"/>
<xs:enumeration value="pseudonymity"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="AuthnContextDeclarationBaseType">
<xs:sequence>
<xs:element ref="Identification" minOccurs="0"/>
<xs:element ref="TechnicalProtection" minOccurs="0"/>
<xs:element ref="OperationalProtection" minOccurs="0"/>
<xs:element ref="AuthnMethod" minOccurs="0"/>
<xs:element ref="GoverningAgreements" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="ID" type="xs:ID" use="optional"/>
</xs:complexType>
<xs:complexType name="IdentificationType">
<xs:sequence>
<xs:element ref="PhysicalVerification" minOccurs="0"/>
<xs:element ref="WrittenConsent" minOccurs="0"/>
<xs:element ref="GoverningAgreements" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="nym" type="nymType">
<xs:annotation>
<xs:documentation>
This attribute indicates whether or not the
Identification mechanisms allow the actions of the Principal to be
linked to an actual end user.
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType name="TechnicalProtectionBaseType">
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element ref="PrivateKeyProtection"/>
<xs:element ref="SecretKeyProtection"/>
</xs:choice>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="OperationalProtectionType">
<xs:sequence>
<xs:element ref="SecurityAudit" minOccurs="0"/>
<xs:element ref="DeactivationCallCenter" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="AuthnMethodBaseType">
<xs:sequence>
<xs:element ref="PrincipalAuthenticationMechanism" minOccurs="0"/>
<xs:element ref="Authenticator" minOccurs="0"/>
<xs:element ref="AuthenticatorTransportProtocol" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="GoverningAgreementsType">
<xs:sequence>
<xs:element ref="GoverningAgreementRef" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="GoverningAgreementRefType">
<xs:attribute name="governingAgreementRef" type="xs:anyURI" use="required"/>
</xs:complexType>
<xs:complexType name="PrincipalAuthenticationMechanismType">
<xs:sequence>
<xs:element ref="Password" minOccurs="0"/>
<xs:element ref="RestrictedPassword" minOccurs="0"/>
<xs:element ref="Token" minOccurs="0"/>
<xs:element ref="Smartcard" minOccurs="0"/>
<xs:element ref="ActivationPin" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="preauth" type="xs:integer" use="optional"/>
</xs:complexType>
<xs:group name="AuthenticatorChoiceGroup">
<xs:choice>
<xs:element ref="PreviousSession"/>
<xs:element ref="ResumeSession"/>
<xs:element ref="DigSig"/>
<xs:element ref="Password"/>
<xs:element ref="RestrictedPassword"/>
<xs:element ref="ZeroKnowledge"/>
<xs:element ref="SharedSecretChallengeResponse"/>
<xs:element ref="SharedSecretDynamicPlaintext"/>
<xs:element ref="IPAddress"/>
<xs:element ref="AsymmetricDecryption"/>
<xs:element ref="AsymmetricKeyAgreement"/>
<xs:element ref="SubscriberLineNumber"/>
<xs:element ref="UserSuffix"/>
<xs:element ref="ComplexAuthenticator"/>
</xs:choice>
</xs:group>
<xs:group name="AuthenticatorSequenceGroup">
<xs:sequence>
<xs:element ref="PreviousSession" minOccurs="0"/>
<xs:element ref="ResumeSession" minOccurs="0"/>
<xs:element ref="DigSig" minOccurs="0"/>
<xs:element ref="Password" minOccurs="0"/>
<xs:element ref="RestrictedPassword" minOccurs="0"/>
<xs:element ref="ZeroKnowledge" minOccurs="0"/>
<xs:element ref="SharedSecretChallengeResponse" minOccurs="0"/>
<xs:element ref="SharedSecretDynamicPlaintext" minOccurs="0"/>
<xs:element ref="IPAddress" minOccurs="0"/>
<xs:element ref="AsymmetricDecryption" minOccurs="0"/>
<xs:element ref="AsymmetricKeyAgreement" minOccurs="0"/>
<xs:element ref="SubscriberLineNumber" minOccurs="0"/>
<xs:element ref="UserSuffix" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:group>
<xs:complexType name="AuthenticatorBaseType">
<xs:sequence>
<xs:group ref="AuthenticatorChoiceGroup"/>
<xs:group ref="AuthenticatorSequenceGroup"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ComplexAuthenticatorType">
<xs:sequence>
<xs:group ref="AuthenticatorChoiceGroup"/>
<xs:group ref="AuthenticatorSequenceGroup"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="AuthenticatorTransportProtocolType">
<xs:sequence>
<xs:choice minOccurs="0">
<xs:element ref="HTTP"/>
<xs:element ref="SSL"/>
<xs:element ref="MobileNetworkNoEncryption"/>
<xs:element ref="MobileNetworkRadioEncryption"/>
<xs:element ref="MobileNetworkEndToEndEncryption"/>
<xs:element ref="WTLS"/>
<xs:element ref="IPSec"/>
<xs:element ref="PSTN"/>
<xs:element ref="ISDN"/>
<xs:element ref="ADSL"/>
</xs:choice>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="KeyActivationType">
<xs:sequence>
<xs:element ref="ActivationPin" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="KeySharingType">
<xs:attribute name="sharing" type="xs:boolean" use="required"/>
</xs:complexType>
<xs:complexType name="PrivateKeyProtectionType">
<xs:sequence>
<xs:element ref="KeyActivation" minOccurs="0"/>
<xs:element ref="KeyStorage" minOccurs="0"/>
<xs:element ref="KeySharing" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="PasswordType">
<xs:sequence>
<xs:element ref="Length" minOccurs="0"/>
<xs:element ref="Alphabet" minOccurs="0"/>
<xs:element ref="Generation" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="ExternalVerification" type="xs:anyURI" use="optional"/>
</xs:complexType>
<xs:element name="RestrictedPassword" type="RestrictedPasswordType"/>
<xs:complexType name="RestrictedPasswordType">
<xs:complexContent>
<xs:restriction base="PasswordType">
<xs:sequence>
<xs:element name="Length" type="RestrictedLengthType" minOccurs="1"/>
<xs:element ref="Generation" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
<xs:attribute name="ExternalVerification" type="xs:anyURI" use="optional"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="RestrictedLengthType">
<xs:complexContent>
<xs:restriction base="LengthType">
<xs:attribute name="min" use="required">
<xs:simpleType>
<xs:restriction base="xs:integer">
<xs:minInclusive value="3"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="max" type="xs:integer" use="optional"/>
</xs:restriction>
</xs:complexContent>
</xs:complexType>
<xs:complexType name="ActivationPinType">
<xs:sequence>
<xs:element ref="Length" minOccurs="0"/>
<xs:element ref="Alphabet" minOccurs="0"/>
<xs:element ref="Generation" minOccurs="0"/>
<xs:element ref="ActivationLimit" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Alphabet" type="AlphabetType"/>
<xs:complexType name="AlphabetType">
<xs:attribute name="requiredChars" type="xs:string" use="required"/>
<xs:attribute name="excludedChars" type="xs:string" use="optional"/>
<xs:attribute name="case" type="xs:string" use="optional"/>
</xs:complexType>
<xs:complexType name="TokenType">
<xs:sequence>
<xs:element ref="TimeSyncToken"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:simpleType name="DeviceTypeType">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="hardware"/>
<xs:enumeration value="software"/>
</xs:restriction>
</xs:simpleType>
<xs:simpleType name="booleanType">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="true"/>
<xs:enumeration value="false"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="TimeSyncTokenType">
<xs:attribute name="DeviceType" type="DeviceTypeType" use="required"/>
<xs:attribute name="SeedLength" type="xs:integer" use="required"/>
<xs:attribute name="DeviceInHand" type="booleanType" use="required"/>
</xs:complexType>
<xs:complexType name="ActivationLimitType">
<xs:choice>
<xs:element ref="ActivationLimitDuration"/>
<xs:element ref="ActivationLimitUsages"/>
<xs:element ref="ActivationLimitSession"/>
</xs:choice>
</xs:complexType>
<xs:element name="ActivationLimitDuration" type="ActivationLimitDurationType">
<xs:annotation>
<xs:documentation>
This element indicates that the Key Activation Limit is
defined as a specific duration of time.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ActivationLimitUsages" type="ActivationLimitUsagesType">
<xs:annotation>
<xs:documentation>
This element indicates that the Key Activation Limit is
defined as a number of usages.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:element name="ActivationLimitSession" type="ActivationLimitSessionType">
<xs:annotation>
<xs:documentation>
This element indicates that the Key Activation Limit is
the session.
</xs:documentation>
</xs:annotation>
</xs:element>
<xs:complexType name="ActivationLimitDurationType">
<xs:attribute name="duration" type="xs:duration" use="required"/>
</xs:complexType>
<xs:complexType name="ActivationLimitUsagesType">
<xs:attribute name="number" type="xs:integer" use="required"/>
</xs:complexType>
<xs:complexType name="ActivationLimitSessionType"/>
<xs:complexType name="LengthType">
<xs:attribute name="min" type="xs:integer" use="required"/>
<xs:attribute name="max" type="xs:integer" use="optional"/>
</xs:complexType>
<xs:simpleType name="mediumType">
<xs:restriction base="xs:NMTOKEN">
<xs:enumeration value="memory"/>
<xs:enumeration value="smartcard"/>
<xs:enumeration value="token"/>
<xs:enumeration value="MobileDevice"/>
<xs:enumeration value="MobileAuthCard"/>
</xs:restriction>
</xs:simpleType>
<xs:complexType name="KeyStorageType">
<xs:attribute name="medium" type="mediumType" use="required"/>
</xs:complexType>
<xs:complexType name="SecretKeyProtectionType">
<xs:sequence>
<xs:element ref="KeyActivation" minOccurs="0"/>
<xs:element ref="KeyStorage" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="SecurityAuditType">
<xs:sequence>
<xs:element ref="SwitchAudit" minOccurs="0"/>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:complexType name="ExtensionOnlyType">
<xs:sequence>
<xs:element ref="Extension" minOccurs="0" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
<xs:element name="Extension" type="ExtensionType"/>
<xs:complexType name="ExtensionType">
<xs:sequence>
<xs:any namespace="##other" processContents="lax" maxOccurs="unbounded"/>
</xs:sequence>
</xs:complexType>
</xs:schema>

View File

@ -0,0 +1,337 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
xmlns:xenc="http://www.w3.org/2001/04/xmlenc#"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<import namespace="http://www.w3.org/2000/09/xmldsig#"
schemaLocation="xmldsig-core-schema.xsd"/>
<import namespace="http://www.w3.org/2001/04/xmlenc#"
schemaLocation="xenc-schema.xsd"/>
<import namespace="urn:oasis:names:tc:SAML:2.0:assertion"
schemaLocation="saml-schema-assertion-2.0.xsd"/>
<import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="xml.xsd"/>
<annotation>
<documentation>
Document identifier: saml-schema-metadata-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V2.0 (March, 2005):
Schema for SAML metadata, first published in SAML 2.0.
</documentation>
</annotation>
<simpleType name="entityIDType">
<restriction base="anyURI">
<maxLength value="1024"/>
</restriction>
</simpleType>
<complexType name="localizedNameType">
<simpleContent>
<extension base="string">
<attribute ref="xml:lang" use="required"/>
</extension>
</simpleContent>
</complexType>
<complexType name="localizedURIType">
<simpleContent>
<extension base="anyURI">
<attribute ref="xml:lang" use="required"/>
</extension>
</simpleContent>
</complexType>
<element name="Extensions" type="md:ExtensionsType"/>
<complexType final="#all" name="ExtensionsType">
<sequence>
<any namespace="##other" processContents="lax" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="EndpointType">
<sequence>
<any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Binding" type="anyURI" use="required"/>
<attribute name="Location" type="anyURI" use="required"/>
<attribute name="ResponseLocation" type="anyURI" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<complexType name="IndexedEndpointType">
<complexContent>
<extension base="md:EndpointType">
<attribute name="index" type="unsignedShort" use="required"/>
<attribute name="isDefault" type="boolean" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="EntitiesDescriptor" type="md:EntitiesDescriptorType"/>
<complexType name="EntitiesDescriptorType">
<sequence>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="md:Extensions" minOccurs="0"/>
<choice minOccurs="1" maxOccurs="unbounded">
<element ref="md:EntityDescriptor"/>
<element ref="md:EntitiesDescriptor"/>
</choice>
</sequence>
<attribute name="validUntil" type="dateTime" use="optional"/>
<attribute name="cacheDuration" type="duration" use="optional"/>
<attribute name="ID" type="ID" use="optional"/>
<attribute name="Name" type="string" use="optional"/>
</complexType>
<element name="EntityDescriptor" type="md:EntityDescriptorType"/>
<complexType name="EntityDescriptorType">
<sequence>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="md:Extensions" minOccurs="0"/>
<choice>
<choice maxOccurs="unbounded">
<element ref="md:RoleDescriptor"/>
<element ref="md:IDPSSODescriptor"/>
<element ref="md:SPSSODescriptor"/>
<element ref="md:AuthnAuthorityDescriptor"/>
<element ref="md:AttributeAuthorityDescriptor"/>
<element ref="md:PDPDescriptor"/>
</choice>
<element ref="md:AffiliationDescriptor"/>
</choice>
<element ref="md:Organization" minOccurs="0"/>
<element ref="md:ContactPerson" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:AdditionalMetadataLocation" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="entityID" type="md:entityIDType" use="required"/>
<attribute name="validUntil" type="dateTime" use="optional"/>
<attribute name="cacheDuration" type="duration" use="optional"/>
<attribute name="ID" type="ID" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<element name="Organization" type="md:OrganizationType"/>
<complexType name="OrganizationType">
<sequence>
<element ref="md:Extensions" minOccurs="0"/>
<element ref="md:OrganizationName" maxOccurs="unbounded"/>
<element ref="md:OrganizationDisplayName" maxOccurs="unbounded"/>
<element ref="md:OrganizationURL" maxOccurs="unbounded"/>
</sequence>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<element name="OrganizationName" type="md:localizedNameType"/>
<element name="OrganizationDisplayName" type="md:localizedNameType"/>
<element name="OrganizationURL" type="md:localizedURIType"/>
<element name="ContactPerson" type="md:ContactType"/>
<complexType name="ContactType">
<sequence>
<element ref="md:Extensions" minOccurs="0"/>
<element ref="md:Company" minOccurs="0"/>
<element ref="md:GivenName" minOccurs="0"/>
<element ref="md:SurName" minOccurs="0"/>
<element ref="md:EmailAddress" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:TelephoneNumber" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="contactType" type="md:ContactTypeType" use="required"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<element name="Company" type="string"/>
<element name="GivenName" type="string"/>
<element name="SurName" type="string"/>
<element name="EmailAddress" type="anyURI"/>
<element name="TelephoneNumber" type="string"/>
<simpleType name="ContactTypeType">
<restriction base="string">
<enumeration value="technical"/>
<enumeration value="support"/>
<enumeration value="administrative"/>
<enumeration value="billing"/>
<enumeration value="other"/>
</restriction>
</simpleType>
<element name="AdditionalMetadataLocation" type="md:AdditionalMetadataLocationType"/>
<complexType name="AdditionalMetadataLocationType">
<simpleContent>
<extension base="anyURI">
<attribute name="namespace" type="anyURI" use="required"/>
</extension>
</simpleContent>
</complexType>
<element name="RoleDescriptor" type="md:RoleDescriptorType"/>
<complexType name="RoleDescriptorType" abstract="true">
<sequence>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="md:Extensions" minOccurs="0"/>
<element ref="md:KeyDescriptor" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:Organization" minOccurs="0"/>
<element ref="md:ContactPerson" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="ID" type="ID" use="optional"/>
<attribute name="validUntil" type="dateTime" use="optional"/>
<attribute name="cacheDuration" type="duration" use="optional"/>
<attribute name="protocolSupportEnumeration" type="md:anyURIListType" use="required"/>
<attribute name="errorURL" type="anyURI" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<simpleType name="anyURIListType">
<list itemType="anyURI"/>
</simpleType>
<element name="KeyDescriptor" type="md:KeyDescriptorType"/>
<complexType name="KeyDescriptorType">
<sequence>
<element ref="ds:KeyInfo"/>
<element ref="md:EncryptionMethod" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="use" type="md:KeyTypes" use="optional"/>
</complexType>
<simpleType name="KeyTypes">
<restriction base="string">
<enumeration value="encryption"/>
<enumeration value="signing"/>
</restriction>
</simpleType>
<element name="EncryptionMethod" type="xenc:EncryptionMethodType"/>
<complexType name="SSODescriptorType" abstract="true">
<complexContent>
<extension base="md:RoleDescriptorType">
<sequence>
<element ref="md:ArtifactResolutionService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:SingleLogoutService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:ManageNameIDService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="ArtifactResolutionService" type="md:IndexedEndpointType"/>
<element name="SingleLogoutService" type="md:EndpointType"/>
<element name="ManageNameIDService" type="md:EndpointType"/>
<element name="NameIDFormat" type="anyURI"/>
<element name="IDPSSODescriptor" type="md:IDPSSODescriptorType"/>
<complexType name="IDPSSODescriptorType">
<complexContent>
<extension base="md:SSODescriptorType">
<sequence>
<element ref="md:SingleSignOnService" maxOccurs="unbounded"/>
<element ref="md:NameIDMappingService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:AttributeProfile" minOccurs="0" maxOccurs="unbounded"/>
<element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="WantAuthnRequestsSigned" type="boolean" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="SingleSignOnService" type="md:EndpointType"/>
<element name="NameIDMappingService" type="md:EndpointType"/>
<element name="AssertionIDRequestService" type="md:EndpointType"/>
<element name="AttributeProfile" type="anyURI"/>
<element name="SPSSODescriptor" type="md:SPSSODescriptorType"/>
<complexType name="SPSSODescriptorType">
<complexContent>
<extension base="md:SSODescriptorType">
<sequence>
<element ref="md:AssertionConsumerService" maxOccurs="unbounded"/>
<element ref="md:AttributeConsumingService" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="AuthnRequestsSigned" type="boolean" use="optional"/>
<attribute name="WantAssertionsSigned" type="boolean" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="AssertionConsumerService" type="md:IndexedEndpointType"/>
<element name="AttributeConsumingService" type="md:AttributeConsumingServiceType"/>
<complexType name="AttributeConsumingServiceType">
<sequence>
<element ref="md:ServiceName" maxOccurs="unbounded"/>
<element ref="md:ServiceDescription" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:RequestedAttribute" maxOccurs="unbounded"/>
</sequence>
<attribute name="index" type="unsignedShort" use="required"/>
<attribute name="isDefault" type="boolean" use="optional"/>
</complexType>
<element name="ServiceName" type="md:localizedNameType"/>
<element name="ServiceDescription" type="md:localizedNameType"/>
<element name="RequestedAttribute" type="md:RequestedAttributeType"/>
<complexType name="RequestedAttributeType">
<complexContent>
<extension base="saml:AttributeType">
<attribute name="isRequired" type="boolean" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="AuthnAuthorityDescriptor" type="md:AuthnAuthorityDescriptorType"/>
<complexType name="AuthnAuthorityDescriptorType">
<complexContent>
<extension base="md:RoleDescriptorType">
<sequence>
<element ref="md:AuthnQueryService" maxOccurs="unbounded"/>
<element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="AuthnQueryService" type="md:EndpointType"/>
<element name="PDPDescriptor" type="md:PDPDescriptorType"/>
<complexType name="PDPDescriptorType">
<complexContent>
<extension base="md:RoleDescriptorType">
<sequence>
<element ref="md:AuthzService" maxOccurs="unbounded"/>
<element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="AuthzService" type="md:EndpointType"/>
<element name="AttributeAuthorityDescriptor" type="md:AttributeAuthorityDescriptorType"/>
<complexType name="AttributeAuthorityDescriptorType">
<complexContent>
<extension base="md:RoleDescriptorType">
<sequence>
<element ref="md:AttributeService" maxOccurs="unbounded"/>
<element ref="md:AssertionIDRequestService" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:NameIDFormat" minOccurs="0" maxOccurs="unbounded"/>
<element ref="md:AttributeProfile" minOccurs="0" maxOccurs="unbounded"/>
<element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="AttributeService" type="md:EndpointType"/>
<element name="AffiliationDescriptor" type="md:AffiliationDescriptorType"/>
<complexType name="AffiliationDescriptorType">
<sequence>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="md:Extensions" minOccurs="0"/>
<element ref="md:AffiliateMember" maxOccurs="unbounded"/>
<element ref="md:KeyDescriptor" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="affiliationOwnerID" type="md:entityIDType" use="required"/>
<attribute name="validUntil" type="dateTime" use="optional"/>
<attribute name="cacheDuration" type="duration" use="optional"/>
<attribute name="ID" type="ID" use="optional"/>
<anyAttribute namespace="##other" processContents="lax"/>
</complexType>
<element name="AffiliateMember" type="md:entityIDType"/>
</schema>

View File

@ -0,0 +1,302 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:samlp="urn:oasis:names:tc:SAML:2.0:protocol"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<import namespace="urn:oasis:names:tc:SAML:2.0:assertion"
schemaLocation="saml-schema-assertion-2.0.xsd"/>
<import namespace="http://www.w3.org/2000/09/xmldsig#"
schemaLocation="xmldsig-core-schema.xsd"/>
<annotation>
<documentation>
Document identifier: saml-schema-protocol-2.0
Location: http://docs.oasis-open.org/security/saml/v2.0/
Revision history:
V1.0 (November, 2002):
Initial Standard Schema.
V1.1 (September, 2003):
Updates within the same V1.0 namespace.
V2.0 (March, 2005):
New protocol schema based in a SAML V2.0 namespace.
</documentation>
</annotation>
<complexType name="RequestAbstractType" abstract="true">
<sequence>
<element ref="saml:Issuer" minOccurs="0"/>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="samlp:Extensions" minOccurs="0"/>
</sequence>
<attribute name="ID" type="ID" use="required"/>
<attribute name="Version" type="string" use="required"/>
<attribute name="IssueInstant" type="dateTime" use="required"/>
<attribute name="Destination" type="anyURI" use="optional"/>
<attribute name="Consent" type="anyURI" use="optional"/>
</complexType>
<element name="Extensions" type="samlp:ExtensionsType"/>
<complexType name="ExtensionsType">
<sequence>
<any namespace="##other" processContents="lax" maxOccurs="unbounded"/>
</sequence>
</complexType>
<complexType name="StatusResponseType">
<sequence>
<element ref="saml:Issuer" minOccurs="0"/>
<element ref="ds:Signature" minOccurs="0"/>
<element ref="samlp:Extensions" minOccurs="0"/>
<element ref="samlp:Status"/>
</sequence>
<attribute name="ID" type="ID" use="required"/>
<attribute name="InResponseTo" type="NCName" use="optional"/>
<attribute name="Version" type="string" use="required"/>
<attribute name="IssueInstant" type="dateTime" use="required"/>
<attribute name="Destination" type="anyURI" use="optional"/>
<attribute name="Consent" type="anyURI" use="optional"/>
</complexType>
<element name="Status" type="samlp:StatusType"/>
<complexType name="StatusType">
<sequence>
<element ref="samlp:StatusCode"/>
<element ref="samlp:StatusMessage" minOccurs="0"/>
<element ref="samlp:StatusDetail" minOccurs="0"/>
</sequence>
</complexType>
<element name="StatusCode" type="samlp:StatusCodeType"/>
<complexType name="StatusCodeType">
<sequence>
<element ref="samlp:StatusCode" minOccurs="0"/>
</sequence>
<attribute name="Value" type="anyURI" use="required"/>
</complexType>
<element name="StatusMessage" type="string"/>
<element name="StatusDetail" type="samlp:StatusDetailType"/>
<complexType name="StatusDetailType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="AssertionIDRequest" type="samlp:AssertionIDRequestType"/>
<complexType name="AssertionIDRequestType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<element ref="saml:AssertionIDRef" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="SubjectQuery" type="samlp:SubjectQueryAbstractType"/>
<complexType name="SubjectQueryAbstractType" abstract="true">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<element ref="saml:Subject"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="AuthnQuery" type="samlp:AuthnQueryType"/>
<complexType name="AuthnQueryType">
<complexContent>
<extension base="samlp:SubjectQueryAbstractType">
<sequence>
<element ref="samlp:RequestedAuthnContext" minOccurs="0"/>
</sequence>
<attribute name="SessionIndex" type="string" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="RequestedAuthnContext" type="samlp:RequestedAuthnContextType"/>
<complexType name="RequestedAuthnContextType">
<choice>
<element ref="saml:AuthnContextClassRef" maxOccurs="unbounded"/>
<element ref="saml:AuthnContextDeclRef" maxOccurs="unbounded"/>
</choice>
<attribute name="Comparison" type="samlp:AuthnContextComparisonType" use="optional"/>
</complexType>
<simpleType name="AuthnContextComparisonType">
<restriction base="string">
<enumeration value="exact"/>
<enumeration value="minimum"/>
<enumeration value="maximum"/>
<enumeration value="better"/>
</restriction>
</simpleType>
<element name="AttributeQuery" type="samlp:AttributeQueryType"/>
<complexType name="AttributeQueryType">
<complexContent>
<extension base="samlp:SubjectQueryAbstractType">
<sequence>
<element ref="saml:Attribute" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="AuthzDecisionQuery" type="samlp:AuthzDecisionQueryType"/>
<complexType name="AuthzDecisionQueryType">
<complexContent>
<extension base="samlp:SubjectQueryAbstractType">
<sequence>
<element ref="saml:Action" maxOccurs="unbounded"/>
<element ref="saml:Evidence" minOccurs="0"/>
</sequence>
<attribute name="Resource" type="anyURI" use="required"/>
</extension>
</complexContent>
</complexType>
<element name="AuthnRequest" type="samlp:AuthnRequestType"/>
<complexType name="AuthnRequestType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<element ref="saml:Subject" minOccurs="0"/>
<element ref="samlp:NameIDPolicy" minOccurs="0"/>
<element ref="saml:Conditions" minOccurs="0"/>
<element ref="samlp:RequestedAuthnContext" minOccurs="0"/>
<element ref="samlp:Scoping" minOccurs="0"/>
</sequence>
<attribute name="ForceAuthn" type="boolean" use="optional"/>
<attribute name="IsPassive" type="boolean" use="optional"/>
<attribute name="ProtocolBinding" type="anyURI" use="optional"/>
<attribute name="AssertionConsumerServiceIndex" type="unsignedShort" use="optional"/>
<attribute name="AssertionConsumerServiceURL" type="anyURI" use="optional"/>
<attribute name="AttributeConsumingServiceIndex" type="unsignedShort" use="optional"/>
<attribute name="ProviderName" type="string" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="NameIDPolicy" type="samlp:NameIDPolicyType"/>
<complexType name="NameIDPolicyType">
<attribute name="Format" type="anyURI" use="optional"/>
<attribute name="SPNameQualifier" type="string" use="optional"/>
<attribute name="AllowCreate" type="boolean" use="optional"/>
</complexType>
<element name="Scoping" type="samlp:ScopingType"/>
<complexType name="ScopingType">
<sequence>
<element ref="samlp:IDPList" minOccurs="0"/>
<element ref="samlp:RequesterID" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="ProxyCount" type="nonNegativeInteger" use="optional"/>
</complexType>
<element name="RequesterID" type="anyURI"/>
<element name="IDPList" type="samlp:IDPListType"/>
<complexType name="IDPListType">
<sequence>
<element ref="samlp:IDPEntry" maxOccurs="unbounded"/>
<element ref="samlp:GetComplete" minOccurs="0"/>
</sequence>
</complexType>
<element name="IDPEntry" type="samlp:IDPEntryType"/>
<complexType name="IDPEntryType">
<attribute name="ProviderID" type="anyURI" use="required"/>
<attribute name="Name" type="string" use="optional"/>
<attribute name="Loc" type="anyURI" use="optional"/>
</complexType>
<element name="GetComplete" type="anyURI"/>
<element name="Response" type="samlp:ResponseType"/>
<complexType name="ResponseType">
<complexContent>
<extension base="samlp:StatusResponseType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="saml:Assertion"/>
<element ref="saml:EncryptedAssertion"/>
</choice>
</extension>
</complexContent>
</complexType>
<element name="ArtifactResolve" type="samlp:ArtifactResolveType"/>
<complexType name="ArtifactResolveType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<element ref="samlp:Artifact"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="Artifact" type="string"/>
<element name="ArtifactResponse" type="samlp:ArtifactResponseType"/>
<complexType name="ArtifactResponseType">
<complexContent>
<extension base="samlp:StatusResponseType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="ManageNameIDRequest" type="samlp:ManageNameIDRequestType"/>
<complexType name="ManageNameIDRequestType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<choice>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
<choice>
<element ref="samlp:NewID"/>
<element ref="samlp:NewEncryptedID"/>
<element ref="samlp:Terminate"/>
</choice>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="NewID" type="string"/>
<element name="NewEncryptedID" type="saml:EncryptedElementType"/>
<element name="Terminate" type="samlp:TerminateType"/>
<complexType name="TerminateType"/>
<element name="ManageNameIDResponse" type="samlp:StatusResponseType"/>
<element name="LogoutRequest" type="samlp:LogoutRequestType"/>
<complexType name="LogoutRequestType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<choice>
<element ref="saml:BaseID"/>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
<element ref="samlp:SessionIndex" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Reason" type="string" use="optional"/>
<attribute name="NotOnOrAfter" type="dateTime" use="optional"/>
</extension>
</complexContent>
</complexType>
<element name="SessionIndex" type="string"/>
<element name="LogoutResponse" type="samlp:StatusResponseType"/>
<element name="NameIDMappingRequest" type="samlp:NameIDMappingRequestType"/>
<complexType name="NameIDMappingRequestType">
<complexContent>
<extension base="samlp:RequestAbstractType">
<sequence>
<choice>
<element ref="saml:BaseID"/>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
<element ref="samlp:NameIDPolicy"/>
</sequence>
</extension>
</complexContent>
</complexType>
<element name="NameIDMappingResponse" type="samlp:NameIDMappingResponseType"/>
<complexType name="NameIDMappingResponseType">
<complexContent>
<extension base="samlp:StatusResponseType">
<choice>
<element ref="saml:NameID"/>
<element ref="saml:EncryptedID"/>
</choice>
</extension>
</complexContent>
</complexType>
</schema>

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:attribute"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"
xmlns:mdattr="urn:oasis:names:tc:SAML:metadata:attribute"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<annotation>
<documentation>
Document title: SAML V2.0 Metadata Extention for Entity Attributes Schema
Document identifier: sstc-metadata-attr.xsd
Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security
Revision history:
V1.0 (November 2008):
Initial version.
</documentation>
</annotation>
<import namespace="urn:oasis:names:tc:SAML:2.0:assertion"
schemaLocation="saml-schema-assertion-2.0.xsd"/>
<element name="EntityAttributes" type="mdattr:EntityAttributesType"/>
<complexType name="EntityAttributesType">
<choice maxOccurs="unbounded">
<element ref="saml:Attribute"/>
<element ref="saml:Assertion"/>
</choice>
</complexType>
</schema>

View File

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:attribute:ext"
xmlns="http://www.w3.org/2001/XMLSchema"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="2.0">
<annotation>
<documentation>
Document title: SAML V2.0 Attribute Extension Schema
Document identifier: sstc-saml-attribute-ext.xsd
Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security
Revision history:
V1.0 (October 2008):
Initial version.
</documentation>
</annotation>
<attribute name="OriginalIssuer" type="anyURI"/>
<attribute name="LastModified" type="dateTime"/>
</schema>

View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:algsupport"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:alg="urn:oasis:names:tc:SAML:metadata:algsupport"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="1.0">
<annotation>
<documentation>
Document title: Metadata Extension Schema for SAML V2.0 Metadata Profile for Algorithm Support Version 1.0
Document identifier: sstc-saml-metadata-algsupport.xsd
Location: http://docs.oasis-open.org/security/saml/Post2.0/
Revision history:
V1.0 (June 2010):
Initial version.
</documentation>
</annotation>
<element name="DigestMethod" type="alg:DigestMethodType"/>
<complexType name="DigestMethodType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="SigningMethod" type="alg:SigningMethodType"/>
<complexType name="SigningMethodType">
<sequence>
<any namespace="##any" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
<attribute name="MinKeySize" type="positiveInteger"/>
<attribute name="MaxKeySize" type="positiveInteger"/>
</complexType>
</schema>

View File

@ -0,0 +1,89 @@
<?xml version="1.0" encoding="UTF-8"?>
<schema
targetNamespace="urn:oasis:names:tc:SAML:metadata:ui"
xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
xmlns:mdui="urn:oasis:names:tc:SAML:metadata:ui"
elementFormDefault="unqualified"
attributeFormDefault="unqualified"
blockDefault="substitution"
version="1.0">
<annotation>
<documentation>
Document title: Metadata Extension Schema for SAML V2.0 Metadata Extensions for Login and Discovery User Interface Version 1.0
Document identifier: sstc-saml-metadata-ui-v1.0.xsd
Location: http://docs.oasis-open.org/security/saml/Post2.0/
Revision history:
16 November 2010:
Added Keywords element/type.
01 November 2010
Changed filename.
September 2010:
Initial version.
</documentation>
</annotation>
<import namespace="urn:oasis:names:tc:SAML:2.0:metadata"
schemaLocation="saml-schema-metadata-2.0.xsd"/>
<import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="xml.xsd"/>
<element name="UIInfo" type="mdui:UIInfoType" />
<complexType name="UIInfoType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="mdui:DisplayName"/>
<element ref="mdui:Description"/>
<element ref="mdui:Keywords"/>
<element ref="mdui:Logo"/>
<element ref="mdui:InformationURL"/>
<element ref="mdui:PrivacyStatementURL"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="DisplayName" type="md:localizedNameType"/>
<element name="Description" type="md:localizedNameType"/>
<element name="InformationURL" type="md:localizedURIType"/>
<element name="PrivacyStatementURL" type="md:localizedURIType"/>
<element name="Keywords" type="mdui:KeywordsType"/>
<complexType name="KeywordsType">
<simpleContent>
<extension base="mdui:listOfStrings">
<attribute ref="xml:lang" use="required"/>
</extension>
</simpleContent>
</complexType>
<simpleType name="listOfStrings">
<list itemType="string"/>
</simpleType>
<element name="Logo" type="mdui:LogoType"/>
<complexType name="LogoType">
<simpleContent>
<extension base="anyURI">
<attribute name="height" type="positiveInteger" use="required"/>
<attribute name="width" type="positiveInteger" use="required"/>
<attribute ref="xml:lang"/>
</extension>
</simpleContent>
</complexType>
<element name="DiscoHints" type="mdui:DiscoHintsType"/>
<complexType name="DiscoHintsType">
<choice minOccurs="0" maxOccurs="unbounded">
<element ref="mdui:IPHint"/>
<element ref="mdui:DomainHint"/>
<element ref="mdui:GeolocationHint"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="IPHint" type="string"/>
<element name="DomainHint" type="string"/>
<element name="GeolocationHint" type="anyURI"/>
</schema>

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<schema xmlns='http://www.w3.org/2001/XMLSchema' version='1.0'
xmlns:xenc='http://www.w3.org/2001/04/xmlenc#'
xmlns:ds='http://www.w3.org/2000/09/xmldsig#'
targetNamespace='http://www.w3.org/2001/04/xmlenc#'
elementFormDefault='qualified'>
<import namespace='http://www.w3.org/2000/09/xmldsig#'
schemaLocation='xmldsig-core-schema.xsd'/>
<complexType name='EncryptedType' abstract='true'>
<sequence>
<element name='EncryptionMethod' type='xenc:EncryptionMethodType'
minOccurs='0'/>
<element ref='ds:KeyInfo' minOccurs='0'/>
<element ref='xenc:CipherData'/>
<element ref='xenc:EncryptionProperties' minOccurs='0'/>
</sequence>
<attribute name='Id' type='ID' use='optional'/>
<attribute name='Type' type='anyURI' use='optional'/>
<attribute name='MimeType' type='string' use='optional'/>
<attribute name='Encoding' type='anyURI' use='optional'/>
</complexType>
<complexType name='EncryptionMethodType' mixed='true'>
<sequence>
<element name='KeySize' minOccurs='0' type='xenc:KeySizeType'/>
<element name='OAEPparams' minOccurs='0' type='base64Binary'/>
<any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</sequence>
<attribute name='Algorithm' type='anyURI' use='required'/>
</complexType>
<simpleType name='KeySizeType'>
<restriction base="integer"/>
</simpleType>
<element name='CipherData' type='xenc:CipherDataType'/>
<complexType name='CipherDataType'>
<choice>
<element name='CipherValue' type='base64Binary'/>
<element ref='xenc:CipherReference'/>
</choice>
</complexType>
<element name='CipherReference' type='xenc:CipherReferenceType'/>
<complexType name='CipherReferenceType'>
<choice>
<element name='Transforms' type='xenc:TransformsType' minOccurs='0'/>
</choice>
<attribute name='URI' type='anyURI' use='required'/>
</complexType>
<complexType name='TransformsType'>
<sequence>
<element ref='ds:Transform' maxOccurs='unbounded'/>
</sequence>
</complexType>
<element name='EncryptedData' type='xenc:EncryptedDataType'/>
<complexType name='EncryptedDataType'>
<complexContent>
<extension base='xenc:EncryptedType'>
</extension>
</complexContent>
</complexType>
<!-- Children of ds:KeyInfo -->
<element name='EncryptedKey' type='xenc:EncryptedKeyType'/>
<complexType name='EncryptedKeyType'>
<complexContent>
<extension base='xenc:EncryptedType'>
<sequence>
<element ref='xenc:ReferenceList' minOccurs='0'/>
<element name='CarriedKeyName' type='string' minOccurs='0'/>
</sequence>
<attribute name='Recipient' type='string'
use='optional'/>
</extension>
</complexContent>
</complexType>
<element name="AgreementMethod" type="xenc:AgreementMethodType"/>
<complexType name="AgreementMethodType" mixed="true">
<sequence>
<element name="KA-Nonce" minOccurs="0" type="base64Binary"/>
<!-- <element ref="ds:DigestMethod" minOccurs="0"/> -->
<any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<element name="OriginatorKeyInfo" minOccurs="0" type="ds:KeyInfoType"/>
<element name="RecipientKeyInfo" minOccurs="0" type="ds:KeyInfoType"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- End Children of ds:KeyInfo -->
<element name='ReferenceList'>
<complexType>
<choice minOccurs='1' maxOccurs='unbounded'>
<element name='DataReference' type='xenc:ReferenceType'/>
<element name='KeyReference' type='xenc:ReferenceType'/>
</choice>
</complexType>
</element>
<complexType name='ReferenceType'>
<sequence>
<any namespace='##other' minOccurs='0' maxOccurs='unbounded'/>
</sequence>
<attribute name='URI' type='anyURI' use='required'/>
</complexType>
<element name='EncryptionProperties' type='xenc:EncryptionPropertiesType'/>
<complexType name='EncryptionPropertiesType'>
<sequence>
<element ref='xenc:EncryptionProperty' maxOccurs='unbounded'/>
</sequence>
<attribute name='Id' type='ID' use='optional'/>
</complexType>
<element name='EncryptionProperty' type='xenc:EncryptionPropertyType'/>
<complexType name='EncryptionPropertyType' mixed='true'>
<choice maxOccurs='unbounded'>
<any namespace='##other' processContents='lax'/>
</choice>
<attribute name='Target' type='anyURI' use='optional'/>
<attribute name='Id' type='ID' use='optional'/>
<anyAttribute namespace="http://www.w3.org/XML/1998/namespace"/>
</complexType>
</schema>

View File

@ -0,0 +1,287 @@
<?xml version='1.0'?>
<?xml-stylesheet href="../2008/09/xsd.xsl" type="text/xsl"?>
<xs:schema targetNamespace="http://www.w3.org/XML/1998/namespace"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns ="http://www.w3.org/1999/xhtml"
xml:lang="en">
<xs:annotation>
<xs:documentation>
<div>
<h1>About the XML namespace</h1>
<div class="bodytext">
<p>
This schema document describes the XML namespace, in a form
suitable for import by other schema documents.
</p>
<p>
See <a href="http://www.w3.org/XML/1998/namespace.html">
http://www.w3.org/XML/1998/namespace.html</a> and
<a href="http://www.w3.org/TR/REC-xml">
http://www.w3.org/TR/REC-xml</a> for information
about this namespace.
</p>
<p>
Note that local names in this namespace are intended to be
defined only by the World Wide Web Consortium or its subgroups.
The names currently defined in this namespace are listed below.
They should not be used with conflicting semantics by any Working
Group, specification, or document instance.
</p>
<p>
See further below in this document for more information about <a
href="#usage">how to refer to this schema document from your own
XSD schema documents</a> and about <a href="#nsversioning">the
namespace-versioning policy governing this schema document</a>.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:attribute name="lang">
<xs:annotation>
<xs:documentation>
<div>
<h3>lang (as an attribute name)</h3>
<p>
denotes an attribute whose value
is a language code for the natural language of the content of
any element; its value is inherited. This name is reserved
by virtue of its definition in the XML specification.</p>
</div>
<div>
<h4>Notes</h4>
<p>
Attempting to install the relevant ISO 2- and 3-letter
codes as the enumerated possible values is probably never
going to be a realistic possibility.
</p>
<p>
See BCP 47 at <a href="http://www.rfc-editor.org/rfc/bcp/bcp47.txt">
http://www.rfc-editor.org/rfc/bcp/bcp47.txt</a>
and the IANA language subtag registry at
<a href="http://www.iana.org/assignments/language-subtag-registry">
http://www.iana.org/assignments/language-subtag-registry</a>
for further information.
</p>
<p>
The union allows for the 'un-declaration' of xml:lang with
the empty string.
</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:union memberTypes="xs:language">
<xs:simpleType>
<xs:restriction base="xs:string">
<xs:enumeration value=""/>
</xs:restriction>
</xs:simpleType>
</xs:union>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="space">
<xs:annotation>
<xs:documentation>
<div>
<h3>space (as an attribute name)</h3>
<p>
denotes an attribute whose
value is a keyword indicating what whitespace processing
discipline is intended for the content of the element; its
value is inherited. This name is reserved by virtue of its
definition in the XML specification.</p>
</div>
</xs:documentation>
</xs:annotation>
<xs:simpleType>
<xs:restriction base="xs:NCName">
<xs:enumeration value="default"/>
<xs:enumeration value="preserve"/>
</xs:restriction>
</xs:simpleType>
</xs:attribute>
<xs:attribute name="base" type="xs:anyURI"> <xs:annotation>
<xs:documentation>
<div>
<h3>base (as an attribute name)</h3>
<p>
denotes an attribute whose value
provides a URI to be used as the base for interpreting any
relative URIs in the scope of the element on which it
appears; its value is inherited. This name is reserved
by virtue of its definition in the XML Base specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xmlbase/">http://www.w3.org/TR/xmlbase/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="id" type="xs:ID">
<xs:annotation>
<xs:documentation>
<div>
<h3>id (as an attribute name)</h3>
<p>
denotes an attribute whose value
should be interpreted as if declared to be of type ID.
This name is reserved by virtue of its definition in the
xml:id specification.</p>
<p>
See <a
href="http://www.w3.org/TR/xml-id/">http://www.w3.org/TR/xml-id/</a>
for information about this attribute.
</p>
</div>
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attributeGroup name="specialAttrs">
<xs:attribute ref="xml:base"/>
<xs:attribute ref="xml:lang"/>
<xs:attribute ref="xml:space"/>
<xs:attribute ref="xml:id"/>
</xs:attributeGroup>
<xs:annotation>
<xs:documentation>
<div>
<h3>Father (in any context at all)</h3>
<div class="bodytext">
<p>
denotes Jon Bosak, the chair of
the original XML Working Group. This name is reserved by
the following decision of the W3C XML Plenary and
XML Coordination groups:
</p>
<blockquote>
<p>
In appreciation for his vision, leadership and
dedication the W3C XML Plenary on this 10th day of
February, 2000, reserves for Jon Bosak in perpetuity
the XML name "xml:Father".
</p>
</blockquote>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div xml:id="usage" id="usage">
<h2><a name="usage">About this schema document</a></h2>
<div class="bodytext">
<p>
This schema defines attributes and an attribute group suitable
for use by schemas wishing to allow <code>xml:base</code>,
<code>xml:lang</code>, <code>xml:space</code> or
<code>xml:id</code> attributes on elements they define.
</p>
<p>
To enable this, such a schema must import this schema for
the XML namespace, e.g. as follows:
</p>
<pre>
&lt;schema . . .>
. . .
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2001/xml.xsd"/>
</pre>
<p>
or
</p>
<pre>
&lt;import namespace="http://www.w3.org/XML/1998/namespace"
schemaLocation="http://www.w3.org/2009/01/xml.xsd"/>
</pre>
<p>
Subsequently, qualified reference to any of the attributes or the
group defined below will have the desired effect, e.g.
</p>
<pre>
&lt;type . . .>
. . .
&lt;attributeGroup ref="xml:specialAttrs"/>
</pre>
<p>
will define a type which will schema-validate an instance element
with any of those attributes.
</p>
</div>
</div>
</xs:documentation>
</xs:annotation>
<xs:annotation>
<xs:documentation>
<div id="nsversioning" xml:id="nsversioning">
<h2><a name="nsversioning">Versioning policy for this schema document</a></h2>
<div class="bodytext">
<p>
In keeping with the XML Schema WG's standard versioning
policy, this schema document will persist at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a>.
</p>
<p>
At the date of issue it can also be found at
<a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd</a>.
</p>
<p>
The schema document at that URI may however change in the future,
in order to remain compatible with the latest version of XML
Schema itself, or with the XML namespace itself. In other words,
if the XML Schema or XML namespaces change, the version of this
document at <a href="http://www.w3.org/2001/xml.xsd">
http://www.w3.org/2001/xml.xsd
</a>
will change accordingly; the version at
<a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd
</a>
will not change.
</p>
<p>
Previous dated (and unchanging) versions of this schema
document are at:
</p>
<ul>
<li><a href="http://www.w3.org/2009/01/xml.xsd">
http://www.w3.org/2009/01/xml.xsd</a></li>
<li><a href="http://www.w3.org/2007/08/xml.xsd">
http://www.w3.org/2007/08/xml.xsd</a></li>
<li><a href="http://www.w3.org/2004/10/xml.xsd">
http://www.w3.org/2004/10/xml.xsd</a></li>
<li><a href="http://www.w3.org/2001/03/xml.xsd">
http://www.w3.org/2001/03/xml.xsd</a></li>
</ul>
</div>
</div>
</xs:documentation>
</xs:annotation>
</xs:schema>

View File

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Schema for XML Signatures
http://www.w3.org/2000/09/xmldsig#
$Revision: 1.1 $ on $Date: 2002/02/08 20:32:26 $ by $Author: reagle $
Copyright 2001 The Internet Society and W3C (Massachusetts Institute
of Technology, Institut National de Recherche en Informatique et en
Automatique, Keio University). All Rights Reserved.
http://www.w3.org/Consortium/Legal/
This document is governed by the W3C Software License [1] as described
in the FAQ [2].
[1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
[2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
-->
<schema xmlns="http://www.w3.org/2001/XMLSchema"
xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
targetNamespace="http://www.w3.org/2000/09/xmldsig#"
version="0.1" elementFormDefault="qualified">
<!-- Basic Types Defined for Signatures -->
<simpleType name="CryptoBinary">
<restriction base="base64Binary">
</restriction>
</simpleType>
<!-- Start Signature -->
<element name="Signature" type="ds:SignatureType"/>
<complexType name="SignatureType">
<sequence>
<element ref="ds:SignedInfo"/>
<element ref="ds:SignatureValue"/>
<element ref="ds:KeyInfo" minOccurs="0"/>
<element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureValue" type="ds:SignatureValueType"/>
<complexType name="SignatureValueType">
<simpleContent>
<extension base="base64Binary">
<attribute name="Id" type="ID" use="optional"/>
</extension>
</simpleContent>
</complexType>
<!-- Start SignedInfo -->
<element name="SignedInfo" type="ds:SignedInfoType"/>
<complexType name="SignedInfoType">
<sequence>
<element ref="ds:CanonicalizationMethod"/>
<element ref="ds:SignatureMethod"/>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
<complexType name="CanonicalizationMethodType" mixed="true">
<sequence>
<any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="SignatureMethod" type="ds:SignatureMethodType"/>
<complexType name="SignatureMethodType" mixed="true">
<sequence>
<element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
<any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
<!-- (0,unbounded) elements from (1,1) external namespace -->
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- Start Reference -->
<element name="Reference" type="ds:ReferenceType"/>
<complexType name="ReferenceType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
<element ref="ds:DigestMethod"/>
<element ref="ds:DigestValue"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="URI" type="anyURI" use="optional"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<element name="Transforms" type="ds:TransformsType"/>
<complexType name="TransformsType">
<sequence>
<element ref="ds:Transform" maxOccurs="unbounded"/>
</sequence>
</complexType>
<element name="Transform" type="ds:TransformType"/>
<complexType name="TransformType" mixed="true">
<choice minOccurs="0" maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
<element name="XPath" type="string"/>
</choice>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<!-- End Reference -->
<element name="DigestMethod" type="ds:DigestMethodType"/>
<complexType name="DigestMethodType" mixed="true">
<sequence>
<any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
</sequence>
<attribute name="Algorithm" type="anyURI" use="required"/>
</complexType>
<element name="DigestValue" type="ds:DigestValueType"/>
<simpleType name="DigestValueType">
<restriction base="base64Binary"/>
</simpleType>
<!-- End SignedInfo -->
<!-- Start KeyInfo -->
<element name="KeyInfo" type="ds:KeyInfoType"/>
<complexType name="KeyInfoType" mixed="true">
<choice maxOccurs="unbounded">
<element ref="ds:KeyName"/>
<element ref="ds:KeyValue"/>
<element ref="ds:RetrievalMethod"/>
<element ref="ds:X509Data"/>
<element ref="ds:PGPData"/>
<element ref="ds:SPKIData"/>
<element ref="ds:MgmtData"/>
<any processContents="lax" namespace="##other"/>
<!-- (1,1) elements from (0,unbounded) namespaces -->
</choice>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="KeyName" type="string"/>
<element name="MgmtData" type="string"/>
<element name="KeyValue" type="ds:KeyValueType"/>
<complexType name="KeyValueType" mixed="true">
<choice>
<element ref="ds:DSAKeyValue"/>
<element ref="ds:RSAKeyValue"/>
<any namespace="##other" processContents="lax"/>
</choice>
</complexType>
<element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
<complexType name="RetrievalMethodType">
<sequence>
<element ref="ds:Transforms" minOccurs="0"/>
</sequence>
<attribute name="URI" type="anyURI"/>
<attribute name="Type" type="anyURI" use="optional"/>
</complexType>
<!-- Start X509Data -->
<element name="X509Data" type="ds:X509DataType"/>
<complexType name="X509DataType">
<sequence maxOccurs="unbounded">
<choice>
<element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
<element name="X509SKI" type="base64Binary"/>
<element name="X509SubjectName" type="string"/>
<element name="X509Certificate" type="base64Binary"/>
<element name="X509CRL" type="base64Binary"/>
<any namespace="##other" processContents="lax"/>
</choice>
</sequence>
</complexType>
<complexType name="X509IssuerSerialType">
<sequence>
<element name="X509IssuerName" type="string"/>
<element name="X509SerialNumber" type="string"/>
</sequence>
</complexType>
<!-- End X509Data -->
<!-- Begin PGPData -->
<element name="PGPData" type="ds:PGPDataType"/>
<complexType name="PGPDataType">
<choice>
<sequence>
<element name="PGPKeyID" type="base64Binary"/>
<element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
<sequence>
<element name="PGPKeyPacket" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"
maxOccurs="unbounded"/>
</sequence>
</choice>
</complexType>
<!-- End PGPData -->
<!-- Begin SPKIData -->
<element name="SPKIData" type="ds:SPKIDataType"/>
<complexType name="SPKIDataType">
<sequence maxOccurs="unbounded">
<element name="SPKISexp" type="base64Binary"/>
<any namespace="##other" processContents="lax" minOccurs="0"/>
</sequence>
</complexType>
<!-- End SPKIData -->
<!-- End KeyInfo -->
<!-- Start Object (Manifest, SignatureProperty) -->
<element name="Object" type="ds:ObjectType"/>
<complexType name="ObjectType" mixed="true">
<sequence minOccurs="0" maxOccurs="unbounded">
<any namespace="##any" processContents="lax"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
<attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
<attribute name="Encoding" type="anyURI" use="optional"/>
</complexType>
<element name="Manifest" type="ds:ManifestType"/>
<complexType name="ManifestType">
<sequence>
<element ref="ds:Reference" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
<complexType name="SignaturePropertiesType">
<sequence>
<element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
</sequence>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<element name="SignatureProperty" type="ds:SignaturePropertyType"/>
<complexType name="SignaturePropertyType" mixed="true">
<choice maxOccurs="unbounded">
<any namespace="##other" processContents="lax"/>
<!-- (1,1) elements from (1,unbounded) namespaces -->
</choice>
<attribute name="Target" type="anyURI" use="required"/>
<attribute name="Id" type="ID" use="optional"/>
</complexType>
<!-- End Object (Manifest, SignatureProperty) -->
<!-- Start Algorithm Parameters -->
<simpleType name="HMACOutputLengthType">
<restriction base="integer"/>
</simpleType>
<!-- Start KeyValue Element-types -->
<element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
<complexType name="DSAKeyValueType">
<sequence>
<sequence minOccurs="0">
<element name="P" type="ds:CryptoBinary"/>
<element name="Q" type="ds:CryptoBinary"/>
</sequence>
<element name="G" type="ds:CryptoBinary" minOccurs="0"/>
<element name="Y" type="ds:CryptoBinary"/>
<element name="J" type="ds:CryptoBinary" minOccurs="0"/>
<sequence minOccurs="0">
<element name="Seed" type="ds:CryptoBinary"/>
<element name="PgenCounter" type="ds:CryptoBinary"/>
</sequence>
</sequence>
</complexType>
<element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
<complexType name="RSAKeyValueType">
<sequence>
<element name="Modulus" type="ds:CryptoBinary"/>
<element name="Exponent" type="ds:CryptoBinary"/>
</sequence>
</complexType>
<!-- End KeyValue Element-types -->
<!-- End Signature -->
</schema>

View File

@ -0,0 +1,421 @@
# The contents of this file are subject to the terms
# of the Common Development and Distribution License
# (the License). You may not use this file except in
# compliance with the License.
#
# You can obtain a copy of the License at
# https://opensso.dev.java.net/public/CDDLv1.0.html or
# opensso/legal/CDDLv1.0.txt
# See the License for the specific language governing
# permission and limitations under the License.
#
# When distributing Covered Code, include this CDDL
# Header Notice in each file and include the License file
# at opensso/legal/CDDLv1.0.txt.
# If applicable, add the following below the CDDL Header,
# with the fields enclosed by brackets [] replaced by
# your own identifying information:
# "Portions Copyrighted [year] [name of copyright owner]"
#
# $Id: xml_sec.rb,v 1.6 2007/10/24 00:28:41 todddd Exp $
#
# Copyright 2007 Sun Microsystems Inc. All Rights Reserved
# Portions Copyrighted 2007 Todd W Saxton.
require 'rubygems'
require "rexml/document"
require "rexml/xpath"
require "openssl"
require 'nokogiri'
require "digest/sha1"
require "digest/sha2"
require "onelogin/ruby-saml/utils"
require "onelogin/ruby-saml/error_handling"
module XMLSecurity
class BaseDocument < REXML::Document
REXML::Document::entity_expansion_limit = 0
C14N = "http://www.w3.org/2001/10/xml-exc-c14n#"
DSIG = "http://www.w3.org/2000/09/xmldsig#"
NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT |
Nokogiri::XML::ParseOptions::NONET
def canon_algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute('Algorithm').value
end
case algorithm
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
Nokogiri::XML::XML_C14N_1_0
when "http://www.w3.org/2006/12/xml-c14n11",
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
Nokogiri::XML::XML_C14N_1_1
else
Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
end
end
def algorithm(element)
algorithm = element
if algorithm.is_a?(REXML::Element)
algorithm = element.attribute("Algorithm").value
end
algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
case algorithm
when 256 then OpenSSL::Digest::SHA256
when 384 then OpenSSL::Digest::SHA384
when 512 then OpenSSL::Digest::SHA512
else
OpenSSL::Digest::SHA1
end
end
end
class Document < BaseDocument
RSA_SHA1 = "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
RSA_SHA256 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256"
RSA_SHA384 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha384"
RSA_SHA512 = "http://www.w3.org/2001/04/xmldsig-more#rsa-sha512"
SHA1 = "http://www.w3.org/2000/09/xmldsig#sha1"
SHA256 = 'http://www.w3.org/2001/04/xmlenc#sha256'
SHA384 = "http://www.w3.org/2001/04/xmldsig-more#sha384"
SHA512 = 'http://www.w3.org/2001/04/xmlenc#sha512'
ENVELOPED_SIG = "http://www.w3.org/2000/09/xmldsig#enveloped-signature"
INC_PREFIX_LIST = "#default samlp saml ds xs xsi md"
attr_writer :uuid
def uuid
@uuid ||= begin
document.root.nil? ? nil : document.root.attributes['ID']
end
end
#<Signature>
#<SignedInfo>
#<CanonicalizationMethod />
#<SignatureMethod />
#<Reference>
#<Transforms>
#<DigestMethod>
#<DigestValue>
#</Reference>
#<Reference /> etc.
#</SignedInfo>
#<SignatureValue />
#<KeyInfo />
#<Object />
#</Signature>
def sign_document(private_key, certificate, signature_method = RSA_SHA1, digest_method = SHA1)
noko = Nokogiri::XML(self.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
signed_info_element = signature_element.add_element("ds:SignedInfo")
signed_info_element.add_element("ds:CanonicalizationMethod", {"Algorithm" => C14N})
signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>signature_method})
# Add Reference
reference_element = signed_info_element.add_element("ds:Reference", {"URI" => "##{uuid}"})
# Add Transforms
transforms_element = reference_element.add_element("ds:Transforms")
transforms_element.add_element("ds:Transform", {"Algorithm" => ENVELOPED_SIG})
c14element = transforms_element.add_element("ds:Transform", {"Algorithm" => C14N})
c14element.add_element("ec:InclusiveNamespaces", {"xmlns:ec" => C14N, "PrefixList" => INC_PREFIX_LIST})
digest_method_element = reference_element.add_element("ds:DigestMethod", {"Algorithm" => digest_method})
inclusive_namespaces = INC_PREFIX_LIST.split(" ")
canon_doc = noko.canonicalize(canon_algorithm(C14N), inclusive_namespaces)
reference_element.add_element("ds:DigestValue").text = compute_digest(canon_doc, algorithm(digest_method_element))
# add SignatureValue
noko_sig_element = Nokogiri::XML(signature_element.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
noko_signed_info_element = noko_sig_element.at_xpath('//ds:Signature/ds:SignedInfo', 'ds' => DSIG)
canon_string = noko_signed_info_element.canonicalize(canon_algorithm(C14N))
signature = compute_signature(private_key, algorithm(signature_method).new, canon_string)
signature_element.add_element("ds:SignatureValue").text = signature
# add KeyInfo
key_info_element = signature_element.add_element("ds:KeyInfo")
x509_element = key_info_element.add_element("ds:X509Data")
x509_cert_element = x509_element.add_element("ds:X509Certificate")
if certificate.is_a?(String)
certificate = OpenSSL::X509::Certificate.new(certificate)
end
x509_cert_element.text = Base64.encode64(certificate.to_der).gsub(/\n/, "")
# add the signature
issuer_element = elements["//saml:Issuer"]
if issuer_element
root.insert_after(issuer_element, signature_element)
elsif first_child = root.children[0]
root.insert_before(first_child, signature_element)
else
root.add_element(signature_element)
end
end
protected
def compute_signature(private_key, signature_algorithm, document)
Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
end
def compute_digest(document, digest_algorithm)
digest = digest_algorithm.digest(document)
Base64.encode64(digest).strip!
end
end
class SignedDocument < BaseDocument
include OneLogin::RubySaml::ErrorHandling
attr_writer :signed_element_id
def initialize(response, errors = [])
super(response)
@errors = errors
end
def signed_element_id
@signed_element_id ||= extract_signed_element_id
end
def validate_document(idp_cert_fingerprint, soft = true, options = {})
# get cert from response
cert_element = REXML::XPath.first(
self,
"//ds:X509Certificate",
{ "ds"=>DSIG }
)
if cert_element
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
cert_text = Base64.decode64(base64_cert)
begin
cert = OpenSSL::X509::Certificate.new(cert_text)
rescue OpenSSL::X509::CertificateError => _e
return append_error("Document Certificate Error", soft)
end
if options[:fingerprint_alg]
fingerprint_alg = XMLSecurity::BaseDocument.new.algorithm(options[:fingerprint_alg]).new
else
fingerprint_alg = OpenSSL::Digest::SHA1.new
end
fingerprint = fingerprint_alg.hexdigest(cert.to_der)
# check cert matches registered idp cert
if fingerprint != idp_cert_fingerprint.gsub(/[^a-zA-Z0-9]/,"").downcase
return append_error("Fingerprint mismatch", soft)
end
else
if options[:cert]
base64_cert = Base64.encode64(options[:cert].to_pem)
else
if soft
return false
else
return append_error("Certificate element missing in response (ds:X509Certificate) and not cert provided at settings", soft)
end
end
end
validate_signature(base64_cert, soft)
end
def validate_document_with_cert(idp_cert, soft = true)
# get cert from response
cert_element = REXML::XPath.first(
self,
"//ds:X509Certificate",
{ "ds"=>DSIG }
)
if cert_element
base64_cert = OneLogin::RubySaml::Utils.element_text(cert_element)
cert_text = Base64.decode64(base64_cert)
begin
cert = OpenSSL::X509::Certificate.new(cert_text)
rescue OpenSSL::X509::CertificateError => _e
return append_error("Document Certificate Error", soft)
end
# check saml response cert matches provided idp cert
if idp_cert.to_pem != cert.to_pem
return append_error("Certificate of the Signature element does not match provided certificate", soft)
end
else
base64_cert = Base64.encode64(idp_cert.to_pem)
end
validate_signature(base64_cert, true)
end
def validate_signature(base64_cert, soft = true)
document = Nokogiri::XML(self.to_s) do |config|
config.options = XMLSecurity::BaseDocument::NOKOGIRI_OPTIONS
end
# create a rexml document
@working_copy ||= REXML::Document.new(self.to_s).root
# get signature node
sig_element = REXML::XPath.first(
@working_copy,
"//ds:Signature",
{"ds"=>DSIG}
)
# signature method
sig_alg_value = REXML::XPath.first(
sig_element,
"./ds:SignedInfo/ds:SignatureMethod",
{"ds"=>DSIG}
)
signature_algorithm = algorithm(sig_alg_value)
# get signature
base64_signature = REXML::XPath.first(
sig_element,
"./ds:SignatureValue",
{"ds" => DSIG}
)
signature = Base64.decode64(OneLogin::RubySaml::Utils.element_text(base64_signature))
# canonicalization method
canon_algorithm = canon_algorithm REXML::XPath.first(
sig_element,
'./ds:SignedInfo/ds:CanonicalizationMethod',
'ds' => DSIG
)
noko_sig_element = document.at_xpath('//ds:Signature', 'ds' => DSIG)
noko_signed_info_element = noko_sig_element.at_xpath('./ds:SignedInfo', 'ds' => DSIG)
canon_string = noko_signed_info_element.canonicalize(canon_algorithm)
noko_sig_element.remove
# get inclusive namespaces
inclusive_namespaces = extract_inclusive_namespaces
# check digests
ref = REXML::XPath.first(sig_element, "//ds:Reference", {"ds"=>DSIG})
hashed_element = document.at_xpath("//*[@ID=$id]", nil, { 'id' => extract_signed_element_id })
canon_algorithm = canon_algorithm REXML::XPath.first(
ref,
'//ds:CanonicalizationMethod',
{ "ds" => DSIG }
)
canon_algorithm = process_transforms(ref, canon_algorithm)
canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
digest_algorithm = algorithm(REXML::XPath.first(
ref,
"//ds:DigestMethod",
{ "ds" => DSIG }
))
hash = digest_algorithm.digest(canon_hashed_element)
encoded_digest_value = REXML::XPath.first(
ref,
"//ds:DigestValue",
{ "ds" => DSIG }
)
digest_value = Base64.decode64(OneLogin::RubySaml::Utils.element_text(encoded_digest_value))
unless digests_match?(hash, digest_value)
return append_error("Digest mismatch", soft)
end
# get certificate object
cert_text = Base64.decode64(base64_cert)
cert = OpenSSL::X509::Certificate.new(cert_text)
# verify signature
unless cert.public_key.verify(signature_algorithm.new, signature, canon_string)
return append_error("Key validation error", soft)
end
return true
end
private
def process_transforms(ref, canon_algorithm)
transforms = REXML::XPath.match(
ref,
"//ds:Transforms/ds:Transform",
{ "ds" => DSIG }
)
transforms.each do |transform_element|
if transform_element.attributes && transform_element.attributes["Algorithm"]
algorithm = transform_element.attributes["Algorithm"]
case algorithm
when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
"http://www.w3.org/TR/2001/REC-xml-c14n-20010315#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_1_0
when "http://www.w3.org/2006/12/xml-c14n11",
"http://www.w3.org/2006/12/xml-c14n11#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_1_1
when "http://www.w3.org/2001/10/xml-exc-c14n#",
"http://www.w3.org/2001/10/xml-exc-c14n#WithComments"
canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
end
end
end
canon_algorithm
end
def digests_match?(hash, digest_value)
hash == digest_value
end
def extract_signed_element_id
reference_element = REXML::XPath.first(
self,
"//ds:Signature/ds:SignedInfo/ds:Reference",
{"ds"=>DSIG}
)
return nil if reference_element.nil?
sei = reference_element.attribute("URI").value[1..-1]
sei.nil? ? reference_element.parent.parent.parent.attribute("ID").value : sei
end
def extract_inclusive_namespaces
element = REXML::XPath.first(
self,
"//ec:InclusiveNamespaces",
{ "ec" => C14N }
)
if element
prefix_list = element.attributes.get_attribute("PrefixList").value
prefix_list.split(" ")
else
nil
end
end
end
end

View File

@ -0,0 +1,81 @@
$LOAD_PATH.push File.expand_path('../lib', __FILE__)
require 'onelogin/ruby-saml/version'
Gem::Specification.new do |s|
s.name = 'ruby-saml-custom'
s.version = OneLogin::RubySaml::VERSION
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
s.authors = ["OneLogin LLC"]
s.date = Time.now.strftime("%Y-%m-%d")
s.description = %q{SAML toolkit for Ruby on Rails}
s.email = %q{support@onelogin.com}
s.license = 'MIT'
s.extra_rdoc_files = [
"LICENSE",
"README.md"
]
s.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
s.homepage = %q{https://github.com/onelogin/ruby-saml}
s.rdoc_options = ["--charset=UTF-8"]
s.require_paths = ["lib"]
s.rubygems_version = %q{1.3.7}
s.required_ruby_version = '>= 1.8.7'
s.summary = %q{SAML Ruby Tookit}
# Because runtime dependencies are determined at build time, we cannot make
# Nokogiri's version dependent on the Ruby version, even though we would
# have liked to constrain Ruby 1.8.7 to install only the 1.5.x versions.
if defined?(JRUBY_VERSION)
if JRUBY_VERSION < '9.2.0.0'
s.add_runtime_dependency('nokogiri', '>= 1.8.2', '<= 1.8.5')
s.add_runtime_dependency('jruby-openssl', '>= 0.9.8')
s.add_runtime_dependency('json', '< 2.3.0')
else
s.add_runtime_dependency('nokogiri', '>= 1.8.2')
end
elsif RUBY_VERSION < '1.9'
s.add_runtime_dependency('uuid')
s.add_runtime_dependency('nokogiri', '<= 1.5.11')
elsif RUBY_VERSION < '2.1'
s.add_runtime_dependency('nokogiri', '>= 1.5.10', '<= 1.6.8.1')
s.add_runtime_dependency('json', '< 2.3.0')
elsif RUBY_VERSION < '2.3'
s.add_runtime_dependency('nokogiri', '>= 1.7', '< 1.10.0')
else
s.add_runtime_dependency('nokogiri', '>= 1.10.5')
s.add_runtime_dependency('rexml')
end
s.add_development_dependency('coveralls')
s.add_development_dependency('minitest', '~> 5.5')
s.add_development_dependency('mocha', '~> 0.14')
s.add_development_dependency('rake', '~> 10')
s.add_development_dependency('shoulda', '~> 2.11')
s.add_development_dependency('simplecov')
s.add_development_dependency('systemu', '~> 2')
if RUBY_VERSION < '2.1'
s.add_development_dependency('timecop', '<= 0.6.0')
else
s.add_development_dependency('timecop', '~> 0.9')
end
if defined?(JRUBY_VERSION)
# All recent versions of JRuby play well with pry
s.add_development_dependency('pry')
elsif RUBY_VERSION < '1.9'
# 1.8.7
s.add_development_dependency('ruby-debug', '~> 0.10.4')
elsif RUBY_VERSION < '2.0'
# 1.9.x
s.add_development_dependency('debugger-linecache', '~> 1.2.0')
s.add_development_dependency('debugger', '~> 1.6.4')
elsif RUBY_VERSION < '2.1'
# 2.0.x
s.add_development_dependency('byebug', '~> 2.1.1')
else
# 2.1.x, 2.2.x
s.add_development_dependency('pry-byebug')
end
end

View File

@ -0,0 +1,30 @@
require File.expand_path(File.join(File.dirname(__FILE__), 'test_helper'))
require 'onelogin/ruby-saml/attributes'
class AttributesTest < Minitest::Test
describe 'Attributes' do
let(:attributes) do
OneLogin::RubySaml::Attributes.new({
'email' => ['tom@hanks.com'],
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname' => ['Tom'],
'http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname' => ['Hanks']
})
end
it 'fetches string attribute' do
assert_equal('tom@hanks.com', attributes.fetch('email'))
end
it 'fetches symbol attribute' do
assert_equal('tom@hanks.com', attributes.fetch(:email))
end
it 'fetches regexp attribute' do
assert_equal('Tom', attributes.fetch(/givenname/))
assert_equal('Tom', attributes.fetch(/gi(.*)/))
assert_nil(attributes.fetch(/^z.*/))
assert_equal('Hanks', attributes.fetch(/surname/))
end
end
end

Binary file not shown.

View File

@ -0,0 +1,12 @@
-----BEGIN CERTIFICATE-----
MIIBrTCCAaGgAwIBAgIBATADBgEAMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApD
YWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9uZUxv
Z2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMB4XDTEwMTAxMTIxMTUxMloX
DTE1MTAxMTIxMTUxMlowZzELMAkGA1UEBhMCVVMxEzARBgNVBAgMCkNhbGlmb3Ju
aWExFTATBgNVBAcMDFNhbnRhIE1vbmljYTERMA8GA1UECgwIT25lTG9naW4xGTAX
BgNVBAMMEGFwcC5vbmVsb2dpbi5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJ
AoGBAMPmjfjy7L35oDpeBXBoRVCgktPkLno9DOEWB7MgYMMVKs2B6ymWQLEWrDug
MK1hkzWFhIb5fqWLGbWy0J0veGR9/gHOQG+rD/I36xAXnkdiXXhzoiAG/zQxM0ed
MOUf40n314FC8moErcUg6QabttzesO59HFz6shPuxcWaVAgxAgMBAAEwAwYBAAMB
AA==
-----END CERTIFICATE-----

View File

@ -0,0 +1 @@
MIIEYTCCA0mgAwIBAgIJAMax+2BoUJmCMA0GCSqGSIb3DQEBBQUAMIHGMQswCQYDVQQGEwJVUzERMA8GA1UECAwISWxsaW5vaXMxEDAOBgNVBAcMB0NoaWNhZ28xIzAhBgNVBAoMGldlbGxzcHJpbmcgV29ybGR3aWRlLCBJbmMuMRwwGgYDVQQLDBNTeXN0ZW1zIEVuZ2luZWVyaW5nMSQwIgYDVQQDDBtzc28ud2VsbHNwcmluZ3dvcmxkd2lkZS5jb20xKTAnBgkqhkiG9w0BCQEWGml0QHdlbGxzcHJpbmd3b3JsZHdpZGUuY29tMB4XDTEzMDIyNzIzNTUwOFoXDTIzMDIyNzIzNTUwOFowgcYxCzAJBgNVBAYTAlVTMREwDwYDVQQIDAhJbGxpbm9pczEQMA4GA1UEBwwHQ2hpY2FnbzEjMCEGA1UECgwaV2VsbHNwcmluZyBXb3JsZHdpZGUsIEluYy4xHDAaBgNVBAsME1N5c3RlbXMgRW5naW5lZXJpbmcxJDAiBgNVBAMMG3Nzby53ZWxsc3ByaW5nd29ybGR3aWRlLmNvbTEpMCcGCSqGSIb3DQEJARYaaXRAd2VsbHNwcmluZ3dvcmxkd2lkZS5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCzNhrDaXa00JAIRaxEyDrK/Zjj8bBTQD5dPgugDndYf1AOpzSGpGFU+lPu0QRv0o66K64HrF24FATWI18Q6aZ+xX8QbuBrfia6hOFef29Sk5paS9+DcDCmisuNpl84kbbiazy6S6cFtcdrG9/cr2iXtYmIzz7EfUcP/UVAp24ZW7dWhcvxoqxF9n6Fj94N+rA0dmUFUGz6glm7us3p36xbkiUMpgr3feD/9P34H+2YFsQ2b2DblDI5Z7YULHxBsl5nuhPLFuPN1olcWQBsJYO6iHElFRH+487L2yZ1mLVXKI0LFb/w1rAJpPeUc8E5s1MATAjNx3wPwwqgw30sKtoXAgMBAAGjUDBOMB0GA1UdDgQWBBSrGGV9w3hGXTafkJLUaWBsWiDGaTAfBgNVHSMEGDAWgBSrGGV9w3hGXTafkJLUaWBsWiDGaTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQCcvZV0VIwIRD4C5CItFwfNRF2LRBmYiNh4FJbo0wESbH0cWT9vXNdcjcx6PHrIj7ICErSCR5eZmIrSgLEBEkptjVsiFsHWSvMHv37WaHwyhZWnhutss32aP9+ifxQ1lzwm54jZWZsVVVFQH155BDsVeU1UwEhvcCExFa7RNjyvqQrZmyQwMFSzL1cQp0humu0hHLtAI7E32lp5itw6kTOfyhjB8d1bzBVZe6RY64RxOPEcx+9hkrHmfCohdt644jRtPdLTvqqxpscYGD+L2QOt1HpbGgAdcgUZeUHo/eosqpwDOoyuFepz7JzqMncxFN//NmjnFGVZdGR+6bTxKUKq

View File

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
-----END CERTIFICATE-----

View File

@ -0,0 +1,42 @@
-----BEGIN CERTIFICATE-----
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ
-----END CERTIFICATE-----

View File

@ -0,0 +1,12 @@
-----BEGIN PRIVATE KEY-----
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3
NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht
YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR
hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR
O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3
B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe
3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL
d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO
5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK
GeW2AKaE6oqRqeVwGw4V
-----END PRIVATE KEY-----

View File

@ -0,0 +1,12 @@
-----BEGIN RSA PRIVATE KEY-----
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3
NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht
YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR
hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR
O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3
B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe
3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL
d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO
5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK
GeW2AKaE6oqRqeVwGw4V
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
-----BEGIN CERTIFICATE----- MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N 4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ +4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ -----END CERTIFICATE-----

View File

@ -0,0 +1 @@
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N 4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ +4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1 2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ

View File

@ -0,0 +1,12 @@
MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UE
BhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVh
MDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIx
wsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNz
c28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkq
hkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jC
mdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpw
Vvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N
4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0G
CSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ
+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/1
2Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ

View File

@ -0,0 +1 @@
-----BEGIN CERTIFICATE-----MIICPDCCAaWgAwIBAgIIEiC/9HMAWW AwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWE wMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE----------BEGIN CERTIFICATE-----MIICPDCCAaWgAw IBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE----------BEGIN CERTIFICATE-----MIICPDCCAaWgAwIBAgIIEiC/9HMAWWAwDQYJKoZIhvcNAQEFBQAwTzELMAkGA1UEBhMCVVMxDDAKBgNVBAoTA2libTEMMAoGA1UECxMDc3NvMSQwIgYDVQQDExtjMjVhMDI3Ny50b3JvbnRvLmNhLmlibS5jb20wHhcNMTEwNTI0MTYzNTQ4WhcNMjEwNTIxwsQMPBj4WQTNzTYMCQYDVQQGEwJVUzEMMAoGA1UEChMDaWJtMQwwCgYDVQQLEwNzc28xJDAiBgNVBAMTG2MyNWEwMjc3LnRvcm9udG8uY2EuaWJtLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAgzfYQZuf5FVdJTcrsIQZ+YHTPjOsw2MGo0jCmdGMcp4brWeFgk1OVaOmytPx6P76wHWR436AleX3crHBPd8gPxuZdnvBQ7PkrKpwVvaq52juenFrho8JY0TeVgVkY5jAh45YzytjP2y2k/cGQurI/56NT0PpQJ0S1G3N4eTg718CAwEAAaMhMB8wHQYDVR0OBBYEFCYVLJqcJ7WgdzGIsuJ/TzDGDqinMA0GCSqGSIb3DQEBBQUAA4GBAB80bIePf+qWDvWe+9bEEnbFTw7pCknLexxZ0AMqrsmZ+4jmI+evP1JZYCjfIg9X+MBH01hfp5dFcetz3o6w6SkV+BxLYLgfcy5KUcYsIM/12Zkedj87bS1glzOy5B89pKD2DMbu6828Abzgc+4lyQ2ASifsqM4cZdVayzo8n+dQ-----END CERTIFICATE-----

View File

@ -0,0 +1 @@
-----BEGIN PRIVATE KEY----- MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe 3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO 5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK GeW2AKaE6oqRqeVwGw4V -----END PRIVATE KEY-----

View File

@ -0,0 +1 @@
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUhtYzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfRhg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQRO1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOLd3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCKGeW2AKaE6oqRqeVwGw4V

View File

@ -0,0 +1,10 @@
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3
NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht
YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR
hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR
O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3
B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe
3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL
d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO
5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK
GeW2AKaE6oqRqeVwGw4V

View File

@ -0,0 +1 @@
-----BEGIN RSA PRIVATE KEY----- MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3 NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3 B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe 3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO 5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK GeW2AKaE6oqRqeVwGw4V -----END RSA PRIVATE KEY-----

View File

@ -0,0 +1 @@
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUhtYzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfRhg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQRO1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOLd3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCKGeW2AKaE6oqRqeVwGw4V

View File

@ -0,0 +1,10 @@
MIIBuwIBAAKBgQDImEj39zKfeh4LbgzPuos/DCnyKZUJzAHX3OSXA1Akl+CA1Ak3
NgRCJ3NOflCGzW+PcLvxrSwH3mHaqQAvDA2fJOySiVtJ9+tm1jrQnL+AAw7JzUht
YzmnRC8wwuN1+TDuKiK1Hzr/4fz2eFZ6+M53YC4eHOkBYA0FdFGRYrH70wIVAJfR
hg3tWWhJvyJBvaZoh3/BP613AoGBAL0KkMDFRc3FXcvdRKNpWbrsU41G32bBlfQR
O1EBe1+ghIasBr7lxEEhdkfthlaF4JiFHyaXuSx5hPKUbo8AO/MfaPJ7SKK2QRS3
B/qlstzIbjmvgYJJuOs4O4x6lYgeU5rb9G5SoOEBvyo46ZxfzdWhAwfZofsrzAhe
3WlOTZkdAoGAGmt0xlYn/0oYZjCxGKStjBA80E5NypAl7UyFj1RhGjIUkiuRcgOL
d3/fC6vKuqsMtLHyb5EGqtHPbqm4re1rw0zDh+qHEFA4N6UW0poc9eNEfosJA2BO
5o8ft9FzKA033pl89mD0CBj05EPadGR7E7QhL5mXuQJpjXJEiyqbce4CFAUFhvCK
GeW2AKaE6oqRqeVwGw4V

View File

@ -0,0 +1,15 @@
-----BEGIN CERTIFICATE-----
MIICVDCCAb2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBHMQswCQYDVQQGEwJ1czEQ
MA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIGA1UEAwwLZXhh
bXBsZS5jb20wHhcNMTcwNDA3MDgzMDAzWhcNMjcwNDA1MDgzMDAzWjBHMQswCQYD
VQQGEwJ1czEQMA4GA1UECAwHZXhhbXBsZTEQMA4GA1UECgwHZXhhbXBsZTEUMBIG
A1UEAwwLZXhhbXBsZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKhP
S4/0azxbQekHHewQGKD7Pivr3CDpsrKxY3xlVanxj427OwzOb5KUVzsDEazumt6s
ZFY8HfidsjXY4EYA4ZzyL7ciIAR5vlAsIYN9nJ4AwVDnN/RjVwj+TN6BqWPLpVIp
Hc6Dl005HyE0zJnk1DZDn2tQVrIzbD3FhCp7YeotAgMBAAGjUDBOMB0GA1UdDgQW
BBRYZx4thASfNvR/E7NsCF2IaZ7wIDAfBgNVHSMEGDAWgBRYZx4thASfNvR/E7Ns
CF2IaZ7wIDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBACz4aobx9aG3
kh+rNyrlgM3K6dYfnKG1/YH5sJCAOvg8kDr0fQAQifH8lFVWumKUMoAe0bFTfwWt
p/VJ8MprrEJth6PFeZdczpuv+fpLcNj2VmNVJqvQYvS4m36OnBFh1QFZW8UrbFIf
dtm2nuZ+twSKqfKwjLdqcoX0p39h7Uw/
-----END CERTIFICATE-----

View File

@ -0,0 +1,14 @@
-----BEGIN CERTIFICATE-----
MIICGzCCAYQCCQCNNcQXom32VDANBgkqhkiG9w0BAQUFADBSMQswCQYDVQQGEwJV
UzELMAkGA1UECBMCSU4xFTATBgNVBAcTDEluZGlhbmFwb2xpczERMA8GA1UEChMI
T25lTG9naW4xDDAKBgNVBAsTA0VuZzAeFw0xNDA0MjMxODQxMDFaFw0xNTA0MjMx
ODQxMDFaMFIxCzAJBgNVBAYTAlVTMQswCQYDVQQIEwJJTjEVMBMGA1UEBxMMSW5k
aWFuYXBvbGlzMREwDwYDVQQKEwhPbmVMb2dpbjEMMAoGA1UECxMDRW5nMIGfMA0G
CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5Pckw
sNgS9pUvV7fzTqCHk8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl
0+mhy6MPdyjyA6G14jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA
7DjNaUYQqwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBALM2vGCiQ/vm+a6v40+VX2zd
qHA2Q/1vF1ibQzJ54MJCOVWvs+vQXfZFhdm0OPM2IrDU7oqvKPqP6xOAeJK6H0yP
7M4YL3fatSvIYmmfyXC9kt3Svz/NyrHzPhUnJ0ye/sUSXxnzQxwcm/9PwAqrQaA3
QpQkH57ybF/OoryPe+2h
-----END CERTIFICATE-----

View File

@ -0,0 +1,15 @@
-----BEGIN RSA PRIVATE KEY-----
MIICXAIBAAKBgQDo6m+QZvYQ/xL0ElLgupK1QDcYL4f5PckwsNgS9pUvV7fzTqCH
k8ThLxTk42MQ2McJsOeUJVP728KhymjFCqxgP4VuwRk9rpAl0+mhy6MPdyjyA6G1
4jrDWS65ysLchK4t/vwpEDz0SQlEoG1kMzllSm7zZS3XregA7DjNaUYQqwIDAQAB
AoGBALGR6bRBit+yV5TUU3MZSrf8WQSLWDLgs/33FQSAEYSib4+DJke2lKbI6jkG
UoSJgFUXFbaQLtMY2+3VDsMKPBdAge9gIdvbkC4yoKjLGm/FBDOxxZcfLpR+9OPq
U3qM9D0CNuliBWI7Je+p/zs09HIYucpDXy9E18KA1KNF6rfhAkEA9KoNam6wAKnm
vMzz31ws3RuIOUeo2rx6aaVY95+P9tTxd6U+pNkwxy1aCGP+InVSwlYNA1aQ4Axi
/GdMIWMkxwJBAPO1CP7cQNZQmu7yusY+GUObDII5YK9WLaY4RAicn5378crPBFxv
Ukqf9G6FHo7u88iTCIp+vwa3Hn9Tumg3iP0CQQDgUXWBasCVqzCxU5wY4tMDWjXY
hpoLCpmVeRML3dDJt004rFm2HKe7Rhpw7PTZNQZOxUSjFeA4e0LaNf838UWLAkB8
QfbHM3ffjhOg96PhhjINdVWoZCb230LBOHj/xxPfUmFTHcBEfQIBSJMxcrBFAnLL
9qPpMXymqOFk3ETz9DTlAj8E0qGbp78aVbTOtuwEwNJII+RPw+Zkc+lKR+yaWkAz
fIXw527NPHH3+rnBG72wyZr9ud4LAum9jh+5No1LQpk=
-----END RSA PRIVATE KEY-----

View File

@ -0,0 +1,758 @@
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
require 'onelogin/ruby-saml/idp_metadata_parser'
class IdpMetadataParserTest < Minitest::Test
class MockSuccessResponse < Net::HTTPSuccess
# override parent's initialize
def initialize; end
attr_accessor :body
end
class MockFailureResponse < Net::HTTPNotFound
# override parent's initialize
def initialize; end
attr_accessor :body
end
describe "parsing an IdP descriptor file" do
it "extract settings details from xml" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse(idp_metadata_descriptor)
assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id
assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
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://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names
assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until
end
it "extract certificate from md:KeyDescriptor[@use='signing']" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
settings = idp_metadata_parser.parse(idp_metadata)
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
end
it "extract certificate from md:KeyDescriptor[@use='encryption']" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
settings = idp_metadata_parser.parse(idp_metadata)
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
end
it "extract certificate from md:KeyDescriptor" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
idp_metadata = idp_metadata.sub('<md:KeyDescriptor use="encryption">', '<md:KeyDescriptor>')
settings = idp_metadata_parser.parse(idp_metadata)
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
end
it "extract SSO endpoint with no specific binding, it takes the first" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
settings = idp_metadata_parser.parse(idp_metadata)
assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", settings.idp_sso_service_url
assert_equal "urn:mace:shibboleth:1.0:profiles:AuthnRequest", settings.idp_sso_service_binding
end
it "extract SSO endpoint with specific binding as a String" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
options = {}
options[:sso_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
options[:slo_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST'
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", settings.idp_sso_service_binding
assert_nil settings.idp_slo_service_url
options[:sso_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
options[:slo_binding] = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect'
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
end
it "extract SSO endpoint with specific binding as an Array" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
options = {}
options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']
options[:slo_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", settings.idp_sso_service_binding
assert_nil settings.idp_slo_service_url
options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
options[:slo_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
options[:slo_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SLO", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
end
it "extract NameIDFormat no specific priority, it takes the first" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
settings = idp_metadata_parser.parse(idp_metadata)
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
end
it "extract NameIDFormat specific priority as a String" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
options = {}
options[:name_id_format] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified'
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
options[:name_id_format] = 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", settings.name_identifier_format
options[:name_id_format] = 'urn:oasis:names:tc:SAML:2.0:nameid-format:persistent'
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", settings.name_identifier_format
end
it "extract NameIDFormat specific priority as an Array" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
options = {}
options[:name_id_format] = ['urn:oasis:names:tc:SAML:2.0:nameid-format:persistent', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress']
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", settings.name_identifier_format
options[:name_id_format] = ['invalid', 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress']
settings = idp_metadata_parser.parse(idp_metadata, options)
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", settings.name_identifier_format
end
it "uses settings options as hash for overrides" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
settings = idp_metadata_parser.parse(idp_metadata, {
:settings => {
:security => {
:digest_method => XMLSecurity::Document::SHA256,
:signature_method => XMLSecurity::Document::RSA_SHA256
}
}
})
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 XMLSecurity::Document::SHA256, settings.security[:digest_method]
assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
end
it "merges results into given settings object" do
settings = OneLogin::RubySaml::Settings.new(:security => {
:digest_method => XMLSecurity::Document::SHA256,
:signature_method => XMLSecurity::Document::RSA_SHA256
})
OneLogin::RubySaml::IdpMetadataParser.new.parse(idp_metadata_descriptor, :settings => settings)
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 XMLSecurity::Document::SHA256, settings.security[:digest_method]
assert_equal XMLSecurity::Document::RSA_SHA256, settings.security[:signature_method]
end
end
describe "parsing an IdP descriptor file into an Hash" do
it "extract settings details from xml" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
metadata = idp_metadata_parser.parse_to_hash(idp_metadata_descriptor)
assert_equal "https://hello.example.com/access/saml/idp.xml", metadata[:idp_entity_id]
assert_equal "https://hello.example.com/access/saml/login", metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_sso_service_binding]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint]
assert_equal "https://hello.example.com/access/saml/logout", metadata[:idp_slo_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", metadata[:idp_slo_service_binding]
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", metadata[:name_identifier_format]
assert_equal ["AuthToken", "SSOStartPage"], metadata[:idp_attribute_names]
assert_equal '2014-04-17T18:02:33.910Z', metadata[:valid_until]
end
it "extract certificate from md:KeyDescriptor[@use='signing']" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata[:idp_cert_fingerprint]
end
it "extract certificate from md:KeyDescriptor[@use='encryption']" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
end
it "extract certificate from md:KeyDescriptor" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
idp_metadata = idp_metadata.sub(/<md:KeyDescriptor use="signing">(.*?)<\/md:KeyDescriptor>/m, "")
idp_metadata = idp_metadata.sub('<md:KeyDescriptor use="encryption">', '<md:KeyDescriptor>')
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
end
it "extract SSO endpoint with no specific binding, it takes the first" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
metadata = idp_metadata_parser.parse_to_hash(idp_metadata)
assert_equal "https://idp.example.com/idp/profile/Shibboleth/SSO", metadata[:idp_sso_service_url]
assert_equal "urn:mace:shibboleth:1.0:profiles:AuthnRequest", metadata[:idp_sso_service_binding]
end
it "extract SSO endpoint with specific binding" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor3
options = {}
options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST']
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/POST/SSO", parsed_metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", parsed_metadata[:idp_sso_service_binding]
options[:sso_binding] = ['urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding]
options[:sso_binding] = ['invalid_binding', 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect']
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, options)
assert_equal "https://idp.example.com/idp/profile/SAML2/Redirect/SSO", parsed_metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding]
end
it "ignores a given :settings hash" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata = idp_metadata_descriptor
parsed_metadata = idp_metadata_parser.parse_to_hash(idp_metadata, {
:settings => {
:security => {
:digest_method => XMLSecurity::Document::SHA256,
:signature_method => XMLSecurity::Document::RSA_SHA256
}
}
})
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_nil parsed_metadata[:security]
end
it "can extract certificates multiple times in sequence" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata1 = idp_metadata_descriptor
idp_metadata2 = idp_metadata_descriptor4
metadata1 = idp_metadata_parser.parse_to_hash(idp_metadata1)
metadata2 = idp_metadata_parser.parse_to_hash(idp_metadata2)
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", metadata1[:idp_cert_fingerprint]
assert_equal "CD:2B:2B:DA:FF:F5:DB:64:10:7C:AC:FD:FE:0F:CB:5D:73:5F:16:07", metadata2[:idp_cert_fingerprint]
end
end
describe "parsing an IdP descriptor file with multiple signing certs" do
it "extract settings details from xml" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse(idp_metadata_descriptor2)
assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id
assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names
assert_nil settings.idp_cert_fingerprint
assert_nil settings.idp_cert
assert_equal 2, settings.idp_cert_multi.size
assert settings.idp_cert_multi.key?(:signing)
assert_equal 2, settings.idp_cert_multi[:signing].size
assert settings.idp_cert_multi.key?(:encryption)
assert_equal 1, settings.idp_cert_multi[:encryption].size
end
end
describe "download and parse IdP descriptor file" do
before do
mock_response = MockSuccessResponse.new
mock_response.body = idp_metadata_descriptor
@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
it "extract settings from remote xml" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse_remote(@url)
assert_equal "https://hello.example.com/access/saml/idp.xml", settings.idp_entity_id
assert_equal "https://hello.example.com/access/saml/login", settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_sso_service_binding
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://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", settings.name_identifier_format
assert_equal ["AuthToken", "SSOStartPage"], settings.idp_attribute_names
assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until
assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
end
it "accept self signed certificate if insturcted" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata_parser.parse_remote(@url, false)
assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode
end
end
describe "download and parse IdP descriptor file into an Hash" do
before do
mock_response = MockSuccessResponse.new
mock_response.body = idp_metadata_descriptor
@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
it "extract settings from remote xml" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
parsed_metadata = idp_metadata_parser.parse_remote_to_hash(@url)
assert_equal "https://hello.example.com/access/saml/idp.xml", parsed_metadata[:idp_entity_id]
assert_equal "https://hello.example.com/access/saml/login", parsed_metadata[:idp_sso_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_sso_service_binding]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", parsed_metadata[:idp_cert_fingerprint]
assert_equal "https://hello.example.com/access/saml/logout", parsed_metadata[:idp_slo_service_url]
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", parsed_metadata[:idp_slo_service_binding]
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", parsed_metadata[:name_identifier_format]
assert_equal ["AuthToken", "SSOStartPage"], parsed_metadata[:idp_attribute_names]
assert_equal '2014-04-17T18:02:33.910Z', parsed_metadata[:valid_until]
assert_equal OpenSSL::SSL::VERIFY_PEER, @http.verify_mode
end
it "accept self signed certificate if insturcted" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
idp_metadata_parser.parse_remote_to_hash(@url, false)
assert_equal OpenSSL::SSL::VERIFY_NONE, @http.verify_mode
end
end
describe "download failure cases" do
it "raises an exception when the url has no scheme" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
exception = assert_raises(ArgumentError) do
idp_metadata_parser.parse_remote("blahblah")
end
assert_equal("url must begin with http or https", exception.message)
end
it "raises an exception when unable to download metadata" do
mock_response = MockFailureResponse.new
@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)
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
exception = assert_raises(OneLogin::RubySaml::HttpError) do
idp_metadata_parser.parse_remote("https://hello.example.com/access/saml/idp.xml")
end
assert_match("Failed to fetch idp metadata", exception.message)
end
end
describe "parsing metadata with and without ValidUntil and CacheDuration" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
end
it "if no ValidUntil or CacheDuration return nothing" do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor3)
assert_nil settings.valid_until
end
it "if ValidUntil and not CacheDuration return ValidUntil value" do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor)
assert_equal '2014-04-17T18:02:33.910Z', settings.valid_until
end
it "if no ValidUntil but CacheDuration return CacheDuration converted in ValidUntil" do
return if RUBY_VERSION < '1.9'
Timecop.freeze(Time.parse("2020-01-02T10:02:33Z", Time.now.utc)) do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor5)
assert_equal '2020-01-03T10:02:33Z', settings.valid_until
end
end
it "if ValidUntil and CacheDuration return the sooner timestamp" do
return if RUBY_VERSION < '1.9'
Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor6)
assert_equal '2020-01-03T10:12:55Z', settings.valid_until
end
Timecop.freeze(Time.parse("2020-01-01T10:12:55Z", Time.now.utc)) do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor6)
assert_equal '2020-01-03T10:12:55Z', settings.valid_until
end
Timecop.freeze(Time.parse("2020-01-03T10:12:55Z", Time.now.utc)) do
settings = @idp_metadata_parser.parse(idp_metadata_descriptor6)
assert_equal '2020-01-04T18:02:33.910Z', settings.valid_until
end
end
end
describe "parsing metadata with many entity descriptors" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = idp_metadata_multiple_descriptors2
@settings = @idp_metadata_parser.parse(@idp_metadata)
end
it "should find first descriptor" do
assert_equal "https://foo.example.com/access/saml/idp.xml", @settings.idp_entity_id
end
it "should find named descriptor" do
entity_id = "https://bar.example.com/access/saml/idp.xml"
settings = @idp_metadata_parser.parse(
@idp_metadata, :entity_id => entity_id
)
assert_equal entity_id, settings.idp_entity_id
end
it "should retreive data" do
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified", @settings.name_identifier_format
assert_equal "https://hello.example.com/access/saml/login", @settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding
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://hello.example.com/access/saml/logout", @settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding
assert_equal ["AuthToken", "SSOStartPage"], @settings.idp_attribute_names
assert_equal '2014-04-17T18:02:33.910Z', @settings.valid_until
end
it "should handle multiple descriptors at once" do
settings = @idp_metadata_parser.parse_to_array(@idp_metadata)
assert_equal "https://foo.example.com/access/saml/idp.xml", settings.first[:idp_entity_id]
assert_equal "F1:3C:6B:80:90:5A:03:0E:6C:91:3E:5D:15:FA:DD:B0:16:45:48:72", settings.first[:idp_cert_fingerprint]
assert_equal '2014-04-17T18:02:33.910Z', settings.first[:valid_until]
assert_equal "https://bar.example.com/access/saml/idp.xml", settings.last[:idp_entity_id]
assert_equal "08:EB:6E:60:A2:14:4E:89:EC:FA:05:74:9D:72:BF:5D:BE:54:F0:1A", settings.last[:idp_cert_fingerprint]
assert_equal '2014-04-17T18:02:33.910Z', settings.last[:valid_until]
end
end
describe "parsing metadata with no IDPSSODescriptor element" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = no_idp_metadata_descriptor
end
it "raise due no IDPSSODescriptor element" do
assert_raises(ArgumentError) { @idp_metadata_parser.parse(@idp_metadata) }
end
end
describe "parsing metadata with IDPSSODescriptor with multiple certs" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = idp_metadata_multiple_certs
@settings = @idp_metadata_parser.parse(@idp_metadata)
end
it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do
assert_nil @settings.idp_cert
assert_nil @settings.idp_cert_fingerprint
expected_multi_cert = {}
expected_multi_cert[:signing] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==", "MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ=="]
expected_multi_cert[:encryption] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw=="]
assert_equal expected_multi_cert, @settings.idp_cert_multi
assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id
assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format
assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding
assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding
end
end
describe "parsing metadata with IDPSSODescriptor with multiple signing certs" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = idp_metadata_multiple_signing_certs
@settings = @idp_metadata_parser.parse(@idp_metadata)
end
it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do
assert_nil @settings.idp_cert
assert_nil @settings.idp_cert_fingerprint
expected_multi_cert = {}
expected_multi_cert[:signing] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==", "MIICZDCCAc2gAwIBAgIBADANBgkqhkiG9w0BAQ0FADBPMQswCQYDVQQGEwJ1czEUMBIGA1UECAwLZXhhbXBsZS5jb20xFDASBgNVBAoMC2V4YW1wbGUuY29tMRQwEgYDVQQDDAtleGFtcGxlLmNvbTAeFw0xNzA0MTUxNjMzMThaFw0xODA0MTUxNjMzMThaME8xCzAJBgNVBAYTAnVzMRQwEgYDVQQIDAtleGFtcGxlLmNvbTEUMBIGA1UECgwLZXhhbXBsZS5jb20xFDASBgNVBAMMC2V4YW1wbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC6GLkl5lDUZdHNDAojp5i24OoPlqrt5TGXJIPqAZYT1hQvJW5nv17MFDHrjmtEnmW4ACKEy0fAX80QWIcHunZSkbEGHb+NG/6oTi5RipXMvmHnfFnPJJ0AdtiLiPE478CV856gXekV4Xx5u3KrylcOgkpYsp0GMIQBDzleMUXlYQIDAQABo1AwTjAdBgNVHQ4EFgQUnP8vlYPGPL2n6ZzDYij2kMDC8wMwHwYDVR0jBBgwFoAUnP8vlYPGPL2n6ZzDYij2kMDC8wMwDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQ0FAAOBgQAlQGAl+b8Cpot1g+65lLLjVoY7APJPWLW0klKQNlMU0s4MU+71Y3ExUEOXDAZgKcFoavb1fEOGMwEf38NaJAy1e/l6VNuixXShffq20ymqHQxOG0q8ujeNkgZF9k6XDfn/QZ3AD0o/IrCT7UMc/0QsfgIjWYxwCvp2syApc5CYfQ==","LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="]
assert_equal expected_multi_cert, @settings.idp_cert_multi
assert_equal "https://idp.examle.com/saml/metadata", @settings.idp_entity_id
assert_equal "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", @settings.name_identifier_format
assert_equal "https://idp.examle.com/saml/sso", @settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding
assert_equal "https://idp.examle.com/saml/slo", @settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_slo_service_binding
end
end
describe "parsing metadata with IDPSSODescriptor with same signature cert and encrypt cert" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = idp_metadata_same_sign_and_encrypt_cert
@settings = @idp_metadata_parser.parse(@idp_metadata)
end
let(:expected_cert) do
"MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD
VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2
MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u
ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z
0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT
gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m
Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF
zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ
UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG
A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV
HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw
DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO
BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu
AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV
gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ
sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP
TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu
QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78
1sE="
end
it "should return idp_cert and idp_cert_fingerprint and no idp_cert_multi" do
assert_equal(expected_cert, @settings.idp_cert)
assert_equal("2D:A9:40:88:28:EE:67:BB:4A:5B:E0:58:A7:CC:71:95:2D:1B:C9:D3", @settings.idp_cert_fingerprint)
assert_equal({ :signing => [expected_cert], :encryption => [expected_cert] }, @settings.idp_cert_multi)
assert_equal("https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id)
assert_equal("urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format)
assert_equal("https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url)
assert_nil(@settings.idp_slo_service_url)
# TODO: next line can be changed to `assert_nil @settings.idp_slo_service_binding` after :embed_sign is removed.
assert_nil(@settings.instance_variable_get('@idp_slo_service_binding'))
end
end
describe "parsing metadata with IDPSSODescriptor with different signature cert and encrypt cert" do
before do
@idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
@idp_metadata = idp_metadata_different_sign_and_encrypt_cert
@settings = @idp_metadata_parser.parse(@idp_metadata)
end
it "should return a idp_cert_multi and no idp_cert and no idp_cert_fingerprint" do
assert_nil @settings.idp_cert
assert_nil @settings.idp_cert_fingerprint
expected_multi_cert = {}
expected_multi_cert[:signing] = ["MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD
VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2
MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u
ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z
0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT
gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m
Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF
zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ
UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG
A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV
HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw
DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO
BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu
AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV
gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ
sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP
TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu
QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78
1sE="]
expected_multi_cert[:encryption] = ["MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw=="]
assert_equal expected_multi_cert, @settings.idp_cert_multi
assert_equal "https://app.onelogin.com/saml/metadata/383123", @settings.idp_entity_id
assert_equal "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", @settings.name_identifier_format
assert_equal "https://app.onelogin.com/trust/saml2/http-post/sso/383123", @settings.idp_sso_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", @settings.idp_sso_service_binding
assert_nil @settings.idp_slo_service_url
# TODO: next line can be changed to `assert_nil @settings.idp_slo_service_binding` after :embed_sign is removed.
assert_nil @settings.instance_variable_get('@idp_slo_service_binding')
end
end
describe "metadata with different singlelogout response location" do
it "should return the responselocation if it exists" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse(idp_different_slo_response_location)
assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_equal "https://hello.example.com/access/saml/logout/return", settings.idp_slo_response_service_url
end
it "should set the responselocation to nil if it doesnt exist" do
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse(idp_without_slo_response_location)
assert_equal "https://hello.example.com/access/saml/logout", settings.idp_slo_service_url
assert_equal "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect", settings.idp_slo_service_binding
assert_nil settings.idp_slo_response_service_url
end
end
end

View File

@ -0,0 +1,62 @@
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
require 'onelogin/ruby-saml/logging'
class LoggingTest < Minitest::Test
describe "Logging" do
before do
OneLogin::RubySaml::Logging.logger = nil
end
after do
OneLogin::RubySaml::Logging.logger = ::TEST_LOGGER
end
describe "given no specific logging setup" do
it "prints to stdout" do
OneLogin::RubySaml::Logging::DEFAULT_LOGGER.expects(:debug).with('hi mom')
OneLogin::RubySaml::Logging.debug('hi mom')
end
end
describe "given a Rails app" do
let(:logger) { mock('Logger') }
before do
::Rails = mock('Rails module')
::Rails.stubs(:logger).returns(logger)
end
after do
Object.instance_eval { remove_const(:Rails) }
end
it "delegates to Rails" do
logger.expects(:debug).with('hi mom')
logger.expects(:info).with('sup?')
OneLogin::RubySaml::Logging.debug('hi mom')
OneLogin::RubySaml::Logging.info('sup?')
end
end
describe "given a specific Logger" do
let(:logger) { mock('Logger') }
before { OneLogin::RubySaml::Logging.logger = logger }
after do
OneLogin::RubySaml::Logging.logger = ::TEST_LOGGER
end
it "delegates to the object" do
logger.expects(:debug).with('hi mom')
logger.expects(:info).with('sup?')
OneLogin::RubySaml::Logging.debug('hi mom')
OneLogin::RubySaml::Logging.info('sup?')
end
end
end
end

View File

@ -0,0 +1,6 @@
<samlp:LogoutRequest Version='2.0' ID='_c0348950-935b-0131-1060-782bcb56fcaa' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' IssueInstant='2014-03-21T19:20:13'>
<saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>someone@example.org</saml:NameID>
<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>someone2@example.org</saml:NameID>
</samlp:LogoutRequest>

View File

@ -0,0 +1,4 @@
<samlp:LogoutRequest Version='2.0' ID='_c0348950-935b-0131-1060-782bcb56fcaa' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' IssueInstant='2014-03-21T19:20:13'>
<saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>someone@example.org</saml:NameID>
</samlp:LogoutRequest>

View File

@ -0,0 +1 @@
PHNhbWxwOkxvZ291dFJlcXVlc3QgVmVyc2lvbj0nMi4wJyBJRD0nX2MwMzQ4OTUwLTkzNWItMDEzMS0xMDYwLTc4MmJjYjU2ZmNhYScgeG1sbnM6c2FtbHA9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpwcm90b2NvbCcgSXNzdWVJbnN0YW50PScyMDE0LTAzLTIxVDE5OjIwOjEzJz4NCiAgPHNhbWw6SXNzdWVyIHhtbG5zOnNhbWw9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24nPmh0dHBzOi8vYXBwLm9uZWxvZ2luLmNvbS9zYW1sL21ldGFkYXRhL1NPTUVBQ0NPVU5UPC9zYW1sOklzc3Vlcj4NCiAgPHNhbWw6TmFtZUlEIHhtbG5zOnNhbWw9J3VybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDphc3NlcnRpb24nPnNvbWVvbmVAZXhhbXBsZS5vcmc8L3NhbWw6TmFtZUlEPg0KPC9zYW1scDpMb2dvdXRSZXF1ZXN0Pg==

View File

@ -0,0 +1 @@
ndG7asMwFIDhvdB38KZJlmTHaSxs05B0COQCTdq1yKrqGqxLdWTI41dJOpgOHToKDv93DqpA6MHxre3sGJ7V16ggJK/KQ29NjbKUomSzrtGbpPlsURYUl3nRYspyhhmdU/ywyFrZFvMPKQRKznowwK/JGo3ecCugB26EVsCD5MflbstjlDtvg5V2iHWAUW0MBGFCBCmbYZrjjJ1YyTPKWY6a+7skqS5Rfh32E+ZvRQAoH+IlqPkMwQEnRDiXWqMG2/UmlVaTS4VoFcS7CIIcD7un5Wp1eNmfKjIhJzvsI7NZ/2cHsFpF+1GdhXaDSq3vfpBbMyK396//aL4B

View File

@ -0,0 +1,4 @@
<samlp:LogoutRequest Version='2.0' ID='_c0348950-935b-0131-1060-782bcb56fcaa' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' IssueInstant='2014-03-21T19:20:13'>
<saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'>someone@example.org</saml:NameID>
</samlp:LogoutRequest>

View File

@ -0,0 +1,5 @@
<samlp:LogoutRequest Version='2.0' ID='_c0348950-935b-0131-1060-782bcb56fcaa' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol' IssueInstant='2014-03-21T19:20:13'>
<saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>https://app.onelogin.com/saml/metadata/SOMEACCOUNT</saml:Issuer>
<saml:NameID xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>someone@example.org</saml:NameID>
<samlp:SessionIndex>_ea853497-c58a-408a-bc23-c849752d9741</samlp:SessionIndex>
</samlp:LogoutRequest>

View File

@ -0,0 +1,86 @@
#encoding: utf-8
def default_logout_response_opts
{
:uuid => "_28024690-000e-0130-b6d2-38f6b112be8b",
:issue_instant => Time.now.strftime('%Y-%m-%dT%H:%M:%SZ'),
:settings => settings
}
end
def valid_logout_response_document(opts = {})
opts = default_logout_response_opts.merge(opts)
"<samlp:LogoutResponse
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
ID=\"#{random_id}\" Version=\"2.0\"
IssueInstant=\"#{opts[:issue_instant]}\"
Destination=\"#{opts[:settings].single_logout_service_url}\"
InResponseTo=\"#{opts[:uuid]}\">
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\">
</samlp:StatusCode>
</samlp:Status>
</samlp:LogoutResponse>"
end
def unsuccessful_logout_response_document(opts = {})
opts = default_logout_response_opts.merge(opts)
"<samlp:LogoutResponse
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
ID=\"#{random_id}\" Version=\"2.0\"
IssueInstant=\"#{opts[:issue_instant]}\"
Destination=\"#{opts[:settings].single_logout_service_url}\"
InResponseTo=\"#{opts[:uuid]}\">
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
</samlp:StatusCode>
</samlp:Status>
</samlp:LogoutResponse>"
end
def unsuccessful_logout_response_with_message_document(opts = {})
opts = default_logout_response_opts.merge(opts)
"<samlp:LogoutResponse
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
ID=\"#{random_id}\" Version=\"2.0\"
IssueInstant=\"#{opts[:issue_instant]}\"
Destination=\"#{opts[:settings].single_logout_service_url}\"
InResponseTo=\"#{opts[:uuid]}\">
<saml:Issuer xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\">#{opts[:settings].issuer}</saml:Issuer>
<samlp:Status xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">
<samlp:StatusCode xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
Value=\"urn:oasis:names:tc:SAML:2.0:status:Requester\">
</samlp:StatusCode>
<samlp:StatusMessage>Logoutrequest expired</samlp:StatusMessage>
</samlp:Status>
</samlp:LogoutResponse>"
end
def invalid_xml_logout_response_document
"<samlp:SomethingAwful
xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\"
ID=\"#{random_id}\" Version=\"2.0\">
</samlp:SomethingAwful>"
end
def settings
@settings ||= OneLogin::RubySaml::Settings.new(
{
:assertion_consumer_service_url => "http://app.muda.no/sso/consume",
:single_logout_service_url => "http://app.muda.no/sso/consume_logout",
:issuer => "http://app.muda.no",
:sp_name_qualifier => "http://sso.muda.no",
:idp_sso_service_url => "http://sso.muda.no/sso",
:idp_slo_service_url => "http://sso.muda.no/slo",
:idp_cert_fingerprint => "00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00:00",
:name_identifier_format => "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
}
)
end

View File

@ -0,0 +1,338 @@
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
require 'onelogin/ruby-saml/logoutrequest'
class RequestTest < Minitest::Test
describe "Logoutrequest" do
let(:settings) { OneLogin::RubySaml::Settings.new }
before do
settings.idp_slo_service_url = "http://unauth.com/logout"
settings.name_identifier_value = "f00f00"
end
it "create the deflated SAMLRequest URL parameter" do
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
assert_match /^http:\/\/unauth\.com\/logout\?SAMLRequest=/, unauth_url
inflated = decode_saml_request_payload(unauth_url)
assert_match /^<samlp:LogoutRequest/, inflated
end
it "support additional params" do
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :hello => nil })
assert_match /&hello=$/, unauth_url
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :foo => "bar" })
assert_match /&foo=bar$/, unauth_url
end
it "RelayState cases" do
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => nil })
assert !unauth_url.include?('RelayState')
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :RelayState => "http://example.com" })
assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => nil })
assert !unauth_url.include?('RelayState')
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { 'RelayState' => "http://example.com" })
assert unauth_url.include?('&RelayState=http%3A%2F%2Fexample.com')
end
it "set sessionindex" do
settings.idp_slo_service_url = "http://example.com"
sessionidx = OneLogin::RubySaml::Utils.uuid
settings.sessionindex = sessionidx
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" })
inflated = decode_saml_request_payload(unauth_url)
assert_match /<samlp:SessionIndex/, inflated
assert_match %r(#{sessionidx}</samlp:SessionIndex>), inflated
end
it "set name_identifier_value" do
settings.name_identifier_format = "transient"
name_identifier_value = "abc123"
settings.name_identifier_value = name_identifier_value
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings, { :nameid => "there" })
inflated = decode_saml_request_payload(unauth_url)
assert_match /<saml:NameID/, inflated
assert_match %r(#{name_identifier_value}</saml:NameID>), inflated
end
describe "when the target url doesn't contain a query string" do
it "create the SAMLRequest parameter correctly" do
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
assert_match /^http:\/\/unauth.com\/logout\?SAMLRequest/, unauth_url
end
end
describe "when the target url contains a query string" do
it "create the SAMLRequest parameter correctly" do
settings.idp_slo_service_url = "http://example.com?field=value"
unauth_url = OneLogin::RubySaml::Logoutrequest.new.create(settings)
assert_match /^http:\/\/example.com\?field=value&SAMLRequest/, unauth_url
end
end
describe "consumation of logout may need to track the transaction" do
it "have access to the request uuid" do
settings.idp_slo_service_url = "http://example.com?field=value"
unauth_req = OneLogin::RubySaml::Logoutrequest.new
unauth_url = unauth_req.create(settings)
inflated = decode_saml_request_payload(unauth_url)
assert_match %r[ID='#{unauth_req.uuid}'], inflated
end
end
describe "playgin with preix" do
it "creates request with ID prefixed with default '_'" do
request = OneLogin::RubySaml::Logoutrequest.new
assert_match /^_/, request.uuid
end
it "creates request with ID is prefixed, when :id_prefix is passed" do
OneLogin::RubySaml::Utils::set_prefix("test")
request = OneLogin::RubySaml::Logoutrequest.new
assert_match /^test/, request.uuid
OneLogin::RubySaml::Utils::set_prefix("_")
end
end
describe "signing with HTTP-POST binding" do
before do
settings.security[:logout_requests_signed] = true
settings.idp_slo_service_binding = :post
settings.idp_sso_service_binding = :redirect
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
end
it "doesn't sign through create_xml_document" do
unauth_req = OneLogin::RubySaml::Logoutrequest.new
inflated = unauth_req.create_xml_document(settings).to_s
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
end
it "sign unsigned request" do
unauth_req = OneLogin::RubySaml::Logoutrequest.new
unauth_req_doc = unauth_req.create_xml_document(settings)
inflated = unauth_req_doc.to_s
refute_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
refute_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
refute_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
inflated = unauth_req.sign_document(unauth_req_doc, settings).to_s
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
end
it "signs through create_logout_request_xml_doc" do
unauth_req = OneLogin::RubySaml::Logoutrequest.new
inflated = unauth_req.create_logout_request_xml_doc(settings).to_s
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
end
it "created a signed logout request" do
settings.compress_request = true
unauth_req = OneLogin::RubySaml::Logoutrequest.new
unauth_url = unauth_req.create(settings)
inflated = decode_saml_request_payload(unauth_url)
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
end
it "create a signed logout request with 256 digest and signature method" do
settings.compress_request = false
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
settings.security[:digest_method] = XMLSecurity::Document::SHA256
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
request_xml = Base64.decode64(params["SAMLRequest"])
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'/>], request_xml
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha256'/>], request_xml
end
it "create a signed logout request with 512 digest and signature method RSA_SHA384" do
settings.compress_request = false
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
settings.security[:digest_method] = XMLSecurity::Document::SHA512
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings)
request_xml = Base64.decode64(params["SAMLRequest"])
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], request_xml
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2001/04/xmldsig-more#rsa-sha384'/>], request_xml
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2001/04/xmlenc#sha512'/>], request_xml
end
end
describe "signing with HTTP-Redirect binding" do
let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
before do
settings.security[:logout_requests_signed] = true
settings.idp_slo_service_binding = :redirect
settings.idp_sso_service_binding = :post
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
end
it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
assert params['SAMLRequest']
assert params[:RelayState]
assert params['Signature']
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
end
it "create a signature parameter with RSA_SHA256 / SHA256 and validate it" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
assert params['Signature']
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA256
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
assert_equal signature_algorithm, OpenSSL::Digest::SHA256
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
end
it "create a signature parameter with RSA_SHA384 / SHA384 and validate it" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA384
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
assert params['Signature']
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA384
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
assert_equal signature_algorithm, OpenSSL::Digest::SHA384
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
end
it "create a signature parameter with RSA_SHA512 / SHA512 and validate it" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA512
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
assert params['Signature']
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA512
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
assert_equal signature_algorithm, OpenSSL::Digest::SHA512
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
end
end
describe "DEPRECATED: signing with HTTP-POST binding via :embed_sign" do
before do
# sign the logout request
settings.security[:logout_requests_signed] = true
settings.security[:embed_sign] = true
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
end
it "created a signed logout request" do
settings.compress_request = true
unauth_req = OneLogin::RubySaml::Logoutrequest.new
unauth_url = unauth_req.create(settings)
inflated = decode_saml_request_payload(unauth_url)
assert_match %r[<ds:SignatureValue>([a-zA-Z0-9/+=]+)</ds:SignatureValue>], inflated
assert_match %r[<ds:SignatureMethod Algorithm='http://www.w3.org/2000/09/xmldsig#rsa-sha1'/>], inflated
assert_match %r[<ds:DigestMethod Algorithm='http://www.w3.org/2000/09/xmldsig#sha1'/>], inflated
end
end
describe "DEPRECATED: signing with HTTP-Redirect binding via :embed_sign" do
let(:cert) { OpenSSL::X509::Certificate.new(ruby_saml_cert_text) }
before do
settings.security[:logout_requests_signed] = true
settings.security[:embed_sign] = false
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
end
it "create a signature parameter with RSA_SHA1 / SHA1 and validate it" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params = OneLogin::RubySaml::Logoutrequest.new.create_params(settings, :RelayState => 'http://example.com')
assert params['SAMLRequest']
assert params[:RelayState]
assert params['Signature']
assert_equal params['SigAlg'], XMLSecurity::Document::RSA_SHA1
query_string = "SAMLRequest=#{CGI.escape(params['SAMLRequest'])}"
query_string << "&RelayState=#{CGI.escape(params[:RelayState])}"
query_string << "&SigAlg=#{CGI.escape(params['SigAlg'])}"
signature_algorithm = XMLSecurity::BaseDocument.new.algorithm(params['SigAlg'])
assert_equal signature_algorithm, OpenSSL::Digest::SHA1
assert cert.public_key.verify(signature_algorithm.new, Base64.decode64(params['Signature']), query_string)
end
end
describe "#manipulate request_id" do
it "be able to modify the request id" do
logoutrequest = OneLogin::RubySaml::Logoutrequest.new
request_id = logoutrequest.request_id
assert_equal request_id, logoutrequest.uuid
logoutrequest.uuid = "new_uuid"
assert_equal logoutrequest.request_id, logoutrequest.uuid
assert_equal "new_uuid", logoutrequest.request_id
end
end
end
end

View File

@ -0,0 +1,425 @@
require File.expand_path(File.join(File.dirname(__FILE__), "test_helper"))
require 'onelogin/ruby-saml/logoutresponse'
require 'logout_responses/logoutresponse_fixtures'
class RubySamlTest < Minitest::Test
describe "Logoutresponse" do
let(:valid_logout_response_without_settings) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document) }
let(:valid_logout_response) { OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings) }
describe "#new" do
it "raise an exception when response is initialized with nil" do
assert_raises(ArgumentError) { OneLogin::RubySaml::Logoutresponse.new(nil) }
end
it "default to empty settings" do
assert_nil valid_logout_response_without_settings.settings
end
it "accept constructor-injected settings" do
refute_nil valid_logout_response.settings
end
it "accept constructor-injected options" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, nil, { :foo => :bar} )
assert !logoutresponse.options.empty?
end
it "support base64 encoded responses" do
generated_logout_response = valid_logout_response_document
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(Base64.encode64(generated_logout_response), settings)
assert_equal generated_logout_response, logoutresponse.response
end
end
describe "#validate_structure" do
it "invalidates when the logout response has an invalid xml" do
settings.soft = true
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_logout_response_document, settings)
assert !logoutresponse.send(:validate_structure)
assert_includes logoutresponse.errors, "Invalid SAML Logout Response. Not match the saml-schema-protocol-2.0.xsd"
end
it "raise when the logout response has an invalid xml" do
settings.soft = false
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(invalid_xml_logout_response_document, settings)
assert_raises OneLogin::RubySaml::ValidationError do
logoutresponse.send(:validate_structure)
end
end
end
describe "#validate" do
describe "when soft=true" do
before do
settings.soft = true
end
it "validate the logout response" do
in_relation_to_request_id = random_id
opts = { :matches_request_id => in_relation_to_request_id}
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings, opts)
assert logoutresponse.validate
assert_equal settings.sp_entity_id, logoutresponse.issuer
assert_equal in_relation_to_request_id, logoutresponse.in_response_to
assert logoutresponse.success?
assert_empty logoutresponse.errors
end
it "validate the logout response extended" do
in_relation_to_request_id = random_id
settings.idp_entity_id = 'http://app.muda.no'
opts = { :matches_request_id => in_relation_to_request_id}
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings, opts)
assert logoutresponse.validate
assert_equal in_relation_to_request_id, logoutresponse.in_response_to
assert logoutresponse.success?
assert_empty logoutresponse.errors
end
it "invalidate logout response when initiated with blank" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new("", settings)
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "Blank logout response"
end
it "invalidate logout response when initiated with no idp cert or fingerprint" do
settings.idp_cert_fingerprint = nil
settings.idp_cert = nil
settings.idp_cert_multi = nil
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings)
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "No fingerprint or certificate on settings of the logout response"
end
it "invalidate logout response with wrong id when given option :matches_request_id" do
expected_request_id = "_some_other_expected_uuid"
opts = { :matches_request_id => expected_request_id}
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings, opts)
assert !logoutresponse.validate
refute_equal expected_request_id, logoutresponse.in_response_to
assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}"
end
it "invalidate logout response with unexpected request status" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
assert !logoutresponse.success?
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
end
it "invalidate logout response with unexpected request status and status message" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_with_message_document, settings)
assert !logoutresponse.success?
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester -> Logoutrequest expired"
end
it "invalidate logout response when in lack of sp_entity_id setting" do
bad_settings = settings
bad_settings.issuer = nil
bad_settings.sp_entity_id = nil
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, bad_settings)
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response"
end
it "invalidate logout response with wrong issuer" do
in_relation_to_request_id = random_id
settings.idp_entity_id = 'http://invalid.issuer.example.com/'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings)
assert !logoutresponse.validate
assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
end
it "collect errors when collect_errors=true" do
settings.idp_entity_id = 'http://invalid.issuer.example.com/'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
collect_errors = true
assert !logoutresponse.validate(collect_errors)
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
end
end
describe "when soft=false" do
before do
settings.soft = false
end
it "validates good logout response" do
in_relation_to_request_id = random_id
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings)
assert logoutresponse.validate
assert_empty logoutresponse.errors
end
it "raises validation error when response initiated with blank" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new("", settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "Blank logout response"
end
it "raises validation error when initiated with no idp cert or fingerprint" do
settings.idp_cert_fingerprint = nil
settings.idp_cert = nil
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "No fingerprint or certificate on settings of the logout response"
end
it "raises validation error when matching for wrong request id" do
expected_request_id = "_some_other_expected_id"
opts = { :matches_request_id => expected_request_id}
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document, settings, opts)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "The InResponseTo of the Logout Response: #{logoutresponse.in_response_to}, does not match the ID of the Logout Request sent by the SP: #{expected_request_id}"
end
it "raise validation error for wrong request status" do
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
end
it "raise validation error when in bad state" do
# no settings
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "The status code of the Logout Response was not Success, was Requester"
end
it "raise validation error when in lack of sp_entity_id setting" do
settings.issuer = nil
settings.sp_entity_id = nil
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(unsuccessful_logout_response_document, settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "No sp_entity_id in settings of the logout response"
end
it "raise validation error when logout response with wrong issuer" do
in_relation_to_request_id = random_id
settings.idp_entity_id = 'http://invalid.issuer.example.com/'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(valid_logout_response_document({:uuid => in_relation_to_request_id}), settings)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.validate }
assert_includes logoutresponse.errors, "Doesn't match the issuer, expected: <#{logoutresponse.settings.idp_entity_id}>, but was: <http://app.muda.no>"
end
end
describe "#validate_signature" do
let (:params) { OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com') }
before do
settings.soft = true
settings.idp_slo_service_url = "http://example.com?field=value"
settings.security[:logout_responses_signed] = true
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
settings.idp_cert = ruby_saml_cert_text
end
it "return true when no idp_cert is provided and option :relax_signature_validation is present" do
settings.idp_cert = nil
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
options[:relax_signature_validation] = true
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert logoutresponse_sign_test.send(:validate_signature)
end
it "return false when no idp_cert is provided and no option :relax_signature_validation is present" do
settings.idp_cert = nil
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert !logoutresponse_sign_test.send(:validate_signature)
end
it "return true when valid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert logoutresponse_sign_test.send(:validate_signature)
end
it "return true when valid RSA_SHA256 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA256
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert logoutresponse.send(:validate_signature)
end
it "return false when invalid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
params['RelayState'] = 'http://invalid.example.com'
options = {}
options[:get_params] = params
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert !logoutresponse.send(:validate_signature)
end
it "raise when invalid RSA_SHA1 Signature" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.soft = false
params['RelayState'] = 'http://invalid.example.com'
options = {}
options[:get_params] = params
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert_raises(OneLogin::RubySaml::ValidationError) { logoutresponse.send(:validate_signature) }
assert logoutresponse.errors.include? "Invalid Signature on Logout Response"
end
it "raise when get_params encoding differs from what this library generates" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.soft = false
options = {}
options[:get_params] = params
options[:get_params]['RelayState'] = 'http://example.com'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
# Assemble query string.
query = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLResponse',
:data => params['SAMLResponse'],
:relay_state => params['RelayState'],
:sig_alg => params['SigAlg']
)
# Modify the query string so that it encodes the same values,
# but with different percent-encoding. Sanity-check that they
# really are equialent before moving on.
original_query = query.dup
query.gsub!("example", "ex%61mple")
refute_equal(query, original_query)
assert_equal(CGI.unescape(query), CGI.unescape(original_query))
# Make normalised signature based on our modified params.
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, query)
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
# Re-create the Logoutresponse based on these modified parameters,
# and ask it to validate the signature. It will do it incorrectly,
# because it will compute it based on re-encoded query parameters,
# rather than their original encodings.
options[:get_params] = params
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert_raises(OneLogin::RubySaml::ValidationError, "Invalid Signature on Logout Request") do
logoutresponse.send(:validate_signature)
end
end
it "return true even if raw_get_params encoding differs from what this library generates" do
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.soft = false
options = {}
options[:get_params] = params
options[:get_params]['RelayState'] = 'http://example.com'
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
# Assemble query string.
query = OneLogin::RubySaml::Utils.build_query(
:type => 'SAMLResponse',
:data => params['SAMLResponse'],
:relay_state => params['RelayState'],
:sig_alg => params['SigAlg']
)
# Modify the query string so that it encodes the same values,
# but with different percent-encoding. Sanity-check that they
# really are equialent before moving on.
original_query = query.dup
query.gsub!("example", "ex%61mple")
refute_equal(query, original_query)
assert_equal(CGI.unescape(query), CGI.unescape(original_query))
# Make normalised signature based on our modified params.
sign_algorithm = XMLSecurity::BaseDocument.new.algorithm(settings.security[:signature_method])
signature = settings.get_sp_key.sign(sign_algorithm.new, query)
params['Signature'] = Base64.encode64(signature).gsub(/\n/, "")
# Re-create the Logoutresponse based on these modified parameters,
# and ask it to validate the signature. Provide the altered parameter
# in its raw URI-encoded form, so that we don't have to guess the value
# that contributed to the signature.
options[:get_params] = params
options[:get_params].delete("RelayState")
options[:raw_get_params] = {
"RelayState" => "http%3A%2F%2Fex%61mple.com",
}
logoutresponse = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert logoutresponse.send(:validate_signature)
end
end
describe "#validate_signature" do
let (:params) { OneLogin::RubySaml::SloLogoutresponse.new.create_params(settings, random_id, "Custom Logout Message", :RelayState => 'http://example.com') }
before do
settings.soft = true
settings.idp_slo_service_url = "http://example.com?field=value"
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
settings.security[:logout_responses_signed] = true
settings.certificate = ruby_saml_cert_text
settings.private_key = ruby_saml_key_text
settings.idp_cert = nil
end
it "return true when at least a idp_cert is valid" do
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
settings.idp_cert_multi = {
:signing => [ruby_saml_cert_text2, ruby_saml_cert_text],
:encryption => []
}
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert logoutresponse_sign_test.send(:validate_signature)
end
it "return false when cert expired and check_idp_cert_expiration expired" do
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
settings.security[:check_idp_cert_expiration] = true
settings.idp_cert = nil
settings.idp_cert_multi = {
:signing => [ruby_saml_cert_text],
:encryption => []
}
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert !logoutresponse_sign_test.send(:validate_signature)
assert_includes logoutresponse_sign_test.errors, "IdP x509 certificate expired"
end
it "return false when none cert on idp_cert_multi is valid" do
params['RelayState'] = params[:RelayState]
options = {}
options[:get_params] = params
settings.idp_cert_multi = {
:signing => [ruby_saml_cert_text2, ruby_saml_cert_text2],
:encryption => []
}
logoutresponse_sign_test = OneLogin::RubySaml::Logoutresponse.new(params['SAMLResponse'], settings, options)
assert !logoutresponse_sign_test.send(:validate_signature)
assert_includes logoutresponse_sign_test.errors, "Invalid Signature on Logout Response"
end
end
end
end
end

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,56 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,18 @@
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:ds="http://www.w3.org/2000/09/xmldsig#" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" entityID="https://idp.example.com/idp/shibboleth">
<md:IDPSSODescriptor protocolSupportEnumeration="urn:mace:shibboleth:1.0 urn:oasis:names:tc:SAML:1.1:protocol urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.example.com/idp/profile/SAML2/Redirect/SLO"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:mace:shibboleth:1.0:profiles:AuthnRequest" Location="https://idp.example.com/idp/profile/Shibboleth/SSO"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://idp.example.com/idp/profile/SAML2/POST/SSO"/>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://idp.example.com/idp/profile/SAML2/Redirect/SSO"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" cacheDuration="P1DT" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,72 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2020-01-04T18:02:33.910Z" cacheDuration="P2DT" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="UTF-8"?>
<md:EntityDescriptor entityID="https://hello.example.com/access/saml/idp.xml" validUntil="2014-04-17T18:02:33.910Z" xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata">
<md:IDPSSODescriptor WantAuthnRequestsSigned="true" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<md:KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF4T1RJeU5UUXhPRm9YRFRNeU1EUXgKTkRJeU5UUXhPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXFqaWUzUjJvaStwRGFldndJeXMvbWJVVApubkdsa3h0ZGlrcnExMXZleHd4SmlQTmhtaHFSVzNtVXVKRXpsbElkVkw2RW14R1lUcXBxZjkzSGxoa3NhZUowCjhVZ2pQOVVtTVlyaFZKdTFqY0ZXVjdmei9yKzIxL2F3VG5EVjlzTVlRcXVJUllZeTdiRzByMU9iaXdkb3ZudGsKN2dGSTA2WjB2WmFjREU1Ym9xVUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJTUk9OOEdKOG8rOGpnRnRqa3R3WmRxeDZCUnlUQVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVrVGpmQmlmS1B2STRCYlk1TGNHWGFzZWdVY21oZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FDRQpUQWVKVERTQVc2ejFVRlRWN1FyZWg0VUxGT1JhajkrZUN1RjNLV0RIYyswSVFDajlyZG5ERzRRL3dmNy9yYVEwCkpuUFFDU0NkclBMSmV5b1BIN1FhVHdvYUY3ZHpWdzRMQ3N5TkpURld4NGNNNTBWdzZSNWZET2dpQzhic2ZmUzgKQkptb3VscnJaRE5OVmpHOG1XNmNMeHJZdlZRT3JSVmVjQ0ZJZ3NzQ2JBPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</md:KeyDescriptor>
<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/logout" ResponseLocation="https://hello.example.com/access/saml/logout/return"/>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</md:NameIDFormat>
<md:NameIDFormat>urn:oasis:names:tc:SAML:2.0:nameid-format:persistent</md:NameIDFormat>
<md:SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://hello.example.com/access/saml/login"/>
<saml:Attribute Name="AuthToken" NameFormat="urn:oasis:names:tc:SAML:2.0:att rname-format:basic" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
<saml:Attribute Name="SSOStartPage" NameFormat="urn:oasis:names:tc:SAML:2.0:attrname-format:uri" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion"/>
</md:IDPSSODescriptor>
</md:EntityDescriptor>

View File

@ -0,0 +1,72 @@
<?xml version="1.0"?>
<EntityDescriptor xmlns="urn:oasis:names:tc:SAML:2.0:metadata" entityID="https://app.onelogin.com/saml/metadata/383123">
<IDPSSODescriptor xmlns:ds="http://www.w3.org/2000/09/xmldsig#" protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
<KeyDescriptor use="signing">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET
MBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD
VQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2
MDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI
DApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u
ZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B
AQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z
0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT
gf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m
Tr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF
zRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ
UAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG
A1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV
HSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV
UzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw
DwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO
BgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu
AuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV
gG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ
sTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP
TbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu
QOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78
1sE=</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<KeyDescriptor use="encryption">
<ds:KeyInfo xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:X509Data>
<ds:X509Certificate>MIIEZTCCA02gAwIBAgIUPyy/A3bZAZ4m28PzEUUoT7RJhxIwDQYJKoZIhvcNAQEF
BQAwcjELMAkGA1UEBhMCVVMxKzApBgNVBAoMIk9uZUxvZ2luIFRlc3QgKHNnYXJj
aWEtdXMtcHJlcHJvZCkxFTATBgNVBAsMDE9uZUxvZ2luIElkUDEfMB0GA1UEAwwW
T25lTG9naW4gQWNjb3VudCA4OTE0NjAeFw0xNjA4MDQyMjI5MzdaFw0yMTA4MDUy
MjI5MzdaMHIxCzAJBgNVBAYTAlVTMSswKQYDVQQKDCJPbmVMb2dpbiBUZXN0IChz
Z2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQLDAxPbmVMb2dpbiBJZFAxHzAdBgNV
BAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDYwggEiMA0GCSqGSIb3DQEBAQUAA4IB
DwAwggEKAoIBAQDN6iqQGcLOCglNO42I2rkzE05UXSiMXT6c8ALThMMiaDw6qqzo
3sd/tKK+NcNKWLIIC8TozWVyh5ykUiVZps+08xil7VsTU7E+wKu3kvmOsvw2wlRw
tnoKZJwYhnr+RkBa+h1r3ZYUgXm1ZPeHMKj1g18KaWz9+MxYL6BhKqrOzfW/P2xx
VRcFH7/pq+ZsDdgNzD2GD+apzY4MZyZj/N6BpBWJ0GlFsmtBegpbX3LBitJuFkk5
L4/U/jjF1AJa3boBdCUVfATqO5G03H4XS1GySjBIRQXmlUF52rLjg6xCgWJ30/+t
1X+IHLJeixiQ0vxyh6C4/usCEt94cgD1r8ADAgMBAAGjgfIwge8wDAYDVR0TAQH/
BAIwADAdBgNVHQ4EFgQUPW0DcH0G3IwynWgi74co4wZ6n7gwga8GA1UdIwSBpzCB
pIAUPW0DcH0G3IwynWgi74co4wZ6n7ihdqR0MHIxCzAJBgNVBAYTAlVTMSswKQYD
VQQKDCJPbmVMb2dpbiBUZXN0IChzZ2FyY2lhLXVzLXByZXByb2QpMRUwEwYDVQQL
DAxPbmVMb2dpbiBJZFAxHzAdBgNVBAMMFk9uZUxvZ2luIEFjY291bnQgODkxNDaC
FD8svwN22QGeJtvD8xFFKE+0SYcSMA4GA1UdDwEB/wQEAwIHgDANBgkqhkiG9w0B
AQUFAAOCAQEAQhB4q9jrycwbHrDSoYR1X4LFFzvJ9Us75wQquRHXpdyS9D6HUBXM
GI6ahPicXCQrfLgN8vzMIiqZqfySXXv/8/dxe/X4UsWLYKYJHDJmxXD5EmWTa65c
hjkeP1oJAc8f3CKCpcP2lOBTthbnk2fEVAeLHR4xNdQO0VvGXWO9BliYPpkYqUIB
vlm+Fg9mF7AM/Uagq2503XXIE1Lq//HON68P10vNMwLSKOtYLsoTiCnuIKGJqG37
MsZVjQ1ZPRcO+LSLkq0i91gFxrOrVCrgztX4JQi5XkvEsYZGIXXjwHqxTVyt3adZ
WQO0LPxPqRiUqUzyhDhLo/xXNrHCu4VbMw==</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</KeyDescriptor>
<NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress</NameIDFormat>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://app.onelogin.com/trust/saml2/http-post/sso/383123"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://app.onelogin.com/trust/saml2/http-post/sso/383123"/>
<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:SOAP" Location="https://app.onelogin.com/trust/saml2/soap/sso/383123"/>
</IDPSSODescriptor>
<ContactPerson contactType="technical">
<SurName>Support</SurName>
<EmailAddress>support@onelogin.com</EmailAddress>
</ContactPerson>
</EntityDescriptor>

Some files were not shown because too many files have changed in this diff Show More