saml_idp 0.14.0 → 0.15.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 847e2cafec28e67417685e6ba1173aa88ac489172e0e31fa51f2bbab37ef5d10
4
- data.tar.gz: 8d9ace44b46770b3ca7481461bd16fce9dfc0e2b9925a4a2b5460a780cfddce3
3
+ metadata.gz: 4da444f25fd4d8cb2b53d847ee3ffa44adab3b2c4b64be57a6935c0922acf1a8
4
+ data.tar.gz: ff0beb64e76c37a0bbcb098f0bd5a50b4d15ff124d63d9c01d421f4693f6fa2d
5
5
  SHA512:
6
- metadata.gz: 6aa29d58babddddc81037fe220276241a355d81a15acb3639a9cc6df935f531bf1fe53e3c5599d49640080776c357ebf6348559ac034313ad64f82f80a7824a0
7
- data.tar.gz: 9f5b83fd6459476d81c2dc2d3f0e79c401ba01474856f201c5a32615da8e22e4c416d220ef82985205f69f1d87a560a80c3bbae552a39ec4bdb49cc0a1baafa0
6
+ metadata.gz: 1fe91e27e817106e66738c73c670ce064c18b18e9528f7aef3c2a4dc87658c9262877b7a62f491c29ff371d39e0306721bc1f97af7ec3fb6fd1d23b8550b32ce
7
+ data.tar.gz: d6ee196976da4fe1af818bca3183632372ef2e1e3059891e75a13dc39caa9fb86c3d312c384d926877f9e14cfbd751d566d517742e72e0bb77e276e446a88aed
data/README.md CHANGED
@@ -42,10 +42,11 @@ Check out our Wiki page for Rails integration
42
42
 
43
43
  #### Signed assertions and Signed Response
44
44
 
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.
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.
46
47
 
47
48
  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)
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)
49
50
 
50
51
  #### Signing algorithm
51
52
 
@@ -85,7 +86,9 @@ CERT
85
86
  # config.attribute_service_location = "#{base}/saml/attributes"
86
87
  # config.single_service_post_location = "#{base}/saml/auth"
87
88
  # config.session_expiry = 86400 # Default: 0 which means never
88
- # config.signed_message = true # Default: false which means unsigned SAML Response
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
89
92
 
90
93
  # Principal (e.g. User) is passed in when you `encode_response`
91
94
  #
@@ -219,7 +222,7 @@ The gem provides an helper to generate a fingerprint for a X.509 certificate.
219
222
  The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
220
223
 
221
224
  ```ruby
222
- Fingerprint.certificate_digest(x509_cert, :sha512)
225
+ SamlIdp::Fingerprint.certificate_digest(x509_cert, :sha512)
223
226
  ```
224
227
 
225
228
  ## Service Providers
@@ -22,6 +22,7 @@ module SamlIdp
22
22
  attr_accessor :service_provider
23
23
  attr_accessor :assertion_consumer_service_hosts
24
24
  attr_accessor :session_expiry
25
+ attr_accessor :logger
25
26
 
26
27
  def initialize
27
28
  self.x509_certificate = Default::X509_CERTIFICATE
@@ -34,6 +35,7 @@ module SamlIdp
34
35
  self.service_provider.persisted_metadata_getter = ->(id, service_provider) { }
35
36
  self.session_expiry = 0
36
37
  self.attributes = {}
38
+ self.logger = defined?(::Rails) ? Rails.logger : ->(msg) { puts msg }
37
39
  end
38
40
 
39
41
  # formats
@@ -1,4 +1,3 @@
1
- # encoding: utf-8
2
1
  require 'openssl'
3
2
  require 'base64'
4
3
  require 'time'
@@ -36,9 +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 defined?(::Rails)
40
- head :forbidden
41
- end
38
+
39
+ head :forbidden if defined?(::Rails)
42
40
  false
43
41
  end
44
42
 
@@ -60,9 +58,13 @@ module SamlIdp
60
58
  expiry = opts[:expiry] || 60*60
61
59
  session_expiry = opts[:session_expiry]
62
60
  encryption_opts = opts[:encryption] || nil
61
+ name_id_formats_opts = opts[:name_id_formats] || nil
62
+ asserted_attributes_opts = opts[:attributes] || nil
63
63
  signed_message_opts = opts[:signed_message] || false
64
64
  name_id_formats_opts = opts[:name_id_formats] || nil
65
65
  asserted_attributes_opts = opts[:attributes] || nil
66
+ signed_assertion_opts = opts[:signed_assertion] || true
67
+ compress_opts = opts[:compress] || false
66
68
 
67
69
  SamlResponse.new(
68
70
  reference_id,
@@ -77,13 +79,15 @@ module SamlIdp
77
79
  expiry,
78
80
  encryption_opts,
79
81
  session_expiry,
80
- signed_message_opts,
81
82
  name_id_formats_opts,
82
- asserted_attributes_opts
83
+ asserted_attributes_opts,
84
+ signed_assertion_opts,
85
+ signed_message_opts,
86
+ compress_opts
83
87
  ).build
