saml_idp 0.7.2 → 0.16.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +5 -5
  2. data/Gemfile +1 -1
  3. data/README.md +59 -52
  4. data/lib/saml_idp/assertion_builder.rb +28 -3
  5. data/lib/saml_idp/configurator.rb +7 -1
  6. data/lib/saml_idp/controller.rb +21 -13
  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 +22 -1
  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 +26 -6
  13. data/lib/saml_idp/response_builder.rb +26 -6
  14. data/lib/saml_idp/saml_response.rb +62 -28
  15. data/lib/saml_idp/service_provider.rb +15 -6
  16. data/lib/saml_idp/signable.rb +1 -2
  17. data/lib/saml_idp/version.rb +1 -1
  18. data/lib/saml_idp/xml_security.rb +1 -1
  19. data/lib/saml_idp.rb +2 -1
  20. data/saml_idp.gemspec +45 -42
  21. data/spec/acceptance/idp_controller_spec.rb +5 -4
  22. data/spec/lib/saml_idp/algorithmable_spec.rb +6 -6
  23. data/spec/lib/saml_idp/assertion_builder_spec.rb +151 -8
  24. data/spec/lib/saml_idp/attribute_decorator_spec.rb +8 -8
  25. data/spec/lib/saml_idp/configurator_spec.rb +9 -7
  26. data/spec/lib/saml_idp/controller_spec.rb +53 -20
  27. data/spec/lib/saml_idp/encryptor_spec.rb +4 -4
  28. data/spec/lib/saml_idp/fingerprint_spec.rb +14 -0
  29. data/spec/lib/saml_idp/incoming_metadata_spec.rb +60 -0
  30. data/spec/lib/saml_idp/metadata_builder_spec.rb +30 -17
  31. data/spec/lib/saml_idp/name_id_formatter_spec.rb +3 -3
  32. data/spec/lib/saml_idp/request_spec.rb +78 -27
  33. data/spec/lib/saml_idp/response_builder_spec.rb +5 -3
  34. data/spec/lib/saml_idp/saml_response_spec.rb +127 -12
  35. data/spec/lib/saml_idp/service_provider_spec.rb +2 -2
  36. data/spec/lib/saml_idp/signable_spec.rb +1 -1
  37. data/spec/lib/saml_idp/signature_builder_spec.rb +2 -2
  38. data/spec/lib/saml_idp/signed_info_builder_spec.rb +3 -3
  39. data/spec/rails_app/app/controllers/saml_controller.rb +1 -1
  40. data/spec/rails_app/app/controllers/saml_idp_controller.rb +55 -3
  41. data/{app → spec/rails_app/app}/views/saml_idp/idp/new.html.erb +1 -5
  42. data/{app → spec/rails_app/app}/views/saml_idp/idp/saml_post.html.erb +1 -1
  43. data/spec/rails_app/config/application.rb +1 -6
  44. data/spec/rails_app/config/boot.rb +1 -1
  45. data/spec/rails_app/config/environments/development.rb +2 -5
  46. data/spec/rails_app/config/environments/production.rb +1 -0
  47. data/spec/rails_app/config/environments/test.rb +1 -0
  48. data/spec/spec_helper.rb +23 -1
  49. data/spec/support/certificates/sp_cert_req.csr +12 -0
  50. data/spec/support/certificates/sp_private_key.pem +16 -0
  51. data/spec/support/certificates/sp_x509_cert.crt +18 -0
  52. data/spec/support/saml_request_macros.rb +66 -4
  53. data/spec/support/security_helpers.rb +10 -0
  54. data/spec/xml_security_spec.rb +12 -12
  55. metadata +135 -81
  56. data/app/controllers/saml_idp/idp_controller.rb +0 -59
  57. data/spec/lib/saml_idp/.assertion_builder_spec.rb.swp +0 -0
@@ -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
@@ -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,20 +18,32 @@ module SamlIdp
17
18
  attr_accessor :expiry
18
19
  attr_accessor :encryption_opts
