saml_idp 0.7.2 → 1.0.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 (59) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -1
  3. data/README.md +71 -55
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +9 -3
  6. data/lib/saml_idp/controller.rb +27 -16
  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 +31 -1
  10. data/lib/saml_idp/metadata_builder.rb +25 -9
  11. data/lib/saml_idp/persisted_metadata.rb +4 -0
  12. data/lib/saml_idp/request.rb +103 -13
  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 +16 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/signature_builder.rb +2 -1
  18. data/lib/saml_idp/signed_info_builder.rb +2 -2
  19. data/lib/saml_idp/version.rb +1 -1
  20. data/lib/saml_idp/xml_security.rb +20 -15
  21. data/lib/saml_idp.rb +4 -3
  22. data/saml_idp.gemspec +46 -42
  23. data/spec/acceptance/idp_controller_spec.rb +5 -4
  24. data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
  25. data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
  26. data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
  27. data/spec/lib/saml_idp/configurator_spec.rb +45 -7
  28. data/spec/lib/saml_idp/controller_spec.rb +86 -25
  29. data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
  30. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  31. data/spec/lib/saml_idp/incoming_metadata_spec.rb +134 -0
  32. data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
  33. data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
  34. data/spec/lib/saml_idp/request_spec.rb +153 -64
  35. data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
  36. data/spec/lib/saml_idp/saml_response_spec.rb +146 -12
  37. data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
  38. data/spec/lib/saml_idp/signable_spec.rb +1 -1
  39. data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
  40. data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
  41. data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
  42. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  43. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +3 -4
  44. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  45. data/spec/rails_app/config/application.rb +1 -6
  46. data/spec/rails_app/config/boot.rb +1 -1
  47. data/spec/rails_app/config/environments/development.rb +2 -5
  48. data/spec/rails_app/config/environments/production.rb +1 -0
  49. data/spec/rails_app/config/environments/test.rb +1 -0
  50. data/spec/spec_helper.rb +23 -1
  51. data/spec/support/certificates/sp_cert_req.csr +12 -0
  52. data/spec/support/certificates/sp_private_key.pem +16 -0
  53. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  54. data/spec/support/saml_request_macros.rb +107 -5
  55. data/spec/support/security_helpers.rb +12 -2
  56. data/spec/xml_security_spec.rb +19 -15
  57. metadata +146 -80
  58. data/app/controllers/saml_idp/idp_controller.rb +0 -59
  59. 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: 4a0fd46437c04823ad86f269552282b0252cb888294345984c5e16b88bcfa9cf
4
+ data.tar.gz: b7c56c2516f7aaf392fe5a999e98f67af0b79f799374757f3724724605616fc4
5
5
  SHA512:
