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.
Files changed (122) hide show
  1. checksums.yaml +15 -0
  2. data/Gemfile +2 -0
  3. data/LICENSE +22 -0
  4. data/README.md +197 -0
  5. data/app/controllers/saml_idp/idp_controller.rb +42 -0
  6. data/app/views/saml_idp/idp/new.html.erb +21 -0
  7. data/app/views/saml_idp/idp/saml_post.html.erb +13 -0
  8. data/lib/saml_idp.rb +92 -0
  9. data/lib/saml_idp/algorithmable.rb +19 -0
  10. data/lib/saml_idp/assertion_builder.rb +144 -0
  11. data/lib/saml_idp/attribute_decorator.rb +27 -0
  12. data/lib/saml_idp/attributeable.rb +24 -0
  13. data/lib/saml_idp/configurator.rb +45 -0
  14. data/lib/saml_idp/controller.rb +71 -0
  15. data/lib/saml_idp/default.rb +28 -0
  16. data/lib/saml_idp/engine.rb +5 -0
  17. data/lib/saml_idp/hashable.rb +26 -0
  18. data/lib/saml_idp/incoming_metadata.rb +144 -0
  19. data/lib/saml_idp/metadata_builder.rb +158 -0
  20. data/lib/saml_idp/name_id_formatter.rb +68 -0
  21. data/lib/saml_idp/persisted_metadata.rb +10 -0
  22. data/lib/saml_idp/request.rb +79 -0
  23. data/lib/saml_idp/response_builder.rb +60 -0
  24. data/lib/saml_idp/saml_response.rb +63 -0
  25. data/lib/saml_idp/service_provider.rb +68 -0
  26. data/lib/saml_idp/signable.rb +131 -0
  27. data/lib/saml_idp/signature_builder.rb +42 -0
  28. data/lib/saml_idp/signed_info_builder.rb +51 -0
  29. data/lib/saml_idp/version.rb +4 -0
  30. data/lib/saml_idp/xml_security.rb +168 -0
  31. data/saml_idp.gemspec +38 -0
  32. data/spec/acceptance/acceptance_helper.rb +9 -0
  33. data/spec/acceptance/idp_controller_spec.rb +16 -0
  34. data/spec/lib/saml_idp/algorithmable_spec.rb +48 -0
  35. data/spec/lib/saml_idp/assertion_builder_spec.rb +27 -0
  36. data/spec/lib/saml_idp/attribute_decorator_spec.rb +31 -0
  37. data/spec/lib/saml_idp/configurator_spec.rb +30 -0
  38. data/spec/lib/saml_idp/controller_spec.rb +51 -0
  39. data/spec/lib/saml_idp/metadata_builder_spec.rb +9 -0
  40. data/spec/lib/saml_idp/name_id_formatter_spec.rb +39 -0
  41. data/spec/lib/saml_idp/request_spec.rb +14 -0
  42. data/spec/lib/saml_idp/response_builder_spec.rb +32 -0
  43. data/spec/lib/saml_idp/saml_response_spec.rb +27 -0
  44. data/spec/lib/saml_idp/service_provider_spec.rb +21 -0
  45. data/spec/lib/saml_idp/signable_spec.rb +74 -0
  46. data/spec/lib/saml_idp/signature_builder_spec.rb +19 -0
  47. data/spec/lib/saml_idp/signed_info_builder_spec.rb +25 -0
  48. data/spec/rails_app/.gitignore +15 -0
  49. data/spec/rails_app/README.rdoc +261 -0
  50. data/spec/rails_app/Rakefile +7 -0
  51. data/spec/rails_app/app/assets/images/rails.png +0 -0
  52. data/spec/rails_app/app/assets/javascripts/application.js +15 -0
  53. data/spec/rails_app/app/assets/stylesheets/application.css +13 -0
  54. data/spec/rails_app/app/controllers/application_controller.rb +3 -0
  55. data/spec/rails_app/app/controllers/saml_controller.rb +8 -0
  56. data/spec/rails_app/app/controllers/saml_idp_controller.rb +11 -0
  57. data/spec/rails_app/app/helpers/application_helper.rb +2 -0
  58. data/spec/rails_app/app/mailers/.gitkeep +0 -0
  59. data/spec/rails_app/app/models/.gitkeep +0 -0
  60. data/spec/rails_app/app/views/layouts/application.html.erb +14 -0
  61. data/spec/rails_app/config.ru +4 -0
  62. data/spec/rails_app/config/application.rb +60 -0
  63. data/spec/rails_app/config/boot.rb +6 -0
  64. data/spec/rails_app/config/database.yml +25 -0
  65. data/spec/rails_app/config/environment.rb +5 -0
  66. data/spec/rails_app/config/environments/development.rb +37 -0
  67. data/spec/rails_app/config/environments/production.rb +67 -0
  68. data/spec/rails_app/config/environments/test.rb +37 -0
  69. data/spec/rails_app/config/initializers/backtrace_silencers.rb +7 -0
  70. data/spec/rails_app/config/initializers/inflections.rb +15 -0
  71. data/spec/rails_app/config/initializers/mime_types.rb +5 -0
  72. data/spec/rails_app/config/initializers/secret_token.rb +7 -0
  73. data/spec/rails_app/config/initializers/session_store.rb +8 -0
  74. data/spec/rails_app/config/initializers/wrap_parameters.rb +14 -0
  75. data/spec/rails_app/config/locales/en.yml +5 -0
  76. data/spec/rails_app/config/routes.rb +6 -0
  77. data/spec/rails_app/db/seeds.rb +7 -0
  78. data/spec/rails_app/doc/README_FOR_APP +2 -0
  79. data/spec/rails_app/lib/assets/.gitkeep +0 -0
  80. data/spec/rails_app/lib/tasks/.gitkeep +0 -0
  81. data/spec/rails_app/log/.gitkeep +0 -0
  82. data/spec/rails_app/public/404.html +26 -0
  83. data/spec/rails_app/public/422.html +26 -0
  84. data/spec/rails_app/public/500.html +25 -0
  85. data/spec/rails_app/public/favicon.ico +0 -0
  86. data/spec/rails_app/public/index.html +241 -0
  87. data/spec/rails_app/public/robots.txt +5 -0
  88. data/spec/rails_app/script/rails +6 -0
  89. data/spec/rails_app/test/fixtures/.gitkeep +0 -0
  90. data/spec/rails_app/test/functional/.gitkeep +0 -0
  91. data/spec/rails_app/test/integration/.gitkeep +0 -0
  92. data/spec/rails_app/test/performance/browsing_test.rb +12 -0
  93. data/spec/rails_app/test/test_helper.rb +13 -0
  94. data/spec/rails_app/test/unit/.gitkeep +0 -0
  95. data/spec/rails_app/vendor/assets/javascripts/.gitkeep +0 -0
  96. data/spec/rails_app/vendor/assets/stylesheets/.gitkeep +0 -0
  97. data/spec/rails_app/vendor/plugins/.gitkeep +0 -0
  98. data/spec/spec_helper.rb +49 -0
  99. data/spec/support/certificates/certificate1 +12 -0
  100. data/spec/support/certificates/r1_certificate2_base64 +1 -0
  101. data/spec/support/responses/adfs_response_sha1.xml +46 -0
  102. data/spec/support/responses/adfs_response_sha256.xml +46 -0
  103. data/spec/support/responses/adfs_response_sha384.xml +46 -0
  104. data/spec/support/responses/adfs_response_sha512.xml +46 -0
  105. data/spec/support/responses/logoutresponse_fixtures.rb +67 -0
  106. data/spec/support/responses/no_signature_ns.xml +48 -0
  107. data/spec/support/responses/open_saml_response.xml +56 -0
  108. data/spec/support/responses/r1_response6.xml.base64 +1 -0
  109. data/spec/support/responses/response1.xml.base64 +1 -0
  110. data/spec/support/responses/response2.xml.base64 +79 -0
  111. data/spec/support/responses/response3.xml.base64 +66 -0
  112. data/spec/support/responses/response4.xml.base64 +93 -0
  113. data/spec/support/responses/response5.xml.base64 +102 -0
  114. data/spec/support/responses/response_with_ampersands.xml +139 -0
  115. data/spec/support/responses/response_with_ampersands.xml.base64 +93 -0
  116. data/spec/support/responses/simple_saml_php.xml +71 -0
  117. data/spec/support/responses/starfield_response.xml.base64 +1 -0
  118. data/spec/support/responses/wrapped_response_2.xml.base64 +150 -0
  119. data/spec/support/saml_request_macros.rb +19 -0
  120. data/spec/support/security_helpers.rb +61 -0
  121. data/spec/xml_security_spec.rb +136 -0
  122. 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,5 @@
1
+ # encoding: utf-8
2
+ module SamlIdp
3
+ class Engine < Rails::Engine
4
+ end
5
+ 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