19
20
  attr_accessor :session_expiry
21
+ attr_accessor :name_id_formats_opts
22
+ attr_accessor :asserted_attributes_opts
23
+ attr_accessor :signed_message_opts
24
+ attr_accessor :signed_assertion_opts
25
+ attr_accessor :compression_opts
26
+
27
+ def initialize(
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
45
+ )
20
46
 
21
- def initialize(reference_id,
22
- response_id,
23
- issuer_uri,
24
- principal,
25
- audience_uri,
26
- saml_request_id,
27
- saml_acs_url,
28
- algorithm,
29
- authn_context_classref,
30
- expiry=60*60,
31
- encryption_opts=nil,
32
- session_expiry=0
33
- )
34
47
  self.reference_id = reference_id
35
48
  self.response_id = response_id
36
49
  self.issuer_uri = issuer_uri
@@ -45,38 +58,59 @@ module SamlIdp
45
58
  self.expiry = expiry
46
59
  self.encryption_opts = encryption_opts
47
60
  self.session_expiry = session_expiry
61
+ self.signed_message_opts = signed_message_opts
62
+ self.name_id_formats_opts = name_id_formats_opts
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
48
68
  end
49
69
 
50
70
  def build
51
- @built ||= response_builder.encoded
71
+ @build ||= encoded_message
52
72
  end
53
73
 
54
74
  def signed_assertion
55
75
  if encryption_opts
56
76
  assertion_builder.encrypt(sign: true)
57
- else
77
+ elsif signed_assertion_opts
58
78
  assertion_builder.signed
79
+ else
80
+ assertion_builder.raw
59
81
  end
60
82
  end
61
83
  private :signed_assertion
62
84
 
85
+ def encoded_message
86
+ if signed_message_opts
87
+ response_builder.encoded(signed_message: true, compress: compression_opts)
88
+ else
89
+ response_builder.encoded(signed_message: false, compress: compression_opts)
90
+ end
91
+ end
92
+ private :encoded_message
93
+
63
94
  def response_builder
64
- ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
95
+ ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion, algorithm)
65
96
  end
66
97
  private :response_builder
67
98
 
68
99
  def assertion_builder
69
- @assertion_builder ||= AssertionBuilder.new reference_id,
70
- issuer_uri,
71
- principal,
72
- audience_uri,
73
- saml_request_id,
74
- saml_acs_url,
75
- algorithm,
76
- authn_context_classref,
77
- expiry,
78
- encryption_opts,
79
- session_expiry
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
80
114
  end
81
115
  private :assertion_builder
82
116
  end
@@ -13,6 +13,7 @@ module SamlIdp
13
13
  attribute :validate_signature
14
14
  attribute :acs_url
15
15
  attribute :assertion_consumer_logout_service_url
16
+ attribute :response_hosts
16
17
 
17
18
  delegate :config, to: :SamlIdp
18
19
 
@@ -21,18 +22,13 @@ module SamlIdp
21
22
  end
22
23
 
23
24
  def valid_signature?(doc, require_signature = false)
24
- if require_signature || should_validate_signature?
25
+ if require_signature || attributes[:validate_signature]
25
26
  doc.valid_signature?(fingerprint)
26
27
  else
27
28
  true
28
29
  end
29
30
  end
30
31
 
31
- def should_validate_signature?
32
- attributes[:validate_signature] ||
33
- current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?
34
- end
35
-
36
32
  def refresh_metadata
37
33
  fresh = fresh_incoming_metadata
38
34
  if valid_signature?(fresh.document)
@@ -46,6 +42,19 @@ module SamlIdp
46
42
  @current_metadata ||= get_current_or_build
47
43
  end
48
44
 
45
+ def acceptable_response_hosts
46
+ hosts = Array(self.response_hosts)
47
+ hosts.push(metadata_url_host) if metadata_url_host
48
+
49
+ hosts
50
+ end
51
+
52
+ def metadata_url_host
53
+ if metadata_url.present?
54
+ URI(metadata_url).host
55
+ end
56
+ end
57
+
49
58
  def get_current_or_build
