saml_idp 0.8.0 → 0.15.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +49 -47
  3. data/lib/saml_idp/assertion_builder.rb +28 -3
  4. data/lib/saml_idp/configurator.rb +6 -1
  5. data/lib/saml_idp/controller.rb +19 -11
  6. data/lib/saml_idp/encryptor.rb +0 -1
  7. data/lib/saml_idp/fingerprint.rb +19 -0
  8. data/lib/saml_idp/incoming_metadata.rb +18 -0
  9. data/lib/saml_idp/metadata_builder.rb +23 -8
  10. data/lib/saml_idp/persisted_metadata.rb +4 -0
  11. data/lib/saml_idp/request.rb +13 -6
  12. data/lib/saml_idp/response_builder.rb +26 -6
  13. data/lib/saml_idp/saml_response.rb +62 -28
  14. data/lib/saml_idp/service_provider.rb +1 -6
  15. data/lib/saml_idp/signable.rb +1 -2
  16. data/lib/saml_idp/version.rb +1 -1
  17. data/lib/saml_idp/xml_security.rb +1 -1
  18. data/lib/saml_idp.rb +2 -1
  19. data/saml_idp.gemspec +31 -31
  20. data/spec/lib/saml_idp/assertion_builder_spec.rb +143 -0
  21. data/spec/lib/saml_idp/configurator_spec.rb +2 -0
  22. data/spec/lib/saml_idp/controller_spec.rb +24 -0
  23. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  24. data/spec/lib/saml_idp/incoming_metadata_spec.rb +20 -1
  25. data/spec/lib/saml_idp/metadata_builder_spec.rb +23 -0
  26. data/spec/lib/saml_idp/request_spec.rb +43 -9
  27. data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
  28. data/spec/lib/saml_idp/saml_response_spec.rb +122 -7
  29. data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
  30. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  31. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
  32. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  33. data/spec/rails_app/config/application.rb +1 -0
  34. data/spec/rails_app/config/boot.rb +1 -1
  35. data/spec/rails_app/config/environments/development.rb +2 -0
  36. data/spec/spec_helper.rb +20 -1
  37. data/spec/support/certificates/sp_cert_req.csr +12 -0
  38. data/spec/support/certificates/sp_private_key.pem +16 -0
  39. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  40. data/spec/support/saml_request_macros.rb +62 -3
  41. data/spec/support/security_helpers.rb +10 -0
  42. metadata +83 -61
  43. data/app/controllers/saml_idp/idp_controller.rb +0 -59
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33008cf3468ff0038f5308fb5820edc2023c071d1d0a046c6778f9fd393d96da
4
- data.tar.gz: 95e3adfcc852d0384ae3fa5c8740a820f16db073f83aef78bdba524d3581f8ed
3
+ metadata.gz: 4da444f25fd4d8cb2b53d847ee3ffa44adab3b2c4b64be57a6935c0922acf1a8
4
+ data.tar.gz: ff0beb64e76c37a0bbcb098f0bd5a50b4d15ff124d63d9c01d421f4693f6fa2d
5
5
  SHA512:
6
- metadata.gz: e66b93acbc0ab6b965258a6ba2c205e4563e197206b94c50dbd5a7603a36ebda7c2dc47d57932b821dcbd4a4e3e033616d2c6879141222b0496a0faa1811af6f
7
- data.tar.gz: d3aee5f5466e2b7c70cb9434eb6c0fe3036d55335ec65555c489124ce4d4889e4051969d8f1cc87e2b3a523f1fbbc447a8a609a86e62a4ffd53f0cf4a7f31285
6
+ metadata.gz: 1fe91e27e817106e66738c73c670ce064c18b18e9528f7aef3c2a4dc87658c9262877b7a62f491c29ff371d39e0306721bc1f97af7ec3fb6fd1d23b8550b32ce
7
+ data.tar.gz: d6ee196976da4fe1af818bca3183632372ef2e1e3059891e75a13dc39caa9fb86c3d312c384d926877f9e14cfbd751d566d517742e72e0bb77e276e446a88aed
data/README.md CHANGED
@@ -1,5 +1,6 @@
1
1
  # Ruby SAML Identity Provider (IdP)
2
- Forked from https://github.com/lawrencepit/ruby-saml-idp
2
+
3
+ Forked from <https://github.com/lawrencepit/ruby-saml-idp>
3
4
 
