saml_idp 0.8.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
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