50
59
  persisted = metadata_getter[identifier, self]
51
60
  if persisted.is_a? Hash
@@ -108,8 +108,7 @@ module SamlIdp
108
108
  canon_algorithm = Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0
109
109
  canon_hashed_element = noko_raw.canonicalize(canon_algorithm, inclusive_namespaces)
110
110
  digest_algorithm = get_algorithm
111
-
112
- hash = digest_algorithm.digest(canon_hashed_element)
111
+ hash = digest_algorithm.digest(canon_hashed_element)
113
112
  Base64.strict_encode64(hash).gsub(/\n/, '')
114
113
  end
115
114
  private :digest
@@ -1,4 +1,4 @@
1
1
  # encoding: utf-8
2
2
  module SamlIdp
3
- VERSION = '0.7.2'
3
+ VERSION = '0.16.0'
4
4
  end
@@ -108,7 +108,7 @@ module SamlIdp
108
108
  canon_algorithm = canon_algorithm REXML::XPath.first(ref, '//ds:CanonicalizationMethod', 'ds' => DSIG)
109
109
  canon_hashed_element = hashed_element.canonicalize(canon_algorithm, inclusive_namespaces)
110
110
 
111
- digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod"))
111
+ digest_algorithm = algorithm(REXML::XPath.first(ref, "//ds:DigestMethod", {'ds' => DSIG}))
112
112
 
113
113
  hash = digest_algorithm.digest(canon_hashed_element)