4
5
  [![Build Status](https://travis-ci.org/saml-idp/saml_idp.svg)](https://travis-ci.org/saml-idp/saml_idp)
5
6
  [![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)
@@ -12,62 +13,49 @@ protocol. It provides a means for managing authentication requests and confirmat
12
13
  This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
13
14
  SAML IDP implementation.
14
15
 
15
- # Installation and Usage
16
+ ## Installation and Usage
16
17
 
17
18
  Add this to your Gemfile:
18
19
 
20
+ ```ruby
19
21
  gem 'saml_idp'
22
+ ```
23
+
24
+ ### Not using rails?
20
25
 
21
- ## Not using rails?
22
26
  Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
23
27
 
24
28
  Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
25
29
  `saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
26
30
  a user is entirely up to you.
27
31
 
28
- Once a user has successfully authenticated on your system send the Service Provider a SAMLReponse by
32
+ Once a user has successfully authenticated on your system send the Service Provider a SAMLResponse by
29
33
  posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
30
34
  `encode_response(user_email)`.
31
35
 
32
- ## Using rails?
33
- Add to your `routes.rb` file, for example:
36
+ ### Using rails?
34
37
 
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
- ```
38
+ Check out our Wiki page for Rails integration
39
+ [Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
41
40
 
42
- Create a controller that looks like this, customize to your own situation:
41
+ ### Configuration
43
42
 
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
43
+ #### Signed assertions and Signed Response
61
44
 
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
- ```
45
+ 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.
46
+ 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.
47
+
48
+ Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
49
+ 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)
50
+
51
+ #### Signing algorithm
69
52
 
70
- ## Configuration
53
+ Following algorithms you can set in your response signing algorithm
54
+ :sha1 - RSA-SHA1 default value but not recommended to production environment
55
+ Highly recommended to use one of following algorithm, suit with your computing power.
56
+ :sha256 - RSA-SHA256
57
+ :sha384 - RSA-SHA384
58
+ :sha512 - RSA-SHA512
71
59
 
72
60
  Be sure to load a file like this during your app initialization:
73
61
 
@@ -88,20 +76,23 @@ KEY DATA
88
76
  CERT
89
77
 
90
78
  # config.password = "secret_key_password"
91
- # config.algorithm = :sha256
79
+ # config.algorithm = :sha256 # Default: sha1 only for development.
92
80
  # config.organization_name = "Your Organization"
93
81
  # config.organization_url = "http://example.com"
94
82
  # config.base_saml_location = "#{base}/saml"
95
- # config.reference_id_generator # Default: -> { UUID.generate }
83
+ # config.reference_id_generator # Default: -> { SecureRandom.uuid }
96
84
  # config.single_logout_service_post_location = "#{base}/saml/logout"
97
85
  # config.single_logout_service_redirect_location = "#{base}/saml/logout"
98
86
  # config.attribute_service_location = "#{base}/saml/attributes"
99
87
  # config.single_service_post_location = "#{base}/saml/auth"
100
88
  # config.session_expiry = 86400 # Default: 0 which means never
89
+ # config.signed_assertion = false # Default: true which means signed assertions on the SAML Response
90
+ # config.compress = true # Default: false which means the SAML Response is not being compressed
91
+ # 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
101
92
 
102
93
  # Principal (e.g. User) is passed in when you `encode_response`
103
94
  #
104
- # config.name_id.formats # =>
95
+ # config.name_id.formats =
105
96
  # { # All 2.0
106
97
  # email_address: -> (principal) { principal.email_address },
107
98
  # transient: -> (principal) { principal.id },
@@ -210,7 +201,8 @@ CERT
210
201
  end
211
202
  ```
212
203
 
213
- # Keys and Secrets
204
+ ## Keys and Secrets
205
+
214
206
  To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
215
207
  You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
216
208
  Obviously you shouldn't use these if you intend to use this in production environments. In that case,
@@ -220,22 +212,32 @@ and `SamlIdp.config.secret_key` properties.
220
212
 
221
213
  The fingerprint to use, if you use the default X.509 certificate of this gem, is:
222
214
 
215
+ ```bash
216
+ 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
223
217
  ```
224
- 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
218
+
219
+ ## Fingerprint
220
+
221
+ The gem provides an helper to generate a fingerprint for a X.509 certificate.
222
+ The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
223
+
224
+ ```ruby
225
+ SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
225
226
  ```
226
227
 
228
+ ## Service Providers
227
229
 
228
- # Service Providers
229
230
  To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
230
231
  excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
231
232
 
233
+ ## Author
232
234
 
233
- # Author
234
- Jon Phenow, me@jphenow.com
235
+ Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
235
236
 
236
237
  Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
237
238
 
238
- # Copyright
239
+ ## Copyright
240
+
239
241
  Copyright (c) 2012 Sport Ngin.
240
242
  Portions Copyright (c) 2010 OneLogin, LLC
241
243
  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,24 +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
20
23
  attr_accessor :assertion_consumer_service_hosts
21
24
  attr_accessor :session_expiry
25
+ attr_accessor :logger
22
26
 
23
27
  def initialize
24
28
  self.x509_certificate = Default::X509_CERTIFICATE
25
29
  self.secret_key = Default::SECRET_KEY
26
30
  self.algorithm = :sha1
27
- self.reference_id_generator = ->() { UUID.generate }
31
+ self.reference_id_generator = ->() { SecureRandom.uuid }
28
32
  self.service_provider = OpenStruct.new
29
33
  self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
30
34
  self.service_provider.metadata_persister = ->(id, settings) { }
31
35
  self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
32
36
  self.session_expiry = 0
33
37
  self.attributes = {}
38
+ self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
34
39
  end
35
40
 
36
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
@@ -36,11 +35,8 @@ module SamlIdp
36
35
  def validate_saml_request(raw_saml_request = params[:SAMLRequest])
37
36
  decode_request(raw_saml_request)
38
37
  return true if valid_saml_request?
39
- if Rails::VERSION::MAJOR >= 4
40
- head :forbidden
41
- else
42
- render nothing: true, status: :forbidden
43
- end
38
+
39
+ head :forbidden if defined?(::Rails)
44
40
  false
45
41
  end
46
42
 
@@ -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_assertion_opts,
85
+ signed_message_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,6 +16,11 @@ 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",
@@ -29,6 +34,19 @@ module SamlIdp
29
34
  end
30
35
  hashable :sign_assertions
31
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
+
32
50
  def display_name
33
51
  role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
34
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.class <= ::Logger
82
+ config.logger.info msg
82
83
  else
83
- puts msg
84
+ config.logger.call msg
84
85
  end
85
86
  end
86
87
 
@@ -106,6 +107,7 @@ module SamlIdp
106
107
  end
107
108
 
108
109
  if !service_provider.acceptable_response_hosts.include?(response_host)
110
+ log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
109
111
  log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
110
112
  return false
111
113
  end
@@ -114,9 +116,14 @@ module SamlIdp
114
116
  end
115
117
 
116
118
  def valid_signature?
117
- # Force signatures for logout requests because there is no other
118
- # protection against a cross-site DoS.
119
- 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
120
127
  end
121
128
 
122
129
  def service_provider?
@@ -1,32 +1,45 @@
1
1
  require 'builder'
2
+ require 'saml_idp/algorithmable'
3
+ require 'saml_idp/signable'
2
4
  module SamlIdp
3
5
  class ResponseBuilder
6
+ include Algorithmable
7
+ include Signable
4
8
  attr_accessor :response_id
5
9
  attr_accessor :issuer_uri
6
10
  attr_accessor :saml_acs_url
7
11
  attr_accessor :saml_request_id
8
12
  attr_accessor :assertion_and_signature
13
+ attr_accessor :raw_algorithm
9
14
 
10
- def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature)
15
+ alias_method :reference_id, :response_id
16
+
17
+ def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature, raw_algorithm)
11
18
  self.response_id = response_id
12
19
  self.issuer_uri = issuer_uri
13
20
  self.saml_acs_url = saml_acs_url
14
21
  self.saml_request_id = saml_request_id
15
22
  self.assertion_and_signature = assertion_and_signature
23
+ self.raw_algorithm = raw_algorithm
16
24
  end
17
25
 
18
- def encoded
19
- @encoded ||= encode
26
+ def encoded(signed_message: false, compress: false)
27
+ @encoded ||= signed_message ? encode_signed_message(compress) : encode_raw_message(compress)
20
28
  end
21
29
 
22
30
  def raw
23
31
  build
24
32
  end
25
33
 
26
- def encode
27
- Base64.strict_encode64(raw)
34
+ def encode_raw_message(compress)
35
+ Base64.strict_encode64(compress ? deflate(raw) : raw)
36
+ end
37
+ private :encode_raw_message
38
+
39
+ def encode_signed_message(compress)
40
+ Base64.strict_encode64(compress ? deflate(signed) : signed)
28
41
  end
29
- private :encode
42
+ private :encode_signed_message
30
43
 
31
44
  def build
32
45
  resp_options = {}
@@ -41,6 +54,7 @@ module SamlIdp
41
54
  builder = Builder::XmlMarkup.new
42
55
  builder.tag! "samlp:Response", resp_options do |response|
43
56
  response.Issuer issuer_uri, xmlns: Saml::XML::Namespaces::ASSERTION
57
+ sign response
44
58
  response.tag! "samlp:Status" do |status|
45
59
  status.tag! "samlp:StatusCode", Value: Saml::XML::Namespaces::Statuses::SUCCESS
46
60
  end
@@ -52,11 +66,17 @@ module SamlIdp
52
66
  def response_id_string
53
67
  "_#{response_id}"
54
68
  end
69
+ alias_method :reference_id, :response_id
55
70
  private :response_id_string
56
71
 
57
72
  def now_iso
58
73
  Time.now.utc.iso8601
59
74
  end
60
75
  private :now_iso
76
+
77
+ def deflate(inflated)
78
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
79
+ end
80
+ private :deflate
61
81
  end
62
82
  end