84
88
  end
85
89
 
86
- def encode_logout_response(principal, opts = {})
90
+ def encode_logout_response(_principal, opts = {})
87
91
  SamlIdp::LogoutResponseBuilder.new(
88
92
  get_saml_response_id,
89
93
  (opts[:issuer_uri] || issuer_uri),
@@ -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
 
@@ -23,21 +23,21 @@ module SamlIdp
23
23
  self.raw_algorithm = raw_algorithm
24
24
  end
25
25
 
26
- def encoded(signed_message: false)
27
- @encoded ||= signed_message ? encode_signed_message : encode_raw_message
26
+ def encoded(signed_message: false, compress: false)
27
+ @encoded ||= signed_message ? encode_signed_message(compress) : encode_raw_message(compress)
28
28
  end
29
29
 
30
30
  def raw
31
31
  build
32
32
  end
33
33
 
34
- def encode_raw_message
35
- Base64.strict_encode64(raw)
34
+ def encode_raw_message(compress)
35
+ Base64.strict_encode64(compress ? deflate(raw) : raw)
36
36
  end
37
37
  private :encode_raw_message
38
38
 
39
- def encode_signed_message
40
- Base64.strict_encode64(signed)
39
+ def encode_signed_message(compress)
40
+ Base64.strict_encode64(compress ? deflate(signed) : signed)
41
41
  end
42
42
  private :encode_signed_message
43
43
 
@@ -66,11 +66,17 @@ module SamlIdp
66
66
  def response_id_string
67
67
  "_#{response_id}"
68
68
  end
69
+ alias_method :reference_id, :response_id
69
70
  private :response_id_string
70
71
 
71
72
  def now_iso
72
73
  Time.now.utc.iso8601
73
74
  end
74
75
  private :now_iso
76
+
77
+ def deflate(inflated)
78
+ Zlib::Deflate.deflate(inflated, 9)[2..-5]
79
+ end
80
+ private :deflate
75
81
  end
76
82
  end
@@ -1,8 +1,9 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'saml_idp/assertion_builder'
2
4
  require 'saml_idp/response_builder'
3
5
  module SamlIdp
4
6
  class SamlResponse
5
- attr_accessor :assertion_with_signature
6
7
  attr_accessor :reference_id
7
8
  attr_accessor :response_id
8
9
  attr_accessor :issuer_uri
@@ -17,27 +18,32 @@ module SamlIdp
17
18
  attr_accessor :expiry
18
19
  attr_accessor :encryption_opts
19
20
  attr_accessor :session_expiry
20
- attr_accessor :signed_message_opts
21
21
  attr_accessor :name_id_formats_opts
22
22
  attr_accessor :asserted_attributes_opts
23
+ attr_accessor :signed_message_opts
24
+ attr_accessor :signed_assertion_opts
25
+ attr_accessor :compression_opts
23
26
 
24
27
  def initialize(
25
- reference_id,
26
- response_id,
27
- issuer_uri,
28
- principal,
29
- audience_uri,
30
- saml_request_id,
31
- saml_acs_url,
32
- algorithm,
33
- authn_context_classref,
34
- expiry=60*60,
35
- encryption_opts=nil,
36
- session_expiry=0,
37
- signed_message_opts=false,
38
- name_id_formats_opts = nil,
39
- asserted_attributes_opts = nil
28
+ reference_id,
29
+ response_id,
30
+ issuer_uri,
31
+ principal,
32
+ audience_uri,
33
+ saml_request_id,
34
+ saml_acs_url,
35
+ algorithm,
36
+ authn_context_classref,
37
+ expiry = 60 * 60,
38
+ encryption_opts = nil,
39
+ session_expiry = 0,
40
+ name_id_formats_opts = nil,
41
+ asserted_attributes_opts = nil,
42
+ signed_message_opts = false,
43
+ signed_assertion_opts = true,
44
+ compression_opts = false
40
45
  )
46
+
41
47
  self.reference_id = reference_id
42
48
  self.response_id = response_id
43
49
  self.issuer_uri = issuer_uri
@@ -55,26 +61,32 @@ module SamlIdp
55
61
  self.signed_message_opts = signed_message_opts
56
62
  self.name_id_formats_opts = name_id_formats_opts
57
63
  self.asserted_attributes_opts = asserted_attributes_opts
64
+ self.signed_assertion_opts = signed_assertion_opts
65
+ self.name_id_formats_opts = name_id_formats_opts
66
+ self.asserted_attributes_opts = asserted_attributes_opts
67
+ self.compression_opts = compression_opts
58
68
  end
59
69
 
60
70
  def build
61
- @built ||= encoded_message
71
+ @build ||= encoded_message
62
72
  end
63
73
 
64
74
  def signed_assertion
65
75
  if encryption_opts
66
76
  assertion_builder.encrypt(sign: true)
67
- else
77
+ elsif signed_assertion_opts
68
78
  assertion_builder.signed
79
+ else
80
+ assertion_builder.raw
69
81
  end
70
82
  end
71
83
  private :signed_assertion
72
84
 
73
85
  def encoded_message
74
86
  if signed_message_opts
75
- response_builder.encoded(signed_message: true)
87
+ response_builder.encoded(signed_message: true, compress: compression_opts)
76
88
  else
77
- response_builder.encoded(signed_message: false)
89
+ response_builder.encoded(signed_message: false, compress: compression_opts)
78
90
  end
79
91
  end
80
92
  private :encoded_message
@@ -85,19 +97,20 @@ module SamlIdp
85
97
  private :response_builder
86
98
 
87
99
  def assertion_builder
88
- @assertion_builder ||= AssertionBuilder.new reference_id,
89
- issuer_uri,
90
- principal,
91
- audience_uri,
92
- saml_request_id,
93
- saml_acs_url,
94
- algorithm,
95
- authn_context_classref,
96
- expiry,
97
- encryption_opts,
98
- session_expiry,
99
- name_id_formats_opts,
100
- asserted_attributes_opts
100
+ @assertion_builder ||=
101
+ AssertionBuilder.new SecureRandom.uuid,
102
+ issuer_uri,
103
+ principal,
104
+ audience_uri,
105
+ saml_request_id,
106
+ saml_acs_url,
107
+ algorithm,
108
+ authn_context_classref,
109
+ expiry,
110
+ encryption_opts,
111
+ session_expiry,
112
+ name_id_formats_opts,
113
+ asserted_attributes_opts
101
114
  end
102
115
  private :assertion_builder
103
116
  end
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module SamlIdp
3
- VERSION = '0.14.0'
3
+ VERSION = '0.15.0'
4
4
  end
data/saml_idp.gemspec CHANGED
@@ -1,62 +1,62 @@
1
1
  # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "saml_idp/version"
2
+
3
+ $LOAD_PATH.push File.expand_path('lib', __dir__)
4
+ require 'saml_idp/version'
4
5
 
5
6
  Gem::Specification.new do |s|
6
7
  s.name = %q{saml_idp}
7
8
  s.version = SamlIdp::VERSION
8
9
  s.platform = Gem::Platform::RUBY
9
- s.authors = ["Jon Phenow"]
10
+ s.authors = ['Jon Phenow']
10
11
  s.email = 'jon.phenow@sportngin.com'
11
12
  s.homepage = 'https://github.com/saml-idp/saml_idp'
12
13
  s.summary = 'SAML Indentity Provider for Ruby'
13
14
  s.description = 'SAML IdP (Identity Provider) Library for Ruby'
14
- s.date = Time.now.utc.strftime("%Y-%m-%d")
15
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
15
16
  s.files = Dir['lib/**/*', 'LICENSE', 'README.md', 'Gemfile', 'saml_idp.gemspec']
16
17
  s.required_ruby_version = '>= 2.5'
17
18
  s.license = 'MIT'
18
19
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
20
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
- s.require_paths = ["lib"]
21
+ s.require_paths = ['lib']
21
22
  s.rdoc_options = ['--charset=UTF-8']
22
23
  s.metadata = {
23
- 'homepage_uri' => 'https://github.com/saml-idp/saml_idp',
24
- 'source_code_uri' => 'https://github.com/saml-idp/saml_idp',
25
- 'bug_tracker_uri' => 'https://github.com/saml-idp/saml_idp/issues',
24
+ 'homepage_uri' => 'https://github.com/saml-idp/saml_idp',
25
+ 'source_code_uri' => 'https://github.com/saml-idp/saml_idp',
26
+ 'bug_tracker_uri' => 'https://github.com/saml-idp/saml_idp/issues',
26
27
  'documentation_uri' => "http://rdoc.info/gems/saml_idp/#{SamlIdp::VERSION}"
27
28
  }
28
29
 
29
30
  s.post_install_message = <<-INST
30
- If you're just recently updating saml_idp - please be aware we've changed the default
31
- certificate. See the PR and a description of why we've done this here:
32
- https://github.com/saml-idp/saml_idp/pull/29
33
-
34
- If you just need to see the certificate `bundle open saml_idp` and go to
35
- `lib/saml_idp/default.rb`
31
+ If you're just recently updating saml_idp - please be aware we've changed the default
32
+ certificate. See the PR and a description of why we've done this here:
33
+ https://github.com/saml-idp/saml_idp/pull/29
36
34
 
37
- Similarly, please see the README about certificates - you should avoid using the
38
- defaults in a Production environment. Post any issues you to github.
35
+ If you just need to see the certificate `bundle open saml_idp` and go to
36
+ `lib/saml_idp/default.rb`
39
37
 
40
- ** New in Version 0.3.0 **
38
+ Similarly, please see the README about certificates - you should avoid using the
39
+ defaults in a Production environment. Post any issues you to github.
41
40
 
42
- Encrypted Assertions require the xmlenc gem. See the example in the Controller
43
- section of the README.
41
+ ** New in Version 0.3.0 **
42
+ Encrypted Assertions require the xmlenc gem. See the example in the Controller
43
+ section of the README.
44
44
  INST
45
45
 
46
46
  s.add_dependency('activesupport', '>= 5.2')
47
47
  s.add_dependency('builder', '>= 3.0')
48
48
  s.add_dependency('nokogiri', '>= 1.6.2')
49
- s.add_dependency('xmlenc', '>= 0.7.1')
50
49
  s.add_dependency('rexml')
50
+ s.add_dependency('xmlenc', '>= 0.7.1')
51
51
 
52
+ s.add_development_dependency('activeresource', '>= 5.1')
53
+ s.add_development_dependency('appraisal')
54
+ s.add_development_dependency('byebug')
55
+ s.add_development_dependency('capybara', '>= 2.16')
56
+ s.add_development_dependency('rails', '>= 5.2')
52
57
  s.add_development_dependency('rake')
53
- s.add_development_dependency('simplecov')
54
58
  s.add_development_dependency('rspec', '>= 3.7.0')
55
59
  s.add_development_dependency('ruby-saml', '>= 1.7.2')
56
- s.add_development_dependency('rails', '>= 5.2')
57
- s.add_development_dependency('activeresource', '>= 5.1')
58
- s.add_development_dependency('capybara', '>= 2.16')
60
+ s.add_development_dependency('simplecov')
59
61
  s.add_development_dependency('timecop', '>= 0.8')
60
- s.add_development_dependency('appraisal')
61
- s.add_development_dependency('byebug')
62
62
  end
@@ -199,5 +199,75 @@ module SamlIdp
199
199
  expect(builder.session_expiry).to eq(8)
200
200
  end
201
201
  end
202
+
203
+ describe "with name_id_formats_opt" do
204
+ let(:name_id_formats_opt) {
205
+ {
206
+ persistent: -> (principal) {
207
+ principal.unique_identifier
208
+ }
209
+ }
210
+ }
211
+ it "delegates name_id_formats to opts" do
212
+ UserWithUniqueId = Struct.new(:unique_identifier, :email, :asserted_attributes)
213
+ principal = UserWithUniqueId.new('unique_identifier_123456', 'foo@example.com', { emailAddress: { getter: :email } })
214
+ builder = described_class.new(
215
+ reference_id,
216
+ issuer_uri,
217
+ principal,
218
+ audience_uri,
219
+ saml_request_id,
220
+ saml_acs_url,
221
+ algorithm,
222
+ authn_context_classref,
223
+ expiry,
224
+ encryption_opts,
225
+ session_expiry,
226
+ name_id_formats_opt,
227
+ asserted_attributes_opt
228
+ )
229
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
230
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:2.0:nameid-format:persistent\">unique_identifier_123456</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"emailAddress\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"emailAddress\"><AttributeValue>foo@example.com</AttributeValue></Attribute></AttributeStatement></Assertion>")
231
+ end
232
+ end
233
+ end
234
+
235
+ describe "with asserted_attributes_opt" do
236
+ let(:asserted_attributes_opt) {
237
+ {
238
+ 'GivenName' => {
239
+ getter: :first_name
240
+ },
241
+ 'SurName' => {
242
+ getter: -> (principal) {
243
+ principal.last_name
244
+ }
245
+ }
246
+ }
247
+ }
248
+
249
+ it "delegates asserted_attributes to opts" do
250
+ UserWithName = Struct.new(:email, :first_name, :last_name)
251
+ principal = UserWithName.new('foo@example.com', 'George', 'Washington')
252
+ builder = described_class.new(
253
+ reference_id,
254
+ issuer_uri,
255
+ principal,
256
+ audience_uri,
257
+ saml_request_id,
258
+ saml_acs_url,
259
+ algorithm,
260
+ authn_context_classref,
261
+ expiry,
262
+ encryption_opts,
263
+ session_expiry,
264
+ name_id_formats_opt,
265
+ asserted_attributes_opt
266
+ )
267
+ Timecop.travel(Time.zone.local(2010, 6, 1, 13, 0, 0)) do
268
+ expect(builder.raw).to eq("<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2010-06-01T13:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">foo@example.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2010-06-01T13:03:00Z\" Recipient=\"http://saml.acs.url\"></SubjectConfirmationData></SubjectConfirmation></Subject><Conditions NotBefore=\"2010-06-01T12:59:55Z\" NotOnOrAfter=\"2010-06-01T16:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AuthnStatement AuthnInstant=\"2010-06-01T13:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</AuthnContextClassRef></AuthnContext></AuthnStatement><AttributeStatement><Attribute Name=\"GivenName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"GivenName\"><AttributeValue>George</AttributeValue></Attribute><Attribute Name=\"SurName\" NameFormat=\"urn:oasis:names:tc:SAML:2.0:attrname-format:uri\" FriendlyName=\"SurName\"><AttributeValue>Washington</AttributeValue></Attribute></AttributeStatement></Assertion>")
269
+ end
270
+ end
271
+ end
202
272
  end
