saml_idp 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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,68 @@
1
+ module SamlIdp
2
+ class NameIdFormatter
3
+ attr_accessor :list
4
+ def initialize(list)
5
+ self.list = (list || {})
6
+ end
7
+
8
+ def all
9
+ if split?
10
+ one_one.map { |key_val| build("1.1", key_val)[:name] } +
11
+ two_zero.map { |key_val| build("2.0", key_val)[:name] }
12
+ else
13
+ list.map { |key_val| build("2.0", key_val)[:name] }
14
+ end
15
+ end
16
+
17
+ def chosen
18
+ if split?
19
+ version, choose = "1.1", one_one.first
20
+ version, choose = "2.0", two_zero.first unless choose
21
+ version, choose = "2.0", "persistent" unless choose
22
+ build(version, choose)
23
+ else
24
+ choose = list.first || "persistent"
25
+ build("2.0", choose)
26
+ end
27
+ end
28
+
29
+ def build(version, key_val)
30
+ key_val = Array(key_val)
31
+ name = key_val.first.to_s.underscore
32
+ getter = build_getter key_val.last || name
33
+ {
34
+ name: "urn:oasis:names:tc:SAML:#{version}:nameid-format:#{name.camelize(:lower)}",
35
+ getter: getter
36
+ }
37
+ end
38
+ private :build
39
+
40
+ def build_getter(getter_val)
41
+ if getter_val.respond_to?(:call)
42
+ getter_val
43
+ else
44
+ ->(p) { p.public_send getter_val.to_s }
45
+ end
46
+ end
47
+ private :build_getter
48
+
49
+ def split?
50
+ list.is_a?(Hash) && (list.key?("2.0") || list.key?("1.1"))
51
+ end
52
+ private :split?
53
+
54
+ def one_one
55
+ list["1.1"] || {}
56
+ rescue TypeError
57
+ {}
58
+ end
59
+ private :one_one
60
+
61
+ def two_zero
62
+ list["2.0"] || {}
63
+ rescue TypeError
64
+ {}
65
+ end
66
+ private :two_zero
67
+ end
68
+ end
@@ -0,0 +1,10 @@
1
+ require 'saml_idp/attributeable'
2
+ module SamlIdp
3
+ class PersistedMetadata
4
+ include Attributeable
5
+
6
+ def sign_assertions?
7
+ !!attributes[:sign_assertions]
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,79 @@
1
+ require 'saml_idp/xml_security'
2
+ require 'saml_idp/service_provider'
3
+ module SamlIdp
4
+ class Request
5
+ def self.from_deflated_request(raw)
6
+ zstream = Zlib::Inflate.new(-Zlib::MAX_WBITS)
7
+ inflated = zstream.inflate(Base64.decode64(raw)).tap do
8
+ zstream.finish
9
+ zstream.close
10
+ end
11
+ new(inflated)
12
+ end
13
+
14
+ attr_accessor :raw_xml
15
+
16
+ delegate :config, to: :SamlIdp
17
+ private :config
18
+ delegate :xpath, to: :document
19
+ private :xpath
20
+
21
+ def initialize(raw_xml = "")
22
+ self.raw_xml = raw_xml
23
+ end
24
+
25
+ def request_id
26
+ authn_request["ID"]
27
+ end
28
+
29
+ def acs_url
30
+ authn_request["AssertionConsumerServiceURL"]
31
+ end
32
+
33
+ def valid_signature?
34
+ service_provider.valid_signature? document
35
+ end
36
+
37
+ def service_provider?
38
+ service_provider.valid?
39
+ end
40
+
41
+ def service_provider
42
+ @service_provider ||= ServiceProvider.new((service_provider_finder[issuer] || {}).merge(identifier: issuer))
43
+ end
44
+
45
+ def issuer
46
+ xpath("//saml:Issuer", saml: assertion).first.try :content
47
+ end
48
+
49
+ def document
50
+ @document ||= Saml::XML::Document.parse(raw_xml)
51
+ end
52
+ private :document
53
+
54
+ def authn_request
55
+ xpath("//samlp:AuthnRequest", samlp: samlp).first
56
+ end
57
+ private :authn_request
58
+
59
+ def samlp
60
+ Saml::XML::Namespaces::PROTOCOL
61
+ end
62
+ private :samlp
63
+
64
+ def assertion
65
+ Saml::XML::Namespaces::ASSERTION
66
+ end
67
+ private :assertion
68
+
69
+ def signature_namespace
70
+ Saml::XML::Namespaces::SIGNATURE
71
+ end
72
+ private :signature_namespace
73
+
74
+ def service_provider_finder
75
+ config.service_provider.finder
76
+ end
77
+ private :service_provider_finder
78
+ end
79
+ end
@@ -0,0 +1,60 @@
1
+ require 'builder'
2
+ module SamlIdp
3
+ class ResponseBuilder
4
+ attr_accessor :response_id
5
+ attr_accessor :issuer_uri
6
+ attr_accessor :saml_acs_url
7
+ attr_accessor :saml_request_id
8
+ attr_accessor :assertion_and_signature
9
+
10
+ def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature)
11
+ self.response_id = response_id
12
+ self.issuer_uri = issuer_uri
13
+ self.saml_acs_url = saml_acs_url
14
+ self.saml_request_id = saml_request_id
15
+ self.assertion_and_signature = assertion_and_signature
16
+ end
17
+
18
+ def encoded
19
+ @encoded ||= encode
20
+ end
21
+
22
+ def raw
23
+ build
24
+ end
25
+
26
+ def encode
27
+ Base64.encode64(raw)
28
+ end
29
+ private :encode
30
+
31
+ def build
32
+ builder = Builder::XmlMarkup.new
33
+ builder.tag! "samlp:Response",
34
+ ID: response_id_string,
35
+ Version: "2.0",
36
+ IssueInstant: now_iso,
37
+ Destination: saml_acs_url,
38
+ Consent: Saml::XML::Namespaces::Consents::UNSPECIFIED,
39
+ InResponseTo: saml_request_id,
40
+ "xmlns:samlp" => Saml::XML::Namespaces::PROTOCOL do |response|
41
+ response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
42
+ response.tag! "samlp:Status" do |status|
43
+ status.tag! "samlp:StatusCode", Value: Saml::XML::Namespaces::Statuses::SUCCESS
44
+ end
45
+ response << assertion_and_signature
46
+ end
47
+ end
48
+ private :build
49
+
50
+ def response_id_string
51
+ "_#{response_id}"
52
+ end
53
+ private :response_id_string
54
+
55
+ def now_iso
56
+ Time.now.utc.iso8601
57
+ end
58
+ private :now_iso
59
+ end
60
+ end
@@ -0,0 +1,63 @@
1
+ require 'saml_idp/assertion_builder'
2
+ require 'saml_idp/response_builder'
3
+ module SamlIdp
4
+ class SamlResponse
5
+ attr_accessor :assertion_with_signature
6
+ attr_accessor :reference_id
7
+ attr_accessor :response_id
8
+ attr_accessor :issuer_uri
9
+ attr_accessor :principal
10
+ attr_accessor :audience_uri
11
+ attr_accessor :saml_request_id
12
+ attr_accessor :saml_acs_url
13
+ attr_accessor :algorithm
14
+ attr_accessor :secret_key
15
+ attr_accessor :x509_certificate
16
+
17
+ def initialize(reference_id,
18
+ response_id,
19
+ issuer_uri,
20
+ principal,
21
+ audience_uri,
22
+ saml_request_id,
23
+ saml_acs_url,
24
+ algorithm
25
+ )
26
+ self.reference_id = reference_id
27
+ self.response_id = response_id
28
+ self.issuer_uri = issuer_uri
29
+ self.principal = principal
30
+ self.audience_uri = audience_uri
31
+ self.saml_request_id = saml_request_id
32
+ self.saml_acs_url = saml_acs_url
33
+ self.algorithm = algorithm
34
+ self.secret_key = secret_key
35
+ self.x509_certificate = x509_certificate
36
+ end
37
+
38
+ def build
39
+ @built ||= response_builder.encoded
40
+ end
41
+
42
+ def signed_assertion
43
+ assertion_builder.signed
44
+ end
45
+ private
46
+
47
+ def response_builder
48
+ ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
49
+ end
50
+ private :response_builder
51
+
52
+ def assertion_builder
53
+ @assertion_builder ||= AssertionBuilder.new reference_id,
54
+ issuer_uri,
55
+ principal,
56
+ audience_uri,
57
+ saml_request_id,
58
+ saml_acs_url,
59
+ algorithm
60
+ end
61
+ private :assertion_builder
62
+ end
63
+ end
@@ -0,0 +1,68 @@
1
+ require 'httparty'
2
+ require 'saml_idp/attributeable'
3
+ require 'saml_idp/incoming_metadata'
4
+ require 'saml_idp/persisted_metadata'
5
+ module SamlIdp
6
+ class ServiceProvider
7
+ include Attributeable
8
+ attribute :identifier
9
+ attribute :fingerprint
10
+ attribute :metadata_url
11
+
12
+ delegate :config, to: :SamlIdp
13
+
14
+ def valid?
15
+ attributes.present?
16
+ end
17
+
18
+ def valid_signature?(doc)
19
+ !should_validate_signature? ||
20
+ doc.valid_signature?(fingerprint)
21
+ end
22
+
23
+ def should_validate_signature?
24
+ current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?
25
+ end
26
+
27
+ def refresh_metadata
28
+ fresh = fresh_incoming_metadata
29
+ if valid_signature?(fresh.document)
30
+ metadata_persister[identifier, fresh]
31
+ @current_metadata = nil
32
+ fresh
33
+ end
34
+ end
35
+
36
+ def current_metadata
37
+ @current_metadata ||= get_current_or_build
38
+ end
39
+
40
+ def get_current_or_build
41
+ persisted = metadata_getter[identifier, self]
42
+ if persisted.is_a? Hash
43
+ PersistedMetadata.new(persisted)
44
+ end
45
+ end
46
+ private :get_current_or_build
47
+
48
+ def metadata_getter
49
+ config.service_provider.persisted_metadata_getter
50
+ end
51
+ private :metadata_getter
52
+
53
+ def metadata_persister
54
+ config.service_provider.metadata_persister
55
+ end
56
+ private :metadata_persister
57
+
58
+ def fresh_incoming_metadata
59
+ IncomingMetadata.new request_metadata
60
+ end
61
+ private :fresh_incoming_metadata
62
+
63
+ def request_metadata
64
+ metadata_url.present? ? HTTParty.get(metadata_url).body : ""
65
+ end
66
+ private :request_metadata
67
+ end
68
+ end
@@ -0,0 +1,131 @@
1
+ # Requires methods:
2
+ # * reference_id
3
+ # * raw
4
+ # * digest
5
+ # * algorithm
6
+ require 'saml_idp/signed_info_builder'
7
+ require 'saml_idp/signature_builder'
8
+ module SamlIdp
9
+ module Signable
10
+ def self.included(base)
11
+ base.extend ClassMethods
12
+ base.send :attr_accessor, :reference_id
13
+ end
14
+
15
+ def signed
16
+ generated_reference_id do
17
+ with_signature do
18
+ send(self.class.raw_method)
19
+ end
20
+ end
21
+ end
22
+
23
+ def sign(el)
24
+ el << signature if sign?
25
+ end
26
+
27
+ def generated_reference_id
28
+ if reference_id
29
+ fin = yield reference_id if block_given?
30
+ else
31
+ self.reference_id = ref = reference_id_generator.call
32
+ fin = yield reference_id if block_given?
33
+ self.reference_id = nil
34
+ end
35
+ block_given? ? fin : ref
36
+ end
37
+ private :generated_reference_id
38
+
39
+ def reference_id_generator
40
+ SamlIdp.config.reference_id_generator
41
+ end
42
+ private :reference_id_generator
43
+
44
+ def with_signature
45
+ original = @sign
46
+ @sign = true
47
+ yield.tap do
48
+ @sign = original
49
+ end
50
+ end
51
+ private :with_signature
52
+
53
+ def without_signature
54
+ original = @sign
55
+ @sign = false
56
+ yield.tap do
57
+ @sign = original
58
+ end
59
+ end
60
+ private :without_signature
61
+
62
+ def sign?
63
+ !!@sign
64
+ end
65
+ private :sign?
66
+
67
+ def signature
68
+ SignatureBuilder.new(signed_info_builder).raw
69
+ end
70
+ private :signature
71
+
72
+ def signed_info_builder
73
+ SignedInfoBuilder.new(get_reference_id, get_digest, get_algorithm)
74
+ end
75
+ private :signed_info_builder
76
+
77
+ def get_reference_id
78
+ send(self.class.reference_id_method)
79
+ end
80
+ private :get_reference_id
81
+
82
+ def get_digest
83
+ without_signature do
84
+ send(self.class.digest_method)
85
+ end
86
+ end
87
+ private :get_digest
88
+
89
+ def get_algorithm
90
+ send(self.class.algorithm_method)
91
+ end
92
+ private :get_algorithm
93
+
94
+ def get_raw
95
+ send(self.class.raw_method)
96
+ end
97
+ private :get_raw
98
+
99
+ def noko_raw
100
+ Nokogiri::XML::Document.parse(get_raw)
101
+ end
102
+ private :noko_raw
103
+
104
+ def digest
105
+ # Make it check for inclusive at some point (https://github.com/onelogin/ruby-saml/blob/master/lib/xml_security.rb#L159)
106
+ inclusive_namespaces = []
107
+ # Also make customizable
108
+ canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
109
+ canon_hashed_element = noko_raw.canonicalize(canon_algorithm, inclusive_namespaces)
110
+ digest_algorithm = get_algorithm
111
+
112
+ hash = digest_algorithm.digest(canon_hashed_element)
113
+ Base64.encode64(hash).gsub(/\n/, '')
114
+ end
115
+ private :digest
116
+
117
+ module ClassMethods
118
+ def self.module_method(name, default = nil)
119
+ default ||= name
120
+ define_method "#{name}_method" do |new_method_name = nil|
121
+ instance_variable_set("@#{name}", new_method_name) if new_method_name
122
+ instance_variable_get("@#{name}") || default
123
+ end
124
+ end
125
+ module_method :raw
126
+ module_method :digest
127
+ module_method :algorithm
128
+ module_method :reference_id
129
+ end
130
+ end
131
+ end