saml_idp 0.7.2 → 0.16.0

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 (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