203
273
  end
@@ -17,6 +17,7 @@ module SamlIdp
17
17
  it { should respond_to :attributes }
18
18
  it { should respond_to :service_provider }
19
19
  it { should respond_to :session_expiry }
20
+ it { should respond_to :logger }
20
21
 
21
22
  it "has a valid x509_certificate" do
22
23
  expect(subject.x509_certificate).to eq(Default::X509_CERTIFICATE)
@@ -1,7 +1,10 @@
1
1
  require 'spec_helper'
2
2
  module SamlIdp
3
3
  describe Request do
4
- let(:raw_authn_request) { "<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>localhost:3000</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>" }
4
+ let(:issuer) { 'localhost:3000' }
5
+ let(:raw_authn_request) do
6
+ "<samlp:AuthnRequest AssertionConsumerServiceURL='http://localhost:3000/saml/consume' Destination='http://localhost:1337/saml/auth' ID='_af43d1a0-e111-0130-661a-3c0754403fdb' IssueInstant='2013-08-06T22:01:35Z' Version='2.0' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'><saml:Issuer xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>#{issuer}</saml:Issuer><samlp:NameIDPolicy AllowCreate='true' Format='urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress' xmlns:samlp='urn:oasis:names:tc:SAML:2.0:protocol'/><samlp:RequestedAuthnContext Comparison='exact'><saml:AuthnContextClassRef xmlns:saml='urn:oasis:names:tc:SAML:2.0:assertion'>urn:oasis:names:tc:SAML:2.0:ac:classes:Password</saml:AuthnContextClassRef></samlp:RequestedAuthnContext></samlp:AuthnRequest>"
7
+ end
5
8
 
