saml_idp 0.7.2 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -1
  3. data/README.md +59 -52
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +7 -1
  6. data/lib/saml_idp/controller.rb +21 -13
  7. data/lib/saml_idp/encryptor.rb +0 -1
  8. data/lib/saml_idp/fingerprint.rb +19 -0
  9. data/lib/saml_idp/incoming_metadata.rb +22 -1
  10. data/lib/saml_idp/metadata_builder.rb +23 -8
  11. data/lib/saml_idp/persisted_metadata.rb +4 -0
  12. data/lib/saml_idp/request.rb +26 -6
  13. data/lib/saml_idp/response_builder.rb +26 -6
  14. data/lib/saml_idp/saml_response.rb +62 -28
  15. data/lib/saml_idp/service_provider.rb +15 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/version.rb +1 -1
  18. data/lib/saml_idp/xml_security.rb +1 -1
  19. data/lib/saml_idp.rb +2 -1
  20. data/saml_idp.gemspec +45 -42
  21. data/spec/acceptance/idp_controller_spec.rb +5 -4
  22. data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
  23. data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
  24. data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
  25. data/spec/lib/saml_idp/configurator_spec.rb +9 -7
  26. data/spec/lib/saml_idp/controller_spec.rb +53 -20
  27. data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
  28. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  29. data/spec/lib/saml_idp/incoming_metadata_spec.rb +60 -0
  30. data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
  31. data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
  32. data/spec/lib/saml_idp/request_spec.rb +78 -27
  33. data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
  34. data/spec/lib/saml_idp/saml_response_spec.rb +127 -12
  35. data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
  36. data/spec/lib/saml_idp/signable_spec.rb +1 -1
  37. data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
  38. data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
  39. data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
  40. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  41. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
  42. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  43. data/spec/rails_app/config/application.rb +1 -6
  44. data/spec/rails_app/config/boot.rb +1 -1
  45. data/spec/rails_app/config/environments/development.rb +2 -5
  46. data/spec/rails_app/config/environments/production.rb +1 -0
  47. data/spec/rails_app/config/environments/test.rb +1 -0
  48. data/spec/spec_helper.rb +23 -1
  49. data/spec/support/certificates/sp_cert_req.csr +12 -0
  50. data/spec/support/certificates/sp_private_key.pem +16 -0
  51. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  52. data/spec/support/saml_request_macros.rb +66 -4
  53. data/spec/support/security_helpers.rb +10 -0
  54. data/spec/xml_security_spec.rb +12 -12
  55. metadata +135 -81
  56. data/app/controllers/saml_idp/idp_controller.rb +0 -59
  57. data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 67d795e966607dc7c0553d2889e7da7e33b0ba92
4
- data.tar.gz: c5eb9c3ab9ae64be8ad5e412a94ad8600b329bbd
2
+ SHA256:
3
+ metadata.gz: 7cd98d292836070eddac0b7f710dbd15b0d3611cc849de827b999c3a2fa12d86
4
+ data.tar.gz: f49f1035b63375d5b8aebfd38484c1859d8ac35520f82df92fc698cea00dde1a
5
5
  SHA512:
6
- metadata.gz: 4d41a8d82d518fc503b50d7206c1f12a59e822aa73dcd03f067d25521404446f66aba9d6c6ec478bfa7b9bb9794859c92d530e41cdae5ebcf5fe26e61f96e0a9
7
- data.tar.gz: c143f75f4326f1f1b927b91b5226797881d2bf2ea69155a4769d3805bd628d199f08edd5466529865cdffe57b2fc657cda3f100138a86aa3040af1e8682f0d10
6
+ metadata.gz: 7ab18f32c643b5c8be43a093349ff5c1c0f3142b8156fb650f749ffde2ce1306e104d4006178f77f27055bc6683a35ffbf8aa1d79331140b9c94ae7a2a1da7ee
7
+ data.tar.gz: e68e9a8f0dd08f5dd9aa31c8ff8461ca229b97cbc5e9813eed9c3575ee65bafd2c766bcfd19d9081d0956bab62b8819574b8765be692156d10967589a791cda3
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source "http://rubygems.org"
1
+ source "https://rubygems.org"
2
2
  gemspec
