saml_idp 0.9.0 → 0.14.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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +39 -45
  3. data/lib/saml_idp.rb +2 -1
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +4 -1
  6. data/lib/saml_idp/controller.rb +11 -9
  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 +13 -0
  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 +9 -3
  13. data/lib/saml_idp/response_builder.rb +19 -5
  14. data/lib/saml_idp/saml_response.rb +37 -16
  15. data/lib/saml_idp/service_provider.rb +1 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/version.rb +1 -1
  18. data/saml_idp.gemspec +8 -8
  19. data/spec/lib/saml_idp/assertion_builder_spec.rb +73 -0
  20. data/spec/lib/saml_idp/configurator_spec.rb +1 -0
  21. data/spec/lib/saml_idp/controller_spec.rb +24 -0
  22. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  23. data/spec/lib/saml_idp/incoming_metadata_spec.rb +15 -1
  24. data/spec/lib/saml_idp/metadata_builder_spec.rb +23 -0
  25. data/spec/lib/saml_idp/response_builder_spec.rb +3 -1
  26. data/spec/lib/saml_idp/saml_response_spec.rb +25 -2
  27. data/spec/rails_app/app/controllers/saml_controller.rb +1 -5
  28. data/spec/rails_app/app/controllers/saml_idp_controller.rb +47 -8
  29. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
  30. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  31. data/spec/rails_app/config/environments/development.rb +2 -0
  32. data/spec/spec_helper.rb +20 -1
  33. data/spec/support/certificates/sp_cert_req.csr +12 -0
  34. data/spec/support/certificates/sp_private_key.pem +16 -0
  35. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  36. data/spec/support/saml_request_macros.rb +62 -3
  37. data/spec/support/security_helpers.rb +10 -0
  38. metadata +51 -28
  39. 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: 4692b2d2c5266c2db128e4942daddd534c7e22efe32f1f45b02776db2eb8b607
4
- data.tar.gz: cc0b169ea2d024b91590e270c6a6cbe742a661280651917147eb0401f821c6a8
3
+ metadata.gz: 847e2cafec28e67417685e6ba1173aa88ac489172e0e31fa51f2bbab37ef5d10
4
+ data.tar.gz: 8d9ace44b46770b3ca7481461bd16fce9dfc0e2b9925a4a2b5460a780cfddce3
5
5
  SHA512:
6
- metadata.gz: 0d1eaa0e214b1c2cb17970987fc0956c991e33831a05f7bf40936180f6a4fc2a22d5539051b456f7a920a49b7cbd98e83296ae55858025351e9fb693a6f6d595
7
- data.tar.gz: 587c1ca1bc298dc8381bd50e49bd3b361fd011b4ae7509107f2336ec2b9cbc30bd625915a76d5833ebbe7c36eb9181acbee2f1f8b60dae0cb71f27a0436a9fec
6
+ metadata.gz: 6aa29d58babddddc81037fe220276241a355d81a15acb3639a9cc6df935f531bf1fe53e3c5599d49640080776c357ebf6348559ac034313ad64f82f80a7824a0
7
+ data.tar.gz: 9f5b83fd6459476d81c2dc2d3f0e79c401ba01474856f201c5a32615da8e22e4c416d220ef82985205f69f1d87a560a80c3bbae552a39ec4bdb49cc0a1baafa0
data/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Ruby SAML Identity Provider (IdP)
2
2
 
3
- Forked from https://github.com/lawrencepit/ruby-saml-idp
3
+ Forked from <https://github.com/lawrencepit/ruby-saml-idp>
4
4
 