6
9
  describe "deflated request" do
7
10
  let(:deflated_request) { Base64.encode64(Zlib::Deflate.deflate(raw_authn_request, 9)[2..-5]) }
@@ -57,16 +60,47 @@ module SamlIdp
57
60
  expect(subject.request['ID']).to eq(subject.request_id)
58
61
  end
59
62
 
60
- it "has a valid authn context" do
61
- expect(subject.requested_authn_context).to eq("urn:oasis:names:tc:SAML:2.0:ac:classes:Password")
63
+ it 'has a valid authn context' do
64
+ expect(subject.requested_authn_context).to eq('urn:oasis:names:tc:SAML:2.0:ac:classes:Password')
62
65
  end
63
66
 
64
- it "does not permit empty issuer" do
65
- raw_req = raw_authn_request.gsub('localhost:3000', '')
66
- authn_request = described_class.new raw_req
67
- expect(authn_request.issuer).to_not eq('')
68
- expect(authn_request.issuer).to be_nil
69
- expect(authn_request.valid?).to eq(false)
67
+ context 'the issuer is empty' do
68
+ let(:issuer) { nil }
69
+ let(:logger) { ->(msg) { puts msg } }
70
+
71
+ before do
72
+ allow(SamlIdp.config).to receive(:logger).and_return(logger)
73
+ end
74
+
75
+ it 'is invalid' do
76
+ expect(subject.issuer).to_not eq('')
77
+ expect(subject.issuer).to be_nil
78
+ expect(subject.valid?).to eq(false)
79
+ end
80
+
81
+ context 'a Ruby Logger is configured' do
82
+ let(:logger) { Logger.new($stdout) }
83
+
84
+ before do
85
+ allow(logger).to receive(:info)
86
+ end
87
+
88
+ it 'logs an error message' do
89
+ expect(subject.valid?).to be false
90
+ expect(logger).to have_received(:info).with('Unable to find service provider for issuer ')
91
+ end
92
+ end
93
+
94
+ context 'a logger lambda is configured' do
95
+ let(:logger) { double }
96
+
97
+ before { allow(logger).to receive(:call) }
98
+
99
+ it 'logs an error message' do
100
+ expect(subject.valid?).to be false
101
+ expect(logger).to have_received(:call).with('Unable to find service provider for issuer ')
102
+ end
103
+ end
70
104
  end