114
114
  digest_value = Base64.decode64(REXML::XPath.first(ref, "//ds:DigestValue", {"ds"=>DSIG}).text)
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
data/saml_idp.gemspec CHANGED
@@ -1,59 +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.email = %q{jon.phenow@sportngin.com}
11
- s.homepage = %q{http://github.com/sportngin/saml_idp}
12
- s.summary = %q{SAML Indentity Provider in ruby}
13
- s.description = %q{SAML IdP (Identity Provider) library in ruby}
14
- s.date = Time.now.utc.strftime("%Y-%m-%d")
15
- s.files = Dir.glob("app/**/*") + Dir.glob("lib/**/*") + [
16
- "LICENSE",
17
- "README.md",
18
- "Gemfile",
19
- "saml_idp.gemspec"
20
- ]
21
- s.required_ruby_version = '>= 2.2'
22
- s.license = "LICENSE"
10
+ s.authors = ['Jon Phenow']
11
+ s.email = 'jon.phenow@sportngin.com'
12
+ s.homepage = 'https://github.com/saml-idp/saml_idp'
13
+ s.summary = 'SAML Indentity Provider for Ruby'
14
+ s.description = 'SAML IdP (Identity Provider) Library for Ruby'
15
+ s.date = Time.now.utc.strftime('%Y-%m-%d')
16
+ s.files = Dir['lib/**/*', 'LICENSE', 'README.md', 'Gemfile', 'saml_idp.gemspec']
17
+ s.required_ruby_version = '>= 2.5'
18
+ s.license = 'MIT'
23
19
  s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
24
20
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
25
- s.require_paths = ["lib"]
26
- s.rdoc_options = ["--charset=UTF-8"]
21
+ s.require_paths = ['lib']
22
+ s.rdoc_options = ['--charset=UTF-8']
23
+ s.metadata = {
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',
27
+ 'documentation_uri' => "http://rdoc.info/gems/saml_idp/#{SamlIdp::VERSION}"
28
+ }
27
29
 
28
30
  s.post_install_message = <<-INST
29
- If you're just recently updating saml_idp - please be aware we've changed the default
30
- certificate. See the PR and a description of why we've done this here:
31
- https://github.com/sportngin/saml_idp/pull/29
32
-
33
- If you just need to see the certificate `bundle open saml_idp` and go to
34
- `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
35
34
 
36
- Similarly, please see the README about certificates - you should avoid using the
37
- 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`
38
37
 
39
- ** 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.
40
40
 
41
- Encrypted Assertions require the xmlenc gem. See the example in the Controller
42
- 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.
43
44
  INST
44
45
 
45
- s.add_dependency('activesupport', '>= 3.2')
46
- s.add_dependency('uuid', '~> 2.3')
47
- s.add_dependency('builder', '~> 3.0')
46
+ s.add_dependency('activesupport', '>= 5.2')
47
+ s.add_dependency('builder', '>= 3.0')
48
48
  s.add_dependency('nokogiri', '>= 1.6.2')
49
-
50
- s.add_development_dependency('rake', '~> 10.4.2')
51
- s.add_development_dependency('simplecov', '~> 0.12')
52
- s.add_development_dependency('rspec', '~> 2.5')
53
- s.add_development_dependency('ruby-saml', '~> 1.3')
54
- s.add_development_dependency('rails', '~> 3.2')
55
- s.add_development_dependency('capybara', '~> 2.11.0')
56
- s.add_development_dependency('timecop', '~> 0.8')
57
- s.add_development_dependency('xmlenc', '>= 0.6.4')
49
+ s.add_dependency('rexml')
50
+ s.add_dependency('xmlenc', '>= 0.7.1')
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')
57
+ s.add_development_dependency('rake')
58
+ s.add_development_dependency('rspec', '>= 3.7.0')
59
+ s.add_development_dependency('ruby-saml', '>= 1.7.2')
60
+ s.add_development_dependency('simplecov')
61
+ s.add_development_dependency('timecop', '>= 0.8')
58
62
  end
59
-
@@ -4,11 +4,12 @@ feature 'IdpController' do
4
4
  scenario 'Login via default signup page' do
5
5
  saml_request = make_saml_request("http://foo.example.com/saml/consume")
6
6
  visit "/saml/auth?SAMLRequest=#{CGI.escape(saml_request)}"
7
- fill_in 'Email', :with => "foo@example.com"
8
- fill_in 'Password', :with => "okidoki"
7
+ expect(status_code).to eq(200)
8
+ fill_in 'email', :with => "foo@example.com"
9
+ fill_in 'password', :with => "okidoki"
9
10
  click_button 'Sign in'
10
11
  click_button 'Submit' # simulating onload
11
- current_url.should == 'http://foo.example.com/saml/consume'
12
- page.should have_content "foo@example.com"
12
+ expect(current_url).to eq('http://foo.example.com/saml/consume')
13
+ expect(page).to have_content "foo@example.com"
13
14
  end
14
15
  end
@@ -9,11 +9,11 @@ module SamlIdp
9
9
  end
10
10
 
11
11
  it "finds algorithm class" do
12
- algorithm.should == OpenSSL::Digest::SHA256
12
+ expect(algorithm).to eq(OpenSSL::Digest::SHA256)
13
13
  end
14
14
 
15
15
  it "finds the name" do
16
- algorithm_name.should == "sha256"
16
+ expect(algorithm_name).to eq("sha256")
17
17
  end
18
18
  end
19
19
 
@@ -23,11 +23,11 @@ module SamlIdp
23
23
  end
24
24
 
25
25
  it "finds algorithm class" do
26
- algorithm.should == OpenSSL::Digest::SHA512
26
+ expect(algorithm).to eq(OpenSSL::Digest::SHA512)
27
27
  end
28
28
 
29
29
  it "finds the name" do
30
- algorithm_name.should == "sha512"
30
+ expect(algorithm_name).to eq("sha512")
31
31
  end
32
32
  end
33
33
 
@@ -37,11 +37,11 @@ module SamlIdp
37
37
  end
38
38
 
39
39
  it "finds algorithm class" do
40
- algorithm.should == OpenSSL::Digest::SHA1
40
+ expect(algorithm).to eq(OpenSSL::Digest::SHA1)
41
41
  end
42
42
 
43
43
  it "finds the name" do
44
- algorithm_name.should == "sha1"
44
+ expect(algorithm_name).to eq("sha1")
45
45
  end
46
46
  end
47
47
  end