5
5
  [![Build Status](https://travis-ci.org/saml-idp/saml_idp.svg)](https://travis-ci.org/saml-idp/saml_idp)
6
6
  [![Gem Version](https://badge.fury.io/rb/saml_idp.svg)](http://badge.fury.io/rb/saml_idp)
@@ -13,13 +13,15 @@ protocol. It provides a means for managing authentication requests and confirmat
13
13
  This was originally setup by @lawrencepit to test SAML Clients. I took it closer to a real
14
14
  SAML IDP implementation.
15
15
 
16
- # Installation and Usage
16
+ ## Installation and Usage
17
17
 
18
18
  Add this to your Gemfile:
19
19
 
20
+ ```ruby
20
21
  gem 'saml_idp'
22
+ ```
21
23
 
22
- ## Not using rails?
24
+ ### Not using rails?
23
25
 
24
26
  Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
25
27
 
@@ -27,50 +29,32 @@ Basically you call `decode_request(params[:SAMLRequest])` on an incoming request
27
29
  `saml_acs_url` to determine the source for which you need to authenticate a user. How you authenticate
28
30
  a user is entirely up to you.
29
31
 
30
- 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
31
33
  posting to `saml_acs_url` the parameter `SAMLResponse` with the return value from a call to
32
34
  `encode_response(user_email)`.
33
35
 
34
- ## Using rails?
36
+ ### Using rails?
35
37
 
36
- Add to your `routes.rb` file, for example:
38
+ Check out our Wiki page for Rails integration
39
+ [Rails Integration guide](https://github.com/saml-idp/saml_idp/wiki/Rails_Integration)
37
40
 
38
- ```ruby
39
- get '/saml/auth' => 'saml_idp#new'
40
- get '/saml/metadata' => 'saml_idp#show'
41
- post '/saml/auth' => 'saml_idp#create'
42
- match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
43
- ```
41
+ ### Configuration
44
42
 
45
- Create a controller that looks like this, customize to your own situation:
43
+ #### Signed assertions and Signed Response
46
44
 
47
- ```ruby
48
- class SamlIdpController < SamlIdp::IdpController
49
- def idp_authenticate(email, password) # not using params intentionally
50
- user = User.by_email(email).first
51
- user && user.valid_password?(password) ? user : nil
52
- end
53
- private :idp_authenticate
54
-
55
- def idp_make_saml_response(found_user) # not using params intentionally
56
- # NOTE encryption is optional
57
- encode_response found_user, encryption: {
58
- cert: saml_request.service_provider.cert,
59
- block_encryption: 'aes256-cbc',
60
- key_transport: 'rsa-oaep-mgf1p'
61
- }
62
- end
63
- private :idp_make_saml_response
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.
64
46
 
65
- def idp_logout
66
- user = User.by_email(saml_request.name_id)
67
- user.logout
68
- end
69
- private :idp_logout
70
- end
71
- ```
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 `config.signed_message` option. [More about SAML spec](https://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf#page=68)
72
49
 
73
- ## Configuration
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
74
58
 
75
59
  Be sure to load a file like this during your app initialization:
76
60
 
@@ -91,16 +75,17 @@ KEY DATA
91
75
  CERT
92
76
 
93
77
  # config.password = "secret_key_password"
94
- # config.algorithm = :sha256
78
+ # config.algorithm = :sha256 # Default: sha1 only for development.
95
79
  # config.organization_name = "Your Organization"
96
80
  # config.organization_url = "http://example.com"
97
81
  # config.base_saml_location = "#{base}/saml"
98
- # config.reference_id_generator # Default: -> { UUID.generate }
82
+ # config.reference_id_generator # Default: -> { SecureRandom.uuid }
99
83
  # config.single_logout_service_post_location = "#{base}/saml/logout"
100
84
  # config.single_logout_service_redirect_location = "#{base}/saml/logout"
101
85
  # config.attribute_service_location = "#{base}/saml/attributes"
102
86
  # config.single_service_post_location = "#{base}/saml/auth"
103
87
  # config.session_expiry = 86400 # Default: 0 which means never
88
+ # config.signed_message = true # Default: false which means unsigned SAML Response
104
89
 
105
90
  # Principal (e.g. User) is passed in when you `encode_response`
106
91
  #
@@ -213,7 +198,7 @@ CERT
213
198
  end
214
199
  ```
215
200
 
216
- # Keys and Secrets
201
+ ## Keys and Secrets
217
202
 
218
203
  To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
219
204
  You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
@@ -224,22 +209,31 @@ and `SamlIdp.config.secret_key` properties.
224
209
 
225
210
  The fingerprint to use, if you use the default X.509 certificate of this gem, is:
226
211
 
212
+ ```bash
213
+ 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
227
214
  ```
228
- 9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
215
+
216
+ ## Fingerprint
217
+
218
+ The gem provides an helper to generate a fingerprint for a X.509 certificate.
219
+ The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
220
+
221
+ ```ruby
222
+ Fingerprint.certificate_digest(x509_cert, :sha512)
229
223
  ```
230
224
 
231
- # Service Providers
225
+ ## Service Providers
232
226
 
233
227
  To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
234
228
  excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
235
229
 
236
- # Author
230
+ ## Author
237
231
 
238
232
  Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
239
233
 
240
234
  Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
241
235
 
242
- # Copyright
236
+ ## Copyright
243
237
 
244
238
  Copyright (c) 2012 Sport Ngin.
245
239
  Portions Copyright (c) 2010 OneLogin, LLC
data/lib/saml_idp.rb CHANGED
@@ -8,7 +8,8 @@ module SamlIdp
8
8
  require 'saml_idp/default'
9
9
  require 'saml_idp/metadata_builder'
10
10
  require 'saml_idp/version'
11
- require 'saml_idp/engine' if defined?(::Rails) && Rails::VERSION::MAJOR > 2
11
+ require 'saml_idp/fingerprint'
12
+ require 'saml_idp/engine' if defined?(::Rails)
12
13
 
13
14
  def self.config
14
15
  @config ||= SamlIdp::Configurator.new
@@ -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,6 +15,7 @@ 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
@@ -24,7 +27,7 @@ module SamlIdp
24
27
  self.x509_certificate = Default::X509_CERTIFICATE
25
28
  self.secret_key = Default::SECRET_KEY
26
29
  self.algorithm = :sha1
27
- self.reference_id_generator = ->() { UUID.generate }
30
+ self.reference_id_generator = ->() { SecureRandom.uuid }
28
31
  self.service_provider = OpenStruct.new
29
32
  self.service_provider.finder = ->(_) { Default::SERVICE_PROVIDER }
30
33
  self.service_provider.metadata_persister = ->(id, settings) { }
@@ -2,7 +2,7 @@
2
2
  require 'openssl'
3
3
  require 'base64'
4
4
  require 'time'
5
- require 'uuid'
5
+ require 'securerandom'
6
6
  require 'saml_idp/request'
7
7
  require 'saml_idp/logout_response_builder'
8
8
  module SamlIdp
@@ -37,11 +37,7 @@ module SamlIdp
37
37
  decode_request(raw_saml_request)
38
38
  return true if valid_saml_request?
39
39
  if defined?(::Rails)
40
- if Rails::VERSION::MAJOR >= 4
41
- head :forbidden
42
- else
43
- render nothing: true, status: :forbidden
44
- end
40
+ head :forbidden
45
41
  end
46
42
  false
47
43
  end
@@ -64,6 +60,9 @@ module SamlIdp
64
60
  expiry = opts[:expiry] || 60*60
65
61
  session_expiry = opts[:session_expiry]
66
62
  encryption_opts = opts[:encryption] || 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
67
66
 
68
67
  SamlResponse.new(
69
68
  reference_id,
@@ -77,7 +76,10 @@ module SamlIdp
77
76
  my_authn_context_classref,
78
77
  expiry,
79
78
  encryption_opts,
80
- session_expiry
79
+ session_expiry,
80
+ signed_message_opts,
81
+ name_id_formats_opts,
82
+ asserted_attributes_opts
81
83
  ).build
82
84
  end
83
85
 
@@ -124,11 +126,11 @@ module SamlIdp
124
126
  end
125
127
 
126
128
  def get_saml_response_id
127
- UUID.generate
129
+ SecureRandom.uuid
128
130
  end
129
131
 
130
132
  def get_saml_reference_id
131
- UUID.generate
133
+ SecureRandom.uuid
132
134
  end
133
135
 
134
136
  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
@@ -34,6 +34,19 @@ module SamlIdp
34
34
  end
35
35
  hashable :sign_assertions
36
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
+
37
50
  def display_name
38
51
  role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
39
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