71
105
  end
72
106
 
@@ -26,6 +26,8 @@ module SamlIdp
26
26
  end
27
27
  let(:signed_response_opts) { true }
28
28
  let(:unsigned_response_opts) { false }
29
+ let(:signed_assertion_opts) { true }
30
+ let(:compress_opts) { false }
29
31
  let(:subject_encrypted) { described_class.new(reference_id,
30
32
  response_id,
31
33
  issuer_uri,
@@ -38,7 +40,11 @@ module SamlIdp
38
40
  expiry,
39
41
  encryption_opts,
40
42
  session_expiry,
41
- unsigned_response_opts
43
+ nil,
44
+ nil,
45
+ unsigned_response_opts,
46
+ signed_assertion_opts,
47
+ compress_opts
42
48
  )
43
49
  }
44
50
 
@@ -54,7 +60,11 @@ module SamlIdp
54
60
  expiry,
55
61
  nil,
56
62
  session_expiry,
57
- signed_response_opts
63
+ nil,
64
+ nil,
65
+ signed_response_opts,
66
+ signed_assertion_opts,
67
+ compress_opts
58
68
  )
59
69
  }
60
70
 
@@ -70,15 +80,97 @@ module SamlIdp
70
80
  expect(subject.build).to be_present
71
81
  end