6
- metadata.gz: 4d41a8d82d518fc503b50d7206c1f12a59e822aa73dcd03f067d25521404446f66aba9d6c6ec478bfa7b9bb9794859c92d530e41cdae5ebcf5fe26e61f96e0a9
7
- data.tar.gz: c143f75f4326f1f1b927b91b5226797881d2bf2ea69155a4769d3805bd628d199f08edd5466529865cdffe57b2fc657cda3f100138a86aa3040af1e8682f0d10
6
+ metadata.gz: 558df23c6ffefed06a205df4ea72cc530f7a963c64741e0bb86cae16f7ffd26a822299793a6b4d7a587addf49428a71e3449f46650d1a26990ec563b690a6cac
7
+ data.tar.gz: ee6eec4ee019c3e01443bbc6d383e4eb6dcd422e3eb5dddcbefd10666799d9604e094e9dec22b213780a415f9efaaebfe54f8b512fef90322f6ce3cc89e4d751
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,53 @@ 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
- Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
25
- `saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
26
- a user is entirely up to you.
27
+ Basically, you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
28
+ `saml_acs_url` to determine the source for which you need to authenticate a user.
29
+ If the signature (`Signature`) and signing algorithm (`SigAlg`) are provided as external parameters in the request,
30
+ you can pass those parameters as `decode_request(params[:SAMLRequest], params[:Signature], params[:SigAlg], params[:RelayState])`.
31
+ Then, you can verify the request signature with the `valid?` method.
32
+
33
+ How you authenticate a user is entirely up to you.
27
34
 
28
- Once a user has successfully authenticated on your system send the Service Provider a SAMLReponse by
35
+ Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
29
36
  posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
30
37
  `encode_response(user_email)`.
31
38
 
32
- ## Using rails?
33
- Add to your `routes.rb` file, for example:
39
+ ### Using rails?
34
40
 
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
- ```
41
+ Check out our Wiki page for Rails integration
42
+ [Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
41
43
 
42
- Create a controller that looks like this, customize to your own situation:
44
+ ### Configuration
43
45
 
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
46
+ #### Signed assertions and Signed Response
61
47
 
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
- ```
48
+ 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.
49
+ 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
50
 
70
- ## Configuration
51
+ Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
52
+ 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)
53
+
54
+ #### Signing algorithm
55
+
56
+ Following algorithms you can set in your response signing algorithm
57
+ :sha1 - RSA-SHA1 default value but not recommended to production environment
58
+ Highly recommended to use one of following algorithm, suit with your computing power.
59
+ :sha256 - RSA-SHA256
60
+ :sha384 - RSA-SHA384
61
+ :sha512 - RSA-SHA512
71
62
 
72
63
  Be sure to load a file like this during your app initialization:
73
64
 
@@ -87,19 +78,29 @@ KEY DATA
87
78
  -----END RSA PRIVATE KEY-----
88
79
  CERT
89
80
 
81
+ # x509_certificate, secret_key, and password may also be set from within a proc, for example:
82
+ # config.x509_certificate = -> { File.read("cert.pem") }
83
+ # config.secret_key = -> { SecretKeyFinder.key_for(id: 1) }
84
+ # config.password = -> { "password" }
85
+
90
86
  # config.password = "secret_key_password"
91
- # config.algorithm = :sha256
87
+ # config.algorithm = :sha256 # Default: sha1 only for development.
92
88
  # config.organization_name = "Your Organization"
93
89
  # config.organization_url = "http://example.com"
94
90
  # config.base_saml_location = "#{base}/saml"
95
- # config.reference_id_generator # Default: -> { UUID.generate }
91
+ # config.reference_id_generator # Default: -> { SecureRandom.uuid }
92
+ # config.single_logout_service_post_location = "#{base}/saml/logout"
93
+ # config.single_logout_service_redirect_location = "#{base}/saml/logout"
96
94
  # config.attribute_service_location = "#{base}/saml/attributes"
97
95
  # config.single_service_post_location = "#{base}/saml/auth"
98
96
  # config.session_expiry = 86400 # Default: 0 which means never
97
+ # config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
98
+ # config.compress = true # Default: false which means the SAML Response is not being compressed
99
+ # 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
100
 
100
101
  # Principal (e.g. User) is passed in when you `encode_response`
101
102
  #
102
- # config.name_id.formats # =>
103
+ # config.name_id.formats =
103
104
  # { # All 2.0
104
105
  # email_address: -> (principal) { principal.email_address },
105
106
  # transient: -> (principal) { principal.id },
@@ -169,7 +170,11 @@ CERT
169
170
  service_providers = {
170
171
  "some-issuer-url.com/saml" => {
171
172
  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"
173
+ metadata_url: "http://some-issuer-url.com/saml/metadata",
174
+
175
+ # We now validate AssertionConsumerServiceURL will match the MetadataURL set above.
176
+ # *If* it's not going to match your Metadata URL's Host, then set this so we can validate the host using this list
177
+ response_hosts: ["foo.some-issuer-url.com"]
173
178
  },
174
179
  }
175
180
 
@@ -177,7 +182,7 @@ CERT
177
182
  # settings is an IncomingMetadata object which has a to_h method that needs to be persisted
178
183
  config.service_provider.metadata_persister = ->(identifier, settings) {
179
184
  fname = identifier.to_s.gsub(/\/|:/,"_")
180
- `mkdir -p #{Rails.root.join("cache/saml/metadata")}`
185
+ FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
181
186
  File.open Rails.root.join("cache/saml/metadata/#{fname}"), "r+b" do |f|
182
187
  Marshal.dump settings.to_h, f
183
188
  end
@@ -188,7 +193,7 @@ CERT
188
193
  # `service_provider` you should return the settings.to_h from above
189
194
  config.service_provider.persisted_metadata_getter = ->(identifier, service_provider){
190
195
  fname = identifier.to_s.gsub(/\/|:/,"_")
191
- `mkdir -p #{Rails.root.join("cache/saml/metadata")}`
196
+ FileUtils.mkdir_p(Rails.root.join('cache', 'saml', 'metadata').to_s)
192
197
  full_filename = Rails.root.join("cache/saml/metadata/#{fname}")
193
198
  if File.file?(full_filename)
194
199
  File.open full_filename, "rb" do |f|
@@ -204,7 +209,8 @@ CERT
204
209
  end
205
210
  ```
206
211
 
207
- # Keys and Secrets
212
+ ## Keys and Secrets
213
+
208
214
  To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
209
215
  You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
210
216
  Obviously you shouldn't use these if you intend to use this in production environments. In that case,
@@ -214,22 +220,32 @@ and `SamlIdp.config.secret_key` properties.
214
220
 
215
221
  The fingerprint to use, if you use the default X.509 certificate of this gem, is:
216
222
 
223
+ ```bash
224
+ 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
217
225
  ```
218
- 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
226
+
227
+ ## Fingerprint
228
+
229
+ The gem provides an helper to generate a fingerprint for a X.509 certificate.
230
+ The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
231
+
232
+ ```ruby
233
+ SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
219
234
  ```
220
235
 
236
+ ## Service Providers
221
237
 
222
- # Service Providers
223
238
  To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
224
239
  excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
225
240
 
241
+ ## Author
226
242
 
227
- # Author
228
- Jon Phenow, me@jphenow.com
243
+ Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
229
244
 
230
245
  Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
231
246
 
232
- # Copyright
247
+ ## Copyright
248
+
233
249
  Copyright (c) 2012 Sport Ngin.
234
250
  Portions Copyright (c) 2010 OneLogin, LLC
235
251
  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
- self.x509_certificate = Default::X509_CERTIFICATE
24
- self.secret_key = Default::SECRET_KEY
28
+ self.x509_certificate = -> { Default::X509_CERTIFICATE }
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.respond_to?(:logger)) ? 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
@@ -34,18 +33,18 @@ module SamlIdp
34
33
  end
35
34
 
36
35
  def validate_saml_request(raw_saml_request = params[:SAMLRequest])
37
- 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
36
+ decode_request(raw_saml_request, params[:Signature], params[:SigAlg], params[:RelayState])
37
+ valid_saml_request?
45
38
  end
46
39
 
47
- def decode_request(raw_saml_request)
48
- @saml_request = Request.from_deflated_request(raw_saml_request)
40
+ def decode_request(raw_saml_request, signature, sig_algorithm, relay_state)
41
+ @saml_request = Request.from_deflated_request(
42
+ raw_saml_request,
43
+ saml_request: raw_saml_request,
44
+ signature: signature,
45
+ sig_algorithm: sig_algorithm,
46
+ relay_state: relay_state
47
+ )
49
48
  end
50
49
 
51
50
  def authn_context_classref
@@ -62,6 +61,13 @@ module SamlIdp
62
61
  expiry = opts[:expiry] || 60*60
63
62
  session_expiry = opts[:session_expiry]
64
63
  encryption_opts = opts[:encryption] || nil
64
+ name_id_formats_opts = opts[:name_id_formats] || nil
65
+ asserted_attributes_opts = opts[:attributes] || nil
66
+ signed_message_opts = opts[:signed_message] || false
67
+ name_id_formats_opts = opts[:name_id_formats] || nil
68
+ asserted_attributes_opts = opts[:attributes] || nil
69
+ signed_assertion_opts = opts[:signed_assertion].nil? ? true : opts[:signed_assertion]
70
+ compress_opts = opts[:compress] || false
65
71
 
66
72
  SamlResponse.new(
67
73
  reference_id,
@@ -75,11 +81,16 @@ module SamlIdp
75
81
  my_authn_context_classref,
76
82
  expiry,
77
83
  encryption_opts,
78
- session_expiry
84
+ session_expiry,
85
+ name_id_formats_opts,
86
+ asserted_attributes_opts,
87
+ signed_message_opts,
88
+ signed_assertion_opts,
89
+ compress_opts
79
90
  ).build
80
91
  end
81
92
 
82
- def encode_logout_response(principal, opts = {})
93
+ def encode_logout_response(_principal, opts = {})
83
94
  SamlIdp::LogoutResponseBuilder.new(
84
95
  get_saml_response_id,
85
96
  (opts[:issuer_uri] || issuer_uri),
@@ -122,11 +133,11 @@ module SamlIdp
122
133
  end
123
134
 
124
135
  def get_saml_response_id
125
- UUID.generate
136
+ SecureRandom.uuid
126
137
  end
127
138
 
128
139
  def get_saml_reference_id
129
- UUID.generate
140
+ SecureRandom.uuid
130
141
  end
131
142
 
132
143
  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
@@ -42,6 +63,15 @@ module SamlIdp
42
63
  end
43
64
  hashable :contact_person
44
65
 
66
+ def unspecified_certificate
67
+ xpath(
68
+ "//md:SPSSODescriptor/md:KeyDescriptor[not(@use)]/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
69
+ ds: signature_namespace,
70
+ md: metadata_namespace
71
+ ).first.try(:content).to_s
72
+ end
73
+ hashable :unspecified_certificate
74
+
45
75
  def signing_certificate
46
76
  xpath(
47
77
  "//md:SPSSODescriptor/md:KeyDescriptor[@use='signing']/ds:KeyInfo/ds:X509Data/ds:X509Certificate",
@@ -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",
@@ -138,7 +152,8 @@ module SamlIdp
138
152
  private :raw_algorithm
139
153
 
140
154
  def x509_certificate
141
- SamlIdp.config.x509_certificate
155
+ certificate = SamlIdp.config.x509_certificate.is_a?(Proc) ? SamlIdp.config.x509_certificate.call : SamlIdp.config.x509_certificate
156
+ certificate
142
157
  .to_s
143
158
  .gsub(/-----BEGIN CERTIFICATE-----/,"")
144
159
  .gsub(/-----END CERTIFICATE-----/,"")
@@ -151,6 +166,7 @@ module SamlIdp
151
166
  organization_url
152
167
  attribute_service_location
153
168
  single_service_post_location
169
+ single_service_redirect_location
154
170
  single_logout_service_post_location
155
171
  single_logout_service_redirect_location
156
172
  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