data/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Ruby SAML Identity Provider (IdP)
2
- Forked from https://github.com/lawrencepit/ruby-saml-idp
3
2
 
4
- [![Build Status](https://travis-ci.org/sportngin/saml_idp.png)](https://travis-ci.org/sportngin/saml_idp)
5
- [![Gem Version](https://badge.fury.io/rb/saml_idp.png)](http://badge.fury.io/rb/saml_idp)
3
+ Forked from <https://github.com/lawrencepit/ruby-saml-idp>
4
+
5
+ [![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)
6
6
 
7
7
  The ruby SAML Identity Provider library is for implementing the server side of SAML authentication. It allows
8
8
  your application to act as an IdP (Identity Provider) using the
@@ -12,62 +12,49 @@ protocol. It provides a means for managing authentication requests and confirmat
12
12
  This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
13
13
  SAML IDP implementation.
14
14
 
15
- # Installation and Usage
15
+ ## Installation and Usage
16
16
 
17
17
  Add this to your Gemfile:
18
18
 
19
+ ```ruby
19
20
  gem 'saml_idp'
21
+ ```
22
+
23
+ ### Not using rails?
20
24
 
21
- ## Not using rails?
22
25
  Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
23
26
 
24
27
  Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
25
28
  `saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
26
29
  a user is entirely up to you.
27
30
 
28
- Once a user has successfully authenticated on your system send the Service Provider a SAMLReponse by
31
+ Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
29
32
  posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
30
33
  `encode_response(user_email)`.
31
34
 
32
- ## Using rails?
33
- Add to your `routes.rb` file, for example:
35
+ ### Using rails?
34
36
 
35
- ``` ruby
36
- get '/saml/auth' => 'saml_idp#new'
37
- get '/saml/metadata' => 'saml_idp#show'
38
- post '/saml/auth' => 'saml_idp#create'
39
- match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
40
- ```
37
+ Check out our Wiki page for Rails integration
38
+ [Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
41
39
 
42
- Create a controller that looks like this, customize to your own situation:
40
+ ### Configuration
43
41
 
44
- ``` ruby
45
- class SamlIdpController < SamlIdp::IdpController
46
- def idp_authenticate(email, password) # not using params intentionally
47
- user = User.by_email(email).first
48
- user && user.valid_password?(password) ? user : nil
49
- end
50
- private :idp_authenticate
51
-
52
- def idp_make_saml_response(found_user) # not using params intentionally
53
- # NOTE encryption is optional
54
- encode_response found_user, encryption: {
55
- cert: saml_request.service_provider.cert,
56
- block_encryption: 'aes256-cbc',
57
- key_transport: 'rsa-oaep-mgf1p'
58
- }
59
- end
60
- private :idp_make_saml_response
42
+ #### Signed assertions and Signed Response
61
43
 
62
- def idp_logout
63
- user = User.by_email(saml_request.name_id)
64
- user.logout
65
- end
66
- private :idp_logout
67
- end
68
- ```
44
+ By default SAML Assertion will be signed with an algorithm which defined to `config.algorithm`, because SAML assertions contain secure information used for authentication such as NameID.
45
+ Besides that, signing assertions could be optional and can be defined with `config.signed_assertion` option. Setting this configuration flag to `false` will add raw assertions on the response instead of signed ones. If the response is encrypted the `config.signed_assertion` will be ignored and all assertions will be signed.
69
46
 
70
- ## Configuration
47
+ Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
48
+ For that, you can enable it with `signed_message: true` option for `encode_response(user_email, signed_message: true)` method. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
49
+
50
+ #### Signing algorithm
51
+
52
+ Following algorithms you can set in your response signing algorithm
53
+ :sha1 - RSA-SHA1 default value but not recommended to production environment
54
+ Highly recommended to use one of following algorithm, suit with your computing power.
55
+ :sha256 - RSA-SHA256
56
+ :sha384 - RSA-SHA384
57
+ :sha512 - RSA-SHA512
71
58
 
72
59
  Be sure to load a file like this during your app initialization:
73
60
 
@@ -88,18 +75,23 @@ KEY DATA
88
75
  CERT
89
76
 
90
77
  # config.password = "secret_key_password"
91
- # config.algorithm = :sha256
78
+ # config.algorithm = :sha256 # Default: sha1 only for development.
92
79
  # config.organization_name = "Your Organization"
93
80
  # config.organization_url = "http://example.com"
94
81
  # config.base_saml_location = "#{base}/saml"
95
- # config.reference_id_generator # Default: -> { UUID.generate }
82
+ # config.reference_id_generator # Default: -> { SecureRandom.uuid }
83
+ # config.single_logout_service_post_location = "#{base}/saml/logout"
84
+ # config.single_logout_service_redirect_location = "#{base}/saml/logout"
96
85
  # config.attribute_service_location = "#{base}/saml/attributes"
97
86
  # config.single_service_post_location = "#{base}/saml/auth"
98
87
  # config.session_expiry = 86400 # Default: 0 which means never
88
+ # config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
89
+ # config.compress = true # Default: false which means the SAML Response is not being compressed
90
+ # config.logger = ::Logger.new($stdout) # Default: if in Rails context - Rails.logger, else ->(msg) { puts msg }. Works with either a Ruby Logger or a lambda
99
91
 
100
92
  # Principal (e.g. User) is passed in when you `encode_response`
101
93
  #
102
- # config.name_id.formats # =>
94
+ # config.name_id.formats =
103
95
  # { # All 2.0
104
96
  # email_address: -> (principal) { principal.email_address },
105
97
  # transient: -> (principal) { principal.id },
@@ -169,7 +161,11 @@ CERT
169
161
  service_providers = {
170
162
  "some-issuer-url.com/saml" => {
171
163
  fingerprint: "9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D",
172
- metadata_url: "http://some-issuer-url.com/saml/metadata"
164
+ metadata_url: "http://some-issuer-url.com/saml/metadata",
165
+
166
+ # We now validate AssertionConsumerServiceURL will match the MetadataURL set above.
167
+ # *If* it's not going to match your Metadata URL's Host, then set this so we can validate the host using this list
168
+ response_hosts: ["foo.some-issuer-url.com"]
173
169
  },
174
170
  }
175
171
 
@@ -177,7 +173,7 @@ CERT
177
173
  # settings is an IncomingMetadata object which has a to_h method that needs to be persisted
178
174
  config.service_provider.metadata_persister = ->(identifier, settings) {
179
175
  fname = identifier.to_s.gsub(/\/|:/,"_")
180
- `mkdir -p #{Rails.root.join("cache/saml/metadata")}`
176
+ FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
181
177
  File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
182
178
  Marshal.dump settings.to_h, f
183
179
  end
@@ -188,7 +184,7 @@ CERT
188
184
  # `service_provider` you should return the settings.to_h from above
189
185
  config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
190
186
  fname = identifier.to_s.gsub(/\/|:/,"_")
191
- `mkdir -p #{Rails.root.join("cache/saml/metadata")}`
187
+ FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
192
188
  full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
193
189
  if File.file?(full_filename)
194
190
  File.open full_filename, "rb" do |f|
@@ -204,7 +200,8 @@ CERT
204
200
  end
205
201
  ```
206
202
 
207
- # Keys and Secrets
203
+ ## Keys and Secrets
204
+
208
205
  To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
209
206
  You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
210
207
  Obviously you shouldn't use these if you intend to use this in production environments. In that case,
@@ -214,22 +211,32 @@ and `SamlIdp.config.secret_key` properties.
214
211
 
215
212
  The fingerprint to use, if you use the default X.509 certificate of this gem, is:
216
213
 
214
+ ```bash
215
+ 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
217
216
  ```
218
- 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
217
+
218
+ ## Fingerprint
219
+
220
+ The gem provides an helper to generate a fingerprint for a X.509 certificate.
221
+ The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
222
+
223
+ ```ruby
224
+ SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
219
225
  ```
220
226
 
227
+ ## Service Providers
221
228
 
222
- # Service Providers
223
229
  To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
224
230
  excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
225
231
 
232
+ ## Author
226
233
 
227
- # Author
228
- Jon Phenow, me@jphenow.com
234
+ Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
229
235
 
230
236
  Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
231
237
 
232
- # Copyright
238
+ ## Copyright
239
+
233
240
  Copyright (c) 2012 Sport Ngin.
234
241
  Portions Copyright (c) 2010 OneLogin, LLC
235
242
  Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
@@ -16,10 +16,26 @@ module SamlIdp
16
16
  attr_accessor :expiry
17
17
  attr_accessor :encryption_opts
18
18
  attr_accessor :session_expiry
19
+ attr_accessor :name_id_formats_opts
20
+ attr_accessor :asserted_attributes_opts
19
21
 
20
22
  delegate :config, to: :SamlIdp
21
23
 
22
- def initialize(reference_id, issuer_uri, principal, audience_uri, saml_request_id, saml_acs_url, raw_algorithm, authn_context_classref, expiry=60*60, encryption_opts=nil, session_expiry=nil)
24
+ def initialize(
25
+ reference_id,
26
+ issuer_uri,
27
+ principal,
28
+ audience_uri,
29
+ saml_request_id,
30
+ saml_acs_url,
31
+ raw_algorithm,
32
+ authn_context_classref,
33
+ expiry=60*60,
34
+ encryption_opts=nil,
35
+ session_expiry=nil,
36
+ name_id_formats_opts = nil,
37
+ asserted_attributes_opts = nil
38
+ )
23
39
  self.reference_id = reference_id
24
40
  self.issuer_uri = issuer_uri
25
41
  self.principal = principal
@@ -31,6 +47,8 @@ module SamlIdp
31
47
  self.expiry = expiry
32
48
  self.encryption_opts = encryption_opts
33
49
  self.session_expiry = session_expiry.nil? ? config.session_expiry : session_expiry
50
+ self.name_id_formats_opts = name_id_formats_opts
51
+ self.asserted_attributes_opts = asserted_attributes_opts
34
52
  end
35
53
 
36
54
  def fresh
@@ -98,7 +116,9 @@ module SamlIdp
98
116
  end
99
117
 
100
118
  def asserted_attributes
101
- if principal.respond_to?(:asserted_attributes)
119
+ if asserted_attributes_opts.present? && !asserted_attributes_opts.empty?
120
+ asserted_attributes_opts
121
+ elsif principal.respond_to?(:asserted_attributes)
102
122
  principal.send(:asserted_attributes)
103
123
  elsif !config.attributes.nil? && !config.attributes.empty?
104
124
  config.attributes
@@ -139,10 +159,15 @@ module SamlIdp
139
159
  private :name_id_getter
140
160
 
141
161
  def name_id_format
142
- @name_id_format ||= NameIdFormatter.new(config.name_id.formats).chosen
162
+ @name_id_format ||= NameIdFormatter.new(name_id_formats).chosen
143
163
  end
144
164
  private :name_id_format
145
165
 
166
+ def name_id_formats
167
+ @name_id_formats ||= (name_id_formats_opts || config.name_id.formats)
168
+ end
169
+ private :name_id_formats
170
+
146
171
  def reference_string
147
172
  "_#{reference_id}"
148
173
  end
@@ -1,5 +1,7 @@
1
1
  # encoding: utf-8
2
2
  require 'ostruct'
3
+ require 'securerandom'
4
+
3
5
  module SamlIdp
4
6
  class Configurator
5
7
  attr_accessor :x509_certificate
@@ -13,23 +15,27 @@ module SamlIdp
13
15
  attr_accessor :reference_id_generator
14
16
  attr_accessor :attribute_service_location
15
17
  attr_accessor :single_service_post_location
18
+ attr_accessor :single_service_redirect_location
16
19
  attr_accessor :single_logout_service_post_location
17
20
  attr_accessor :single_logout_service_redirect_location
18
21
  attr_accessor :attributes
19
22
  attr_accessor :service_provider
23
+ attr_accessor :assertion_consumer_service_hosts
20
24
  attr_accessor :session_expiry
25
+ attr_accessor :logger
21
26
 
22
27
  def initialize
23
28
  self.x509_certificate = Default::X509_CERTIFICATE
24
29
  self.secret_key = Default::SECRET_KEY
25
30
  self.algorithm = :sha1
26
- self.reference_id_generator = ->() { UUID.generate }
31
+ self.reference_id_generator = ->() { SecureRandom.uuid }
27
32
  self.service_provider = OpenStruct.new
28
33
  self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
29
34
  self.service_provider.metadata_persister = ->(id, settings) { }
30
35
  self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
31
36
  self.session_expiry = 0
32
37
  self.attributes = {}
38
+ self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
33
39
  end
34
40
 
35
41
  # formats
@@ -1,8 +1,7 @@
1
- # encoding: utf-8
2
1
  require 'openssl'
3
2
  require 'base64'
4
3
  require 'time'
5
- require 'uuid'
4
+ require 'securerandom'
6
5
  require 'saml_idp/request'
7
6
  require 'saml_idp/logout_response_builder'
8
7
  module SamlIdp
@@ -35,13 +34,10 @@ module SamlIdp
35
34
 
36
35
  def validate_saml_request(raw_saml_request = params[:SAMLRequest])
37
36
  decode_request(raw_saml_request)
38
- unless valid_saml_request?
39
- if Rails::VERSION::MAJOR >= 4
40
- head :forbidden
41
- else
42
- render nothing: true, status: :forbidden
43
- end
44
- end
37
+ return true if valid_saml_request?
38
+
39
+ head :forbidden if defined?(::Rails)
40
+ false
45
41
  end
46
42
 
47
43
  def decode_request(raw_saml_request)
@@ -62,6 +58,13 @@ module SamlIdp
62
58
  expiry = opts[:expiry] || 60*60
63
59
  session_expiry = opts[:session_expiry]
64
60
  encryption_opts = opts[:encryption] || nil
61
+ name_id_formats_opts = opts[:name_id_formats] || nil
62
+ asserted_attributes_opts = opts[:attributes] || nil
63
+ signed_message_opts = opts[:signed_message] || false
64
+ name_id_formats_opts = opts[:name_id_formats] || nil
65
+ asserted_attributes_opts = opts[:attributes] || nil
66
+ signed_assertion_opts = opts[:signed_assertion] || true
67
+ compress_opts = opts[:compress] || false
65
68
 
66
69
  SamlResponse.new(
67
70
  reference_id,
@@ -75,11 +78,16 @@ module SamlIdp
75
78
  my_authn_context_classref,
76
79
  expiry,
77
80
  encryption_opts,
78
- session_expiry
81
+ session_expiry,
82
+ name_id_formats_opts,
83
+ asserted_attributes_opts,
84
+ signed_message_opts,
85
+ signed_assertion_opts,
86
+ compress_opts
79
87
  ).build
80
88
  end
81
89
 
82
- def encode_logout_response(principal, opts = {})
90
+ def encode_logout_response(_principal, opts = {})
83
91
  SamlIdp::LogoutResponseBuilder.new(
84
92
  get_saml_response_id,
85
93
  (opts[:issuer_uri] || issuer_uri),
@@ -122,11 +130,11 @@ module SamlIdp
122
130
  end
123
131
 
124
132
  def get_saml_response_id
125
- UUID.generate
133
+ SecureRandom.uuid
126
134
  end
127
135
 
128
136
  def get_saml_reference_id
129
- UUID.generate
137
+ SecureRandom.uuid
130
138
  end
131
139
 
132
140
  def default_algorithm
@@ -61,7 +61,6 @@ module SamlIdp
61
61
  key_info.EncryptedKey Id: 'EK', xmlns: 'http://www.w3.org/2001/04/xmlenc#' do |enc_key|
62
62
  enc_key.EncryptionMethod Algorithm: key_transport_ns
63
63
  enc_key.tag! 'ds:KeyInfo', 'xmlns:ds' => 'http://www.w3.org/2000/09/xmldsig#' do |key_info2|
64
- key_info2.tag! 'ds:KeyName'
65
64
  key_info2.tag! 'ds:X509Data' do |x509_data|
66
65
  x509_data.tag! 'ds:X509Certificate' do |x509_cert|
67
66
  x509_cert << cert.to_s.gsub(/-+(BEGIN|END) CERTIFICATE-+/, '')
@@ -0,0 +1,19 @@
1
+ module SamlIdp
2
+ module Fingerprint
3
+ def self.certificate_digest(cert, sha_size = nil)
4
+ sha_size ||= SamlIdp.config.algorithm
5
+ digest_sha_class(sha_size).hexdigest(OpenSSL::X509::Certificate.new(cert).to_der).scan(/../).join(':')
6
+ end
7
+
8
+ def self.digest_sha_class(sha_size)
9
+ case sha_size
10
+ when :sha256
11
+ Digest::SHA256
12
+ when :sha512
13
+ Digest::SHA512
14
+ else
15
+ raise ArgumentError, "Unsupported sha size parameter: #{sha_size}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -16,16 +16,37 @@ module SamlIdp
16
16
  @document ||= Saml::XML::Document.parse raw
17
17
  end
18
18
 
19
+ def entity_id
20
+ xpath('//md:EntityDescriptor/@entityID', md: metadata_namespace).first.try(:content).to_s
21
+ end
22
+ hashable :entity_id
23
+
19
24
  def sign_assertions
20
25
  doc = xpath(
21
26
  "//md:SPSSODescriptor",
22
27
  ds: signature_namespace,
23
28
  md: metadata_namespace
24
29
  ).first
25
- doc ? !!doc["WantAssertionsSigned"] : false
30
+ if (doc && !doc['WantAssertionsSigned'].nil?)
31
+ return doc['WantAssertionsSigned'].strip.downcase == 'true'
32
+ end
33
+ return false
26
34
  end
27
35
  hashable :sign_assertions
28
36
 
37
+ def sign_authn_request
38
+ doc = xpath(
39
+ "//md:SPSSODescriptor",
40
+ ds: signature_namespace,
41
+ md: metadata_namespace
42
+ ).first
43
+ if (doc && !doc['AuthnRequestsSigned'].nil?)
44
+ return doc['AuthnRequestsSigned'].strip.downcase == 'true'
45
+ end
46
+ return false
47
+ end
48
+ hashable :sign_authn_request
49
+
29
50
  def display_name
30
51
  role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
31
52
  end
@@ -24,13 +24,15 @@ module SamlIdp
24
24
 
25
25
  entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
26
26
  build_key_descriptor descriptor
27
- descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
28
- Location: single_logout_service_post_location
29
- descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
30
- Location: single_logout_service_redirect_location
27
+ build_endpoint descriptor, [
28
+ { tag: 'SingleLogoutService', url: single_logout_service_post_location, bind: 'HTTP-POST' },
29
+ { tag: 'SingleLogoutService', url: single_logout_service_redirect_location, bind: 'HTTP-Redirect'}
30
+ ]
31
31
  build_name_id_formats descriptor
32
- descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
33
- Location: single_service_post_location
32
+ build_endpoint descriptor, [
33
+ { tag: 'SingleSignOnService', url: single_service_post_location, bind: 'HTTP-POST' },
34
+ { tag: 'SingleSignOnService', url: single_service_redirect_location, bind: 'HTTP-Redirect'}
35
+ ]
34
36
  build_attribute descriptor
35
37
  end
36
38
 
@@ -38,8 +40,9 @@ module SamlIdp
38
40
  build_key_descriptor authority_descriptor
39
41
  build_organization authority_descriptor
40
42
  build_contact authority_descriptor
41
- authority_descriptor.AttributeService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
42
- Location: attribute_service_location
43
+ build_endpoint authority_descriptor, [
44
+ { tag: 'AttributeService', url: attribute_service_location, bind: 'HTTP-Redirect' }
45
+ ]
43
46
  build_name_id_formats authority_descriptor
44
47
  build_attribute authority_descriptor
45
48
  end
@@ -69,6 +72,17 @@ module SamlIdp
69
72
  end
70
73
  private :build_name_id_formats
71
74
 
75
+ def build_endpoint(el, end_points)
76
+ end_points.each do |ep|
77
+ next unless ep[:url].present?
78
+
79
+ el.tag! ep[:tag],
80
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:#{ep[:bind]}",
81
+ Location: ep[:url]
82
+ end
83
+ end
84
+ private :build_endpoint
85
+
72
86
  def build_attribute(el)
73
87
  attributes.each do |attribute|
74
88
  el.tag! "saml:Attribute",
@@ -151,6 +165,7 @@ module SamlIdp
151
165
  organization_url
152
166
  attribute_service_location
153
167
  single_service_post_location
168
+ single_service_redirect_location
154
169
  single_logout_service_post_location
155
170
  single_logout_service_redirect_location
156
171
  technical_contact
@@ -6,5 +6,9 @@ module SamlIdp
6
6
  def sign_assertions?
7
7
  !!attributes[:sign_assertions]
8
8
  end
9
+
10
+ def sign_authn_request?
11
+ !!attributes[:sign_authn_request]
12
+ end
9
13
  end
10
14
  end
@@ -1,5 +1,6 @@
1
1
  require 'saml_idp/xml_security'
2
2
  require 'saml_idp/service_provider'
3
+ require 'logger'
3
4
  module SamlIdp
4
5
  class Request
5
6
  def self.from_deflated_request(raw)
@@ -77,10 +78,10 @@ module SamlIdp
77
78
  end
78
79
 
79
80
  def log(msg)
80
- if defined?(::Rails) && Rails.logger
81
- Rails.logger.info msg
81
+ if config.logger.respond_to?(:call)
82
+ config.logger.call msg
82
83
  else
83
- puts msg
84
+ config.logger.info msg
84
85
  end
85
86
  end
86
87
 
@@ -105,13 +106,24 @@ module SamlIdp
105
106
  return false
106
107
  end
107
108
 
109
+ if !service_provider.acceptable_response_hosts.include?(response_host)
110
+ log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
111
+ log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
112
+ return false
113
+ end
114
+
108
115
  return true
109
116
  end
110
117
 
111
118
  def valid_signature?
112
- # Force signatures for logout requests because there is no other
113
- # protection against a cross-site DoS.
114
- service_provider.valid_signature?(document, logout_request?)
119
+ # Force signatures for logout requests because there is no other protection against a cross-site DoS.
120
+ # Validate signature when metadata specify AuthnRequest should be signed
121
+ metadata = service_provider.current_metadata
122
+ if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
123
+ document.valid_signature?(service_provider.fingerprint)
124
+ else
125
+ true
126
+ end
115
127
  end
116
128
 
117
129
  def service_provider?
@@ -136,6 +148,14 @@ module SamlIdp
136
148
  @_session_index ||= xpath("//samlp:SessionIndex", samlp: samlp).first.try(:content)
137
149
  end
138
150
 
151
+ def response_host
152
+ uri = URI(response_url)
153
+ if uri
154
+ uri.host
155
+ end
156
+ end
157
+ private :response_host
158
+
139
159
  def document
140
160
  @_document ||= Saml::XML::Document.parse(raw_xml)
141
161
  end