72
82
 
73
- it "builds encrypted" do
74
- expect(subject_encrypted.build).to_not match(audience_uri)
75
- encoded_xml = subject_encrypted.build
76
- resp_settings = saml_settings(saml_acs_url)
77
- resp_settings.private_key = Default::SECRET_KEY
78
- resp_settings.issuer = audience_uri
79
- saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
80
- saml_resp.soft = false
81
- expect(saml_resp.is_valid?).to eq(true)
83
+ context "encrypted" do
84
+ it "builds encrypted" do
85
+ expect(subject_encrypted.build).to_not match(audience_uri)
86
+ encoded_xml = subject_encrypted.build
87
+ resp_settings = saml_settings(saml_acs_url)
88
+ resp_settings.private_key = Default::SECRET_KEY
89
+ resp_settings.issuer = audience_uri
90
+ saml_resp = OneLogin::RubySaml::Response.new(encoded_xml, settings: resp_settings)
91
+ saml_resp.soft = false
92
+ expect(saml_resp.is_valid?).to eq(true)
93
+ end
94
+ end
95
+
96
+ context "signed response" do
97
+ let(:resp_settings) do
98
+ resp_settings = saml_settings(saml_acs_url)
99
+ resp_settings.private_key = Default::SECRET_KEY
100
+ resp_settings.issuer = audience_uri
101
+ resp_settings
102
+ end
103
+
104
+ it "will build signed valid response" do
105
+ expect { subject.build }.not_to raise_error
106
+ signed_encoded_xml = subject.build
107
+ saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
108
+ expect(
109
+ Nokogiri::XML(saml_resp.response).at_xpath(
110
+ "//p:Response//ds:Signature",
111
+ {
112
+ "p" => "urn:oasis:names:tc:SAML:2.0:protocol",
113
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
114
+ }
115
+ )).to be_present
116
+ expect(saml_resp.send(:validate_signature)).to eq(true)
117
+ expect(saml_resp.is_valid?).to eq(true)
118
+ end
119
+
120
+ context "when signed_assertion_opts is true" do
121
+ it "builds a signed assertion" do
122
+ expect { subject.build }.not_to raise_error
123
+ signed_encoded_xml = subject.build
124
+ saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
125
+ expect(
126
+ Nokogiri::XML(saml_resp.response).at_xpath(
127
+ "//p:Response//a:Assertion//ds:Signature",
128
+ {
129
+ "p" => "urn:oasis:names:tc:SAML:2.0:protocol",
130
+ "a" => "urn:oasis:names:tc:SAML:2.0:assertion",
131
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
132
+ }
133
+ )).to be_present
134
+ end
135
+ end
136
+
137
+ context "when signed_assertion_opts is false" do
138
+ let(:signed_assertion_opts) { false }
139
+
140
+ it "builds a raw assertion" do
141
+ expect { subject.build }.not_to raise_error
142
+ signed_encoded_xml = subject.build
143
+ saml_resp = OneLogin::RubySaml::Response.new(signed_encoded_xml, settings: resp_settings)
144
+ expect(
145
+ Nokogiri::XML(saml_resp.response).at_xpath(
146
+ "//p:Response//a:Assertion",
147
+ {
148
+ "p" => "urn:oasis:names:tc:SAML:2.0:protocol",
149
+ "a" => "urn:oasis:names:tc:SAML:2.0:assertion"
150
+ }
151
+ )).to be_present
152
+
153
+ expect(
154
+ Nokogiri::XML(saml_resp.response).at_xpath(
155
+ "//p:Response//Assertion//ds:Signature",
156
+ {
157
+ "p" => "urn:oasis:names:tc:SAML:2.0:protocol",
158
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
159
+ }
160
+ )).to_not be_present
161
+ end
162
+ end
163
+
164
+ context "when compress opts is true" do
165
+ let(:compress_opts) { true }
166
+ it "will build a compressed valid response" do
167
+ expect { subject.build }.not_to raise_error
168
+ compressed_signed_encoded_xml = subject.build
169
+ saml_resp = OneLogin::RubySaml::Response.new(compressed_signed_encoded_xml, settings: resp_settings)
170
+ expect(saml_resp.send(:validate_signature)).to eq(true)
171
+ expect(saml_resp.is_valid?).to eq(true)
172
+ end
173
+ end
82
174
  end
83
175
 
84
176
  it "will build signed valid response" do
@@ -1,48 +1,61 @@
1
1
  class SamlIdpController < ApplicationController
2
- include SamlIdp::Controller
2
+ include SamlIdp::Controller
3
3
 
4
+ if Rails::VERSION::MAJOR >= 4
5
+ before_action :add_view_path, only: [:new, :create, :logout]
4
6
  before_action :validate_saml_request, only: [:new, :create, :logout]
