saml_idp 0.9.0 → 0.14.0

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