saml_idp 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|