5
-
6
- def new
7
- render template: "saml_idp/idp/new"
7
+ else
8
+ before_filter :add_view_path, only: [:new, :create, :logout]
9
+ before_filter :validate_saml_request, only: [:new, :create, :logout]
10
+ end
11
+
12
+ def new
13
+ render template: "saml_idp/idp/new"
14
+ end
15
+
16
+ def show
17
+ render xml: SamlIdp.metadata.signed
18
+ end
19
+
20
+ def create
21
+ unless params[:email].blank? && params[:password].blank?
22
+ person = idp_authenticate(params[:email], params[:password])
23
+ if person.nil?
24
+ @saml_idp_fail_msg = "Incorrect email or password."
25
+ else
26
+ @saml_response = idp_make_saml_response(person)
27
+ render :template => "saml_idp/idp/saml_post", :layout => false
28
+ return
29
+ end
8
30
  end
31
+ render :template => "saml_idp/idp/new"
32
+ end
9
33
 
10
- def show
11
- render xml: SamlIdp.metadata.signed
12
- end
34
+ def logout
35
+ idp_logout
36
+ @saml_response = idp_make_saml_response(nil)
37
+ render :template => "saml_idp/idp/saml_post", :layout => false
38
+ end
13
39
 
14
- def create
15
- unless params[:email].blank? && params[:password].blank?
16
- person = idp_authenticate(params[:email], params[:password])
17
- if person.nil?
18
- @saml_idp_fail_msg = "Incorrect email or password."
19
- else
20
- @saml_response = idp_make_saml_response(person)
21
- render :template => "saml_idp/idp/saml_post", :layout => false
22
- return
23
- end
24
- end
25
- render :template => "saml_idp/idp/new"
26
- end
40
+ def idp_logout
41
+ raise NotImplementedError
42
+ end
43
+ private :idp_logout
27
44
 
28
- def logout
29
- idp_logout
30
- @saml_response = idp_make_saml_response(nil)
31
- render :template => "saml_idp/idp/saml_post", :layout => false
32
- end
45
+ def idp_authenticate(email, password)
46
+ { :email => email }
47
+ end
48
+ protected :idp_authenticate
33
49
 
34
- def idp_logout
35
- raise NotImplementedError
36
- end
37
- private :idp_logout
50
+ def idp_make_saml_response(person)
51
+ encode_response(person[:email])
52
+ end
53
+ protected :idp_make_saml_response
38
54
 
39
- def idp_authenticate(email, password)
40
- { :email => email }
41
- end
42
- protected :idp_authenticate
55
+ private
56
+
57
+ def add_view_path
58
+ prepend_view_path("app/views")
59
+ end
43
60
 
44
- def idp_make_saml_response(person)
45
- encode_response(person[:email])
46
- end
47
- protected :idp_make_saml_response
48
61
  end
@@ -18,6 +18,7 @@ module RailsApp
18
18
 
19
19
  # Custom directories with classes and modules you want to be autoloadable.
20
20
  # config.autoload_paths += %W(#{config.root}/extras)
21
+ config.autoload_paths += %w[app/controllers]
21
22
 
22
23
  # Only load the plugins named here, in the order given (default is alphabetical).
23
24
  # :all can be used as a placeholder for all plugins not explicitly named.
@@ -3,4 +3,4 @@ require 'rubygems'
3
3
  # Set up gems listed in the Gemfile.
4
4
  ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__)
5
5
 
6
- require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE'])
6
+ require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE'])
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: saml_idp
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.14.0
4
+ version: 0.15.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Phenow
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-07-26 00:00:00.000000000 Z
11
+ date: 2023-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -53,49 +53,49 @@ dependencies:
53
53
  - !ruby/object:Gem::Version
54
54
  version: 1.6.2
55
55
  - !ruby/object:Gem::Dependency
56
- name: xmlenc
56
+ name: rexml
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: 0.7.1
61
+ version: '0'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: 0.7.1
68
+ version: '0'
69
69
  - !ruby/object:Gem::Dependency
70
- name: rexml
70
+ name: xmlenc
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: 0.7.1
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: 0.7.1
83
83
  - !ruby/object:Gem::Dependency
84
- name: rake
84
+ name: activeresource
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
87
  - - ">="
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '5.1'
90
90
  type: :development
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
94
  - - ">="
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '5.1'
97
97
  - !ruby/object:Gem::Dependency
98
- name: simplecov
98
+ name: appraisal
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
@@ -109,33 +109,33 @@ dependencies:
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  - !ruby/object:Gem::Dependency
112
- name: rspec
112
+ name: byebug
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - ">="
116
116
  - !ruby/object:Gem::Version
117
- version: 3.7.0
117
+ version: '0'
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - ">="
123
123
  - !ruby/object:Gem::Version
124
- version: 3.7.0
124
+ version: '0'
125
125
  - !ruby/object:Gem::Dependency
126
- name: ruby-saml
126
+ name: capybara
127
127
  requirement: !ruby/object:Gem::Requirement
128
128
  requirements:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: 1.7.2
131
+ version: '2.16'
132
132
  type: :development
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: 1.7.2
138
+ version: '2.16'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rails
141
141
  requirement: !ruby/object:Gem::Requirement
