saml_idp 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +15 -0
- data/Gemfile +2 -0
- data/LICENSE +22 -0
- data/README.md +197 -0
- data/app/controllers/saml_idp/idp_controller.rb +42 -0
- data/app/views/saml_idp/idp/new.html.erb +21 -0
- data/app/views/saml_idp/idp/saml_post.html.erb +13 -0
- data/lib/saml_idp.rb +92 -0
- data/lib/saml_idp/algorithmable.rb +19 -0
- data/lib/saml_idp/assertion_builder.rb +144 -0
- data/lib/saml_idp/attribute_decorator.rb +27 -0
- data/lib/saml_idp/attributeable.rb +24 -0
- data/lib/saml_idp/configurator.rb +45 -0
- data/lib/saml_idp/controller.rb +71 -0
- data/lib/saml_idp/default.rb +28 -0
- data/lib/saml_idp/engine.rb +5 -0
- data/lib/saml_idp/hashable.rb +26 -0
- data/lib/saml_idp/incoming_metadata.rb +144 -0
- data/lib/saml_idp/metadata_builder.rb +158 -0
- data/lib/saml_idp/name_id_formatter.rb +68 -0
- data/lib/saml_idp/persisted_metadata.rb +10 -0
- data/lib/saml_idp/request.rb +79 -0
- data/lib/saml_idp/response_builder.rb +60 -0
- data/lib/saml_idp/saml_response.rb +63 -0
- data/lib/saml_idp/service_provider.rb +68 -0
- data/lib/saml_idp/signable.rb +131 -0
- data/lib/saml_idp/signature_builder.rb +42 -0
- data/lib/saml_idp/signed_info_builder.rb +51 -0
- data/lib/saml_idp/version.rb +4 -0
- data/lib/saml_idp/xml_security.rb +168 -0
- data/saml_idp.gemspec +38 -0
- data/spec/acceptance/acceptance_helper.rb +9 -0
- data/spec/acceptance/idp_controller_spec.rb +16 -0
- data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
- data/spec/lib/saml_idp/assertion_builder_spec.rb +27 -0
- data/spec/lib/saml_idp/attribute_decorator_spec.rb +31 -0
- data/spec/lib/saml_idp/configurator_spec.rb +30 -0
- data/spec/lib/saml_idp/controller_spec.rb +51 -0
- data/spec/lib/saml_idp/metadata_builder_spec.rb +9 -0
- data/spec/lib/saml_idp/name_id_formatter_spec.rb +39 -0
- data/spec/lib/saml_idp/request_spec.rb +14 -0
- data/spec/lib/saml_idp/response_builder_spec.rb +32 -0
- data/spec/lib/saml_idp/saml_response_spec.rb +27 -0
- data/spec/lib/saml_idp/service_provider_spec.rb +21 -0
- data/spec/lib/saml_idp/signable_spec.rb +74 -0
- data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
- data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
- data/spec/rails_app/.gitignore +15 -0
- data/spec/rails_app/README.rdoc +261 -0
- data/spec/rails_app/Rakefile +7 -0
- data/spec/rails_app/app/assets/images/rails.png +0 -0
- data/spec/rails_app/app/assets/javascripts/application.js +15 -0
- data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
- data/spec/rails_app/app/controllers/application_controller.rb +3 -0
- data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
- data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
- data/spec/rails_app/app/helpers/application_helper.rb +2 -0
- data/spec/rails_app/app/mailers/.gitkeep +0 -0
- data/spec/rails_app/app/models/.gitkeep +0 -0
- data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
- data/spec/rails_app/config.ru +4 -0
- data/spec/rails_app/config/application.rb +60 -0
- data/spec/rails_app/config/boot.rb +6 -0
- data/spec/rails_app/config/database.yml +25 -0
- data/spec/rails_app/config/environment.rb +5 -0
- data/spec/rails_app/config/environments/development.rb +37 -0
- data/spec/rails_app/config/environments/production.rb +67 -0
- data/spec/rails_app/config/environments/test.rb +37 -0
- data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/rails_app/config/initializers/inflections.rb +15 -0
- data/spec/rails_app/config/initializers/mime_types.rb +5 -0
- data/spec/rails_app/config/initializers/secret_token.rb +7 -0
- data/spec/rails_app/config/initializers/session_store.rb +8 -0
- data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
- data/spec/rails_app/config/locales/en.yml +5 -0
- data/spec/rails_app/config/routes.rb +6 -0
- data/spec/rails_app/db/seeds.rb +7 -0
- data/spec/rails_app/doc/README_FOR_APP +2 -0
- data/spec/rails_app/lib/assets/.gitkeep +0 -0
- data/spec/rails_app/lib/tasks/.gitkeep +0 -0
- data/spec/rails_app/log/.gitkeep +0 -0
- data/spec/rails_app/public/404.html +26 -0
- data/spec/rails_app/public/422.html +26 -0
- data/spec/rails_app/public/500.html +25 -0
- data/spec/rails_app/public/favicon.ico +0 -0
- data/spec/rails_app/public/index.html +241 -0
- data/spec/rails_app/public/robots.txt +5 -0
- data/spec/rails_app/script/rails +6 -0
- data/spec/rails_app/test/fixtures/.gitkeep +0 -0
- data/spec/rails_app/test/functional/.gitkeep +0 -0
- data/spec/rails_app/test/integration/.gitkeep +0 -0
- data/spec/rails_app/test/performance/browsing_test.rb +12 -0
- data/spec/rails_app/test/test_helper.rb +13 -0
- data/spec/rails_app/test/unit/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
- data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
- data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
- data/spec/spec_helper.rb +49 -0
- data/spec/support/certificates/certificate1 +12 -0
- data/spec/support/certificates/r1_certificate2_base64 +1 -0
- data/spec/support/responses/adfs_response_sha1.xml +46 -0
- data/spec/support/responses/adfs_response_sha256.xml +46 -0
- data/spec/support/responses/adfs_response_sha384.xml +46 -0
- data/spec/support/responses/adfs_response_sha512.xml +46 -0
- data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
- data/spec/support/responses/no_signature_ns.xml +48 -0
- data/spec/support/responses/open_saml_response.xml +56 -0
- data/spec/support/responses/r1_response6.xml.base64 +1 -0
- data/spec/support/responses/response1.xml.base64 +1 -0
- data/spec/support/responses/response2.xml.base64 +79 -0
- data/spec/support/responses/response3.xml.base64 +66 -0
- data/spec/support/responses/response4.xml.base64 +93 -0
- data/spec/support/responses/response5.xml.base64 +102 -0
- data/spec/support/responses/response_with_ampersands.xml +139 -0
- data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
- data/spec/support/responses/simple_saml_php.xml +71 -0
- data/spec/support/responses/starfield_response.xml.base64 +1 -0
- data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
- data/spec/support/saml_request_macros.rb +19 -0
- data/spec/support/security_helpers.rb +61 -0
- data/spec/xml_security_spec.rb +136 -0
- metadata +407 -0
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
require 'delegate'
|
|
2
|
+
module SamlIdp
|
|
3
|
+
class AttributeDecorator < SimpleDelegator
|
|
4
|
+
alias_method :source, :__getobj__
|
|
5
|
+
|
|
6
|
+
def initialize(*)
|
|
7
|
+
super
|
|
8
|
+
__setobj__((source || {}).with_indifferent_access)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def name
|
|
12
|
+
source[:name]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def friendly_name
|
|
16
|
+
source[:friendly_name]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def name_format
|
|
20
|
+
source[:name_format] || Saml::XML::Namespaces::Formats::Attr::URI
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def values
|
|
24
|
+
Array(source[:values])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SamlIdp
|
|
2
|
+
module Attributeable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def initialize(attributes = {})
|
|
6
|
+
self.attributes = attributes
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def attributes
|
|
10
|
+
@attributes ||= {}.with_indifferent_access
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def attributes=(new_attributes)
|
|
14
|
+
@attributes = (new_attributes || {}).with_indifferent_access
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
def attribute(att)
|
|
19
|
+
define_method(att) { attributes[att] }
|
|
20
|
+
define_method("#{att}=") { |new_value| self.attributes[att] = new_value }
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'ostruct'
|
|
3
|
+
module SamlIdp
|
|
4
|
+
class Configurator
|
|
5
|
+
attr_accessor :x509_certificate
|
|
6
|
+
attr_accessor :secret_key
|
|
7
|
+
attr_accessor :algorithm
|
|
8
|
+
attr_accessor :organization_name
|
|
9
|
+
attr_accessor :organization_url
|
|
10
|
+
attr_accessor :base_saml_location
|
|
11
|
+
attr_accessor :reference_id_generator
|
|
12
|
+
attr_accessor :attribute_service_location
|
|
13
|
+
attr_accessor :single_service_post_location
|
|
14
|
+
attr_accessor :attributes
|
|
15
|
+
attr_accessor :service_provider
|
|
16
|
+
|
|
17
|
+
def initialize
|
|
18
|
+
self.x509_certificate = Default::X509_CERTIFICATE
|
|
19
|
+
self.secret_key = Default::SECRET_KEY
|
|
20
|
+
self.algorithm = :sha1
|
|
21
|
+
self.reference_id_generator = ->() { UUID.generate }
|
|
22
|
+
self.service_provider = OpenStruct.new
|
|
23
|
+
self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
|
|
24
|
+
self.service_provider.metadata_persister = ->(id, settings) { }
|
|
25
|
+
self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
|
|
26
|
+
self.attributes = {}
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# formats
|
|
30
|
+
# getter
|
|
31
|
+
def name_id
|
|
32
|
+
@name_id ||= OpenStruct.new
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def technical_contact
|
|
36
|
+
@technical_contact ||= TechnicalContact.new
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
class TechnicalContact < OpenStruct
|
|
40
|
+
def mail_to_string
|
|
41
|
+
"mailto:#{email_address}" if email_address.to_s.length > 0
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'openssl'
|
|
3
|
+
require 'base64'
|
|
4
|
+
require 'time'
|
|
5
|
+
require 'uuid'
|
|
6
|
+
require 'saml_idp/request'
|
|
7
|
+
module SamlIdp
|
|
8
|
+
module Controller
|
|
9
|
+
extend ActiveSupport::Concern
|
|
10
|
+
|
|
11
|
+
included do
|
|
12
|
+
helper_method :saml_acs_url if respond_to? :helper_method
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
attr_accessor :algorithm
|
|
16
|
+
attr_accessor :saml_request
|
|
17
|
+
|
|
18
|
+
protected
|
|
19
|
+
|
|
20
|
+
def validate_saml_request(raw_saml_request = params[:SAMLRequest])
|
|
21
|
+
decode_request(raw_saml_request)
|
|
22
|
+
render nothing: true, status: :forbidden unless valid_service_provider?
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def decode_request(raw_saml_request)
|
|
26
|
+
self.saml_request = Request.from_deflated_request(raw_saml_request)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def encode_response(principal, opts = {})
|
|
30
|
+
response_id, reference_id = get_saml_response_id, get_saml_reference_id
|
|
31
|
+
audience_uri = opts[:audience_uri] || saml_acs_url[/^(.*?\/\/.*?\/)/, 1]
|
|
32
|
+
opt_issuer_uri = opts[:issuer_uri] || issuer_uri
|
|
33
|
+
|
|
34
|
+
SamlResponse.new(
|
|
35
|
+
reference_id,
|
|
36
|
+
response_id,
|
|
37
|
+
issuer_uri,
|
|
38
|
+
principal,
|
|
39
|
+
audience_uri,
|
|
40
|
+
saml_request_id,
|
|
41
|
+
saml_acs_url,
|
|
42
|
+
algorithm
|
|
43
|
+
).build
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def issuer_uri
|
|
47
|
+
issuer_uri = (defined?(request) && request.url.to_s.split("?").first) || "http://example.com"
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def valid_service_provider?
|
|
51
|
+
saml_request.service_provider? &&
|
|
52
|
+
saml_request.valid_signature?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def saml_request_id
|
|
56
|
+
saml_request.request_id
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def saml_acs_url
|
|
60
|
+
saml_request.acs_url
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def get_saml_response_id
|
|
64
|
+
UUID.generate
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def get_saml_reference_id
|
|
68
|
+
UUID.generate
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
module SamlIdp
|
|
3
|
+
module Default
|
|
4
|
+
NAME_ID_FORMAT = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
|
|
5
|
+
X509_CERTIFICATE = "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURxekNDQXhTZ0F3SUJBZ0lCQVRBTkJna3Foa2lHOXcwQkFRc0ZBRENCaGpFTE1Ba0dBMVVFQmhNQ1FWVXgKRERBS0JnTlZCQWdUQTA1VFZ6RVBNQTBHQTFVRUJ4TUdVM2xrYm1WNU1Rd3dDZ1lEVlFRS0RBTlFTVlF4Q1RBSApCZ05WQkFzTUFERVlNQllHQTFVRUF3d1BiR0YzY21WdVkyVndhWFF1WTI5dE1TVXdJd1lKS29aSWh2Y05BUWtCCkRCWnNZWGR5Wlc1alpTNXdhWFJBWjIxaGFXd3VZMjl0TUI0WERURXlNRFF5T0RBeU1qSXlPRm9YRFRNeU1EUXkKTXpBeU1qSXlPRm93Z1lZeEN6QUpCZ05WQkFZVEFrRlZNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVApCbE41Wkc1bGVURU1NQW9HQTFVRUNnd0RVRWxVTVFrd0J3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psCmJtTmxjR2wwTG1OdmJURWxNQ01HQ1NxR1NJYjNEUUVKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnYKYlRDQm56QU5CZ2txaGtpRzl3MEJBUUVGQUFPQmpRQXdnWWtDZ1lFQXVCeXdQTmxDMUZvcEdMWWZGOTZTb3RpSwo4Tmo2L25XMDg0TzRvbVJNaWZ6eTd4OTU1UkxFeTY3M3EyYWlKTkIzTHZFNlh2a3Q5Y0d0eHROb09YdzFnMlV2CkhLcGxkUWJyNmJPRWpMTmVETlc3ajBvYitKclJ2QVVPSzlDUmdkeXc1TUM2bHdxVlFRNUMxRG5hVC8yZlNCRmoKYXNCRlRSMjRkRXBmVHk4SGZLRUNBd0VBQWFPQ0FTVXdnZ0VoTUFrR0ExVWRFd1FDTUFBd0N3WURWUjBQQkFRRApBZ1VnTUIwR0ExVWREZ1FXQkJRTkJHbW10M3l0S3BjSmFCYVlOYm55VTJ4a2F6QVRCZ05WSFNVRUREQUtCZ2dyCkJnRUZCUWNEQVRBZEJnbGdoa2dCaHZoQ0FRMEVFQllPVkdWemRDQllOVEE1SUdObGNuUXdnYk1HQTFVZEl3U0IKcXpDQnFJQVVEUVJwcHJkOHJTcVhDV2dXbURXNThsTnNaR3VoZ1l5a2dZa3dnWVl4Q3pBSkJnTlZCQVlUQWtGVgpNUXd3Q2dZRFZRUUlFd05PVTFjeER6QU5CZ05WQkFjVEJsTjVaRzVsZVRFTU1Bb0dBMVVFQ2d3RFVFbFVNUWt3CkJ3WURWUVFMREFBeEdEQVdCZ05WQkFNTUQyeGhkM0psYm1ObGNHbDBMbU52YlRFbE1DTUdDU3FHU0liM0RRRUoKQVF3V2JHRjNjbVZ1WTJVdWNHbDBRR2R0WVdsc0xtTnZiWUlCQVRBTkJna3Foa2lHOXcwQkFRc0ZBQU9CZ1FBRQpjVlVQQlg3dVptenFaSmZ5K3RVUE9UNUltTlFqOFZFMmxlcmhuRmpuR1BIbUhJcWhwemdud0hRdWpKZnMvYTMwCjlXbTVxd2NDYUMxZU81Y1dqY0cweDNPamRsbHNnWURhdGw1R0F1bXRCeDhKM05oV1JxTlVnaXRDSWtRbHhISXcKVWZnUWFDdXNoWWdEREw1WWJJUWErK2VnQ2dwSVorVDBEajVvUmV3Ly9BPT0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo="
|
|
6
|
+
FINGERPRINT = "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D"
|
|
7
|
+
SECRET_KEY = <<EOS
|
|
8
|
+
-----BEGIN RSA PRIVATE KEY-----
|
|
9
|
+
MIICXAIBAAKBgQC4HLA82ULUWikYth8X3pKi2Irw2Pr+dbTzg7iiZEyJ/PLvH3nl
|
|
10
|
+
EsTLrverZqIk0Hcu8Tpe+S31wa3G02g5fDWDZS8cqmV1Buvps4SMs14M1buPShv4
|
|
11
|
+
mtG8BQ4r0JGB3LDkwLqXCpVBDkLUOdpP/Z9IEWNqwEVNHbh0Sl9PLwd8oQIDAQAB
|
|
12
|
+
AoGAQmUGIUtwUEgbXe//kooPc26H3IdDLJSiJtcvtFBbUb/Ik/dT7AoysgltA4DF
|
|
13
|
+
pGURNfqERE+0BVZNJtCCW4ixew4uEhk1XowYXHCzjkzyYoFuT9v5SP4cu4q3t1kK
|
|
14
|
+
51JF237F0eCY3qC3k96CzPGG67bwOu9EeXAu4ka/plLdsAECQQDkg0uhR/vsJffx
|
|
15
|
+
tiWxcDRNFoZpCpzpdWfQBnHBzj9ZC0xrdVilxBgBpupCljO2Scy4MeiY4S1Mleel
|
|
16
|
+
CWRqh7RBAkEAzkIjUnllEkr5sjVb7pNy+e/eakuDxvZck0Z8X3USUki/Nm3E/GPP
|
|
17
|
+
c+CwmXR4QlpMpJr3/Prf1j59l/LAK9AwYQJBAL9eRSQYCJ3HXlGKXR6v/NziFEY7
|
|
18
|
+
oRTSQdIw02ueseZ8U89aQpbwFbqsclq5Ny1duJg5E7WUPj94+rl3mCSu6QECQBVh
|
|
19
|
+
0duY7htpXl1VHsSq0H6MmVgXn/+eRpaV9grHTjDtjbUMyCEKD9WJc4VVB6qJRezC
|
|
20
|
+
i/bT4ySIsehwp+9i08ECQEH03lCpHpbwiWH4sD25l/z3g2jCbIZ+RTV6yHIz7Coh
|
|
21
|
+
gAbBqA04wh64JhhfG69oTBwqwj3imlWF8+jDzV9RNNw=
|
|
22
|
+
-----END RSA PRIVATE KEY-----
|
|
23
|
+
EOS
|
|
24
|
+
SERVICE_PROVIDER = {
|
|
25
|
+
fingerprint: FINGERPRINT
|
|
26
|
+
}
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module SamlIdp
|
|
2
|
+
module Hashable
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def hashables
|
|
6
|
+
self.class.hashables
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def to_h
|
|
10
|
+
hashables.reduce({}) do |hash, key|
|
|
11
|
+
hash[key.to_sym] = send(key)
|
|
12
|
+
hash
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
module ClassMethods
|
|
17
|
+
def hashables
|
|
18
|
+
@hashables ||= Set.new
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def hashable(method_name)
|
|
22
|
+
self.hashables << method_name.to_s
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
require 'set'
|
|
2
|
+
require 'saml_idp/hashable'
|
|
3
|
+
module SamlIdp
|
|
4
|
+
class IncomingMetadata
|
|
5
|
+
include Hashable
|
|
6
|
+
attr_accessor :raw
|
|
7
|
+
|
|
8
|
+
delegate :xpath, to: :document
|
|
9
|
+
private :xpath
|
|
10
|
+
|
|
11
|
+
def initialize(raw = "")
|
|
12
|
+
self.raw = raw
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def document
|
|
16
|
+
@document ||= Saml::XML::Document.parse raw
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def sign_assertions
|
|
20
|
+
doc = xpath(
|
|
21
|
+
"//md:SPSSODescriptor",
|
|
22
|
+
ds: signature_namespace,
|
|
23
|
+
md: metadata_namespace
|
|
24
|
+
).first
|
|
25
|
+
doc ? !!doc["WantAssertionsSigned"] : false
|
|
26
|
+
end
|
|
27
|
+
hashable :sign_assertions
|
|
28
|
+
|
|
29
|
+
def display_name
|
|
30
|
+
role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
|
|
31
|
+
end
|
|
32
|
+
hashable :display_name
|
|
33
|
+
|
|
34
|
+
def contact_person
|
|
35
|
+
{
|
|
36
|
+
given_name: given_name,
|
|
37
|
+
surname: surname,
|
|
38
|
+
company: company,
|
|
39
|
+
telephone_number: telephone_number,
|
|
40
|
+
email_address: email_address
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
hashable :contact_person
|
|
44
|
+
|
|
45
|
+
def signing_certificate
|
|
46
|
+
xpath(
|
|
47
|
+
"//md:SPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
|
48
|
+
ds: signature_namespace,
|
|
49
|
+
md: metadata_namespace
|
|
50
|
+
).first.try(:content).to_s
|
|
51
|
+
end
|
|
52
|
+
hashable :signing_certificate
|
|
53
|
+
|
|
54
|
+
def encryption_certificate
|
|
55
|
+
xpath(
|
|
56
|
+
"//md:SPSSODescriptor/md:KeyDescriptor[@use='encryption']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
|
|
57
|
+
ds: signature_namespace,
|
|
58
|
+
md: metadata_namespace
|
|
59
|
+
).first.try(:content).to_s
|
|
60
|
+
end
|
|
61
|
+
hashable :encryption_certificate
|
|
62
|
+
|
|
63
|
+
def single_logout_services
|
|
64
|
+
xpath(
|
|
65
|
+
"//md:SPSSODescriptor/md:SingleLogoutService",
|
|
66
|
+
md: metadata_namespace
|
|
67
|
+
).reduce({}) do |hash, el|
|
|
68
|
+
hash[el["Binding"].to_s.split(":").last] = el["Location"]
|
|
69
|
+
hash
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
hashable :single_logout_services
|
|
73
|
+
|
|
74
|
+
def name_id_formats
|
|
75
|
+
xpath(
|
|
76
|
+
"//md:SPSSODescriptor/md:NameIDFormat",
|
|
77
|
+
md: metadata_namespace
|
|
78
|
+
).reduce(Set.new) do |set, el|
|
|
79
|
+
props = el.content.to_s.match /urn:oasis:names:tc:SAML:(?<version>\S+):nameid-format:(?<name>\S+)/
|
|
80
|
+
set << props[:name].to_s.underscore if props[:name].present?
|
|
81
|
+
set
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
hashable :name_id_formats
|
|
85
|
+
|
|
86
|
+
def assertion_consumer_services
|
|
87
|
+
xpath(
|
|
88
|
+
"//md:SPSSODescriptor/md:AssertionConsumerService",
|
|
89
|
+
md: metadata_namespace
|
|
90
|
+
).sort_by { |el| el["index"].to_i }.reduce([]) do |array, el|
|
|
91
|
+
props = el["Binding"].to_s.match /urn:oasis:names:tc:SAML:(?<version>\S+):bindings:(?<name>\S+)/
|
|
92
|
+
array << { binding: props[:name], location: el["Location"], default: !!el["isDefault"] }
|
|
93
|
+
array
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
hashable :assertion_consumer_services
|
|
97
|
+
|
|
98
|
+
def given_name
|
|
99
|
+
contact_person_document.xpath("//md:GivenName", md: metadata_namespace).first.try(:content).to_s
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def surname
|
|
103
|
+
contact_person_document.xpath("//md:SurName", md: metadata_namespace).first.try(:content).to_s
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def company
|
|
107
|
+
contact_person_document.xpath("//md:Company", md: metadata_namespace).first.try(:content).to_s
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def telephone_number
|
|
111
|
+
contact_person_document.xpath("//md:TelephoneNumber", md: metadata_namespace).first.try(:content).to_s
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def email_address
|
|
115
|
+
contact_person_document.xpath("//md:EmailAddress", md: metadata_namespace).first.try(:content).to_s.gsub("mailto:", "")
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def role_descriptor_document
|
|
119
|
+
@role_descriptor ||= xpath("//md:RoleDescriptor", md: metadata_namespace).first
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def service_provider_descriptor_document
|
|
123
|
+
@service_provider_descriptor ||= xpath("//md:SPSSODescriptor", md: metadata_namespace).first
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def idp_descriptor_document
|
|
127
|
+
@idp_descriptor ||= xpath("//md:IDPSSODescriptor", md: metadata_namespace).first
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def contact_person_document
|
|
131
|
+
@contact_person_document ||= xpath("//md:ContactPerson", md: metadata_namespace).first
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def metadata_namespace
|
|
135
|
+
Saml::XML::Namespaces::METADATA
|
|
136
|
+
end
|
|
137
|
+
private :metadata_namespace
|
|
138
|
+
|
|
139
|
+
def signature_namespace
|
|
140
|
+
Saml::XML::Namespaces::SIGNATURE
|
|
141
|
+
end
|
|
142
|
+
private :signature_namespace
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
require 'saml_idp/name_id_formatter'
|
|
2
|
+
require 'saml_idp/attribute_decorator'
|
|
3
|
+
require 'saml_idp/algorithmable'
|
|
4
|
+
require 'saml_idp/signable'
|
|
5
|
+
module SamlIdp
|
|
6
|
+
class MetadataBuilder
|
|
7
|
+
include Algorithmable
|
|
8
|
+
include Signable
|
|
9
|
+
attr_accessor :configurator
|
|
10
|
+
|
|
11
|
+
def initialize(configurator = SamlIdp.config)
|
|
12
|
+
self.configurator = configurator
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def fresh
|
|
16
|
+
builder = Builder::XmlMarkup.new
|
|
17
|
+
generated_reference_id do
|
|
18
|
+
builder.EntityDescriptor ID: reference_string,
|
|
19
|
+
xmlns: Saml::XML::Namespaces::METADATA,
|
|
20
|
+
"xmlns:saml" => Saml::XML::Namespaces::ASSERTION,
|
|
21
|
+
"xmlns:ds" => Saml::XML::Namespaces::SIGNATURE,
|
|
22
|
+
entityID: entity_id do |entity|
|
|
23
|
+
sign entity
|
|
24
|
+
build_organization entity
|
|
25
|
+
build_contact entity
|
|
26
|
+
|
|
27
|
+
entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
|
|
28
|
+
build_key_descriptor descriptor
|
|
29
|
+
build_name_id_formats descriptor
|
|
30
|
+
descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
31
|
+
Location: single_service_post_location
|
|
32
|
+
build_attribute descriptor
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
entity.AttributeAuthorityDescriptor ID: reference_string,
|
|
36
|
+
protocolSupportEnumeration: protocol_enumeration do |authority_descriptor|
|
|
37
|
+
build_key_descriptor authority_descriptor
|
|
38
|
+
build_organization authority_descriptor
|
|
39
|
+
build_contact authority_descriptor
|
|
40
|
+
authority_descriptor.AttributeService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
|
|
41
|
+
Location: attribute_service_location
|
|
42
|
+
build_name_id_formats authority_descriptor
|
|
43
|
+
build_attribute authority_descriptor
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
alias_method :raw, :fresh
|
|
49
|
+
|
|
50
|
+
def build_key_descriptor(el)
|
|
51
|
+
el.KeyDescriptor use: "signing" do |key_descriptor|
|
|
52
|
+
key_descriptor.KeyInfo xmlns: Saml::XML::Namespaces::SIGNATURE do |key_info|
|
|
53
|
+
key_info.X509Data do |x509|
|
|
54
|
+
x509.X509Certificate x509_certificate
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
private :build_key_descriptor
|
|
60
|
+
|
|
61
|
+
def build_name_id_formats(el)
|
|
62
|
+
name_id_formats.each do |format|
|
|
63
|
+
el.NameIDFormat format
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
private :build_name_id_formats
|
|
67
|
+
|
|
68
|
+
def build_attribute(el)
|
|
69
|
+
attributes.each do |attribute|
|
|
70
|
+
el.tag! "saml:Attribute",
|
|
71
|
+
NameFormat: attribute.name_format,
|
|
72
|
+
Name: attribute.name,
|
|
73
|
+
FriendlyName: attribute.friendly_name do |attribute_xml|
|
|
74
|
+
attribute.values.each do |value|
|
|
75
|
+
attribute_xml.tag! "saml:AttributeValue", value
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
private :build_attribute
|
|
81
|
+
|
|
82
|
+
def build_organization(el)
|
|
83
|
+
el.Organization do |organization|
|
|
84
|
+
organization.OrganizationName organization_name, "xml:lang" => "en"
|
|
85
|
+
organization.OrganizationDisplayName organization_name, "xml:lang" => "en"
|
|
86
|
+
organization.OrganizationURL organization_url, "xml:lang" => "en"
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
private :build_organization
|
|
90
|
+
|
|
91
|
+
def build_contact(el)
|
|
92
|
+
el.ContactPerson contactType: "technical" do |contact|
|
|
93
|
+
contact.Company technical_contact.company if technical_contact.company.present?
|
|
94
|
+
contact.GivenName technical_contact.given_name if technical_contact.given_name.present?
|
|
95
|
+
contact.SurName technical_contact.sur_name if technical_contact.sur_name.present?
|
|
96
|
+
contact.TelephoneNumber technical_contact.telephone if technical_contact.telephone.present?
|
|
97
|
+
contact.EmailAddress technical_contact.mail_to_string if technical_contact.mail_to_string.present?
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
private :build_contact
|
|
101
|
+
|
|
102
|
+
def reference_string
|
|
103
|
+
"_#{reference_id}"
|
|
104
|
+
end
|
|
105
|
+
private :reference_string
|
|
106
|
+
|
|
107
|
+
def entity_id
|
|
108
|
+
configurator.base_saml_location
|
|
109
|
+
end
|
|
110
|
+
private :entity_id
|
|
111
|
+
|
|
112
|
+
def protocol_enumeration
|
|
113
|
+
Saml::XML::Namespaces::PROTOCOL
|
|
114
|
+
end
|
|
115
|
+
private :protocol_enumeration
|
|
116
|
+
|
|
117
|
+
def attributes
|
|
118
|
+
@attributes ||= configurator.attributes.inject([]) do |list, (key, opts)|
|
|
119
|
+
opts[:friendly_name] = key
|
|
120
|
+
list << AttributeDecorator.new(opts)
|
|
121
|
+
list
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
private :attributes
|
|
125
|
+
|
|
126
|
+
def name_id_formats
|
|
127
|
+
@name_id_formats ||= NameIdFormatter.new(configurator.name_id.formats).all
|
|
128
|
+
end
|
|
129
|
+
private :name_id_formats
|
|
130
|
+
|
|
131
|
+
def raw_algorithm
|
|
132
|
+
configurator.algorithm
|
|
133
|
+
end
|
|
134
|
+
private :raw_algorithm
|
|
135
|
+
|
|
136
|
+
def x509_certificate
|
|
137
|
+
SamlIdp.config.x509_certificate
|
|
138
|
+
.to_s
|
|
139
|
+
.gsub(/-----BEGIN CERTIFICATE-----/,"")
|
|
140
|
+
.gsub(/-----END CERTIFICATE-----/,"")
|
|
141
|
+
.gsub(/\n/, "")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
%w[
|
|
145
|
+
support_email
|
|
146
|
+
organization_name
|
|
147
|
+
organization_url
|
|
148
|
+
attribute_service_location
|
|
149
|
+
single_service_post_location
|
|
150
|
+
technical_contact
|
|
151
|
+
].each do |delegatable|
|
|
152
|
+
define_method(delegatable) do
|
|
153
|
+
configurator.public_send delegatable
|
|
154
|
+
end
|
|
155
|
+
private delegatable
|
|
156
|
+
end
|
|
157
|
+
end
|
|
158
|
+
end
|