@@ -151,49 +151,49 @@ dependencies:
151
151
  - !ruby/object:Gem::Version
152
152
  version: '5.2'
153
153
  - !ruby/object:Gem::Dependency
154
- name: activeresource
154
+ name: rake
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: '5.1'
159
+ version: '0'
160
160
  type: :development
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
- version: '5.1'
166
+ version: '0'
167
167
  - !ruby/object:Gem::Dependency
168
- name: capybara
168
+ name: rspec
169
169
  requirement: !ruby/object:Gem::Requirement
170
170
  requirements:
171
171
  - - ">="
172
172
  - !ruby/object:Gem::Version
173
- version: '2.16'
173
+ version: 3.7.0
174
174
  type: :development
175
175
  prerelease: false
176
176
  version_requirements: !ruby/object:Gem::Requirement
177
177
  requirements:
178
178
  - - ">="
179
179
  - !ruby/object:Gem::Version
180
- version: '2.16'
180
+ version: 3.7.0
181
181
  - !ruby/object:Gem::Dependency
182
- name: timecop
182
+ name: ruby-saml
183
183
  requirement: !ruby/object:Gem::Requirement
184
184
  requirements:
185
185
  - - ">="
186
186
  - !ruby/object:Gem::Version
187
- version: '0.8'
187
+ version: 1.7.2
188
188
  type: :development
189
189
  prerelease: false
190
190
  version_requirements: !ruby/object:Gem::Requirement
191
191
  requirements:
192
192
  - - ">="
193
193
  - !ruby/object:Gem::Version
194
- version: '0.8'
194
+ version: 1.7.2
195
195
  - !ruby/object:Gem::Dependency
196
- name: appraisal
196
+ name: simplecov
197
197
  requirement: !ruby/object:Gem::Requirement
198
198
  requirements:
199
199
  - - ">="
@@ -207,19 +207,19 @@ dependencies:
207
207
  - !ruby/object:Gem::Version
208
208
  version: '0'
209
209
  - !ruby/object:Gem::Dependency
210
- name: byebug
210
+ name: timecop
211
211
  requirement: !ruby/object:Gem::Requirement
212
212
  requirements:
213
213
  - - ">="
214
214
  - !ruby/object:Gem::Version
215
- version: '0'
215
+ version: '0.8'
216
216
  type: :development
217
217
  prerelease: false
218
218
  version_requirements: !ruby/object:Gem::Requirement
219
219
  requirements:
220
220
  - - ">="
221
221
  - !ruby/object:Gem::Version
222
- version: '0'
222
+ version: '0.8'
223
223
  description: SAML IdP (Identity Provider) Library for Ruby
224
224
  email: jon.phenow@sportngin.com
225
225
  executables: []
@@ -365,22 +365,21 @@ metadata:
365
365
  homepage_uri: https://github.com/saml-idp/saml_idp
366
366
  source_code_uri: https://github.com/saml-idp/saml_idp
367
367
  bug_tracker_uri: https://github.com/saml-idp/saml_idp/issues
368
- documentation_uri: http://rdoc.info/gems/saml_idp/0.14.0
369
- post_install_message: |
370
- If you're just recently updating saml_idp - please be aware we've changed the default
371
- certificate. See the PR and a description of why we've done this here:
372
- https://github.com/saml-idp/saml_idp/pull/29
373
-
374
- If you just need to see the certificate `bundle open saml_idp` and go to
375
- `lib/saml_idp/default.rb`
368
+ documentation_uri: http://rdoc.info/gems/saml_idp/0.15.0
369
+ post_install_message: |2
370
+ If you're just recently updating saml_idp - please be aware we've changed the default
371
+ certificate. See the PR and a description of why we've done this here:
372
+ https://github.com/saml-idp/saml_idp/pull/29
376
373
 
377
- Similarly, please see the README about certificates - you should avoid using the
378
- defaults in a Production environment. Post any issues you to github.
374
+ If you just need to see the certificate `bundle open saml_idp` and go to
375
+ `lib/saml_idp/default.rb`
379
376
 
380
- ** New in Version 0.3.0 **
377
+ Similarly, please see the README about certificates - you should avoid using the
378
+ defaults in a Production environment. Post any issues you to github.
381
379
 
382
- Encrypted Assertions require the xmlenc gem. See the example in the Controller
383
- section of the README.
380
+ ** New in Version 0.3.0 **
381
+ Encrypted Assertions require the xmlenc gem. See the example in the Controller
382
+ section of the README.
384
383
  rdoc_options:
385
384
  - "--charset=UTF-8"
386
385
  require_paths:
@@ -396,8 +395,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
396
395
  - !ruby/object:Gem::Version
397
396
  version: '0'
398
397
  requirements: []
399
- rubygems_version: 3.1.2
400
- signing_key:
398
+ rubygems_version: 3.3.7
399
+ signing_key:
401
400
  specification_version: 4
402
401
  summary: SAML Indentity Provider for Ruby
403
402
  test_files: