saml_idp 0.8.0 → 0.12.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 33008cf3468ff0038f5308fb5820edc2023c071d1d0a046c6778f9fd393d96da
4
- data.tar.gz: 95e3adfcc852d0384ae3fa5c8740a820f16db073f83aef78bdba524d3581f8ed
3
+ metadata.gz: 73712903d3949f895e57a13b138c007e0ae74715d7d546b4415f278829e59054
4
+ data.tar.gz: fbab7e28d01ea3fc7624e52e20a234f6f997a51643905170e6809cb3f7beeec7
5
5
  SHA512:
6
- metadata.gz: e66b93acbc0ab6b965258a6ba2c205e4563e197206b94c50dbd5a7603a36ebda7c2dc47d57932b821dcbd4a4e3e033616d2c6879141222b0496a0faa1811af6f
7
- data.tar.gz: d3aee5f5466e2b7c70cb9434eb6c0fe3036d55335ec65555c489124ce4d4889e4051969d8f1cc87e2b3a523f1fbbc447a8a609a86e62a4ffd53f0cf4a7f31285
6
+ metadata.gz: 80a4683963e04b8b7f68051d15b12a5f0a098300cbfba9b72c4ed3940338ab3505c47cda0b091a9a36c1605f4396b5f1732bed7d3356438dd944f83541573a47
7
+ data.tar.gz: b01be29f645e31f9987afef74bb8b368ccfc86340b67e29524f582fe139e99b56cd827315d5a4e73d34b69e2c3c5b89d881c89f6d29b68c4b3cd2ff5499a39c3
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # Ruby SAML Identity Provider (IdP)
2
+
2
3
  Forked from https://github.com/lawrencepit/ruby-saml-idp
3
4
 
4
5
  [![Build Status](https://travis-ci.org/saml-idp/saml_idp.svg)](https://travis-ci.org/saml-idp/saml_idp)
@@ -19,6 +20,7 @@ Add this to your Gemfile:
19
20
  gem 'saml_idp'
20
21
 
21
22
  ## Not using rails?
23
+
22
24
  Include `SamlIdp::Controller` and see the examples that use rails. It should be straightforward for you.
23
25
 
24
26
  Basically you call `decode_request(params[:SAMLRequest])` on an incoming request and then use the value
@@ -30,9 +32,10 @@ posting to `saml_acs_url` the parameter `SAMLResponse` with the return value fro
30
32
  `encode_response(user_email)`.
31
33
 
32
34
  ## Using rails?
35
+
33
36
  Add to your `routes.rb` file, for example:
34
37
 
35
- ``` ruby
38
+ ```ruby
36
39
  get '/saml/auth' => 'saml_idp#new'
37
40
  get '/saml/metadata' => 'saml_idp#show'
38
41
  post '/saml/auth' => 'saml_idp#create'
@@ -41,7 +44,7 @@ match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]
41
44
 
42
45
  Create a controller that looks like this, customize to your own situation:
43
46
 
44
- ``` ruby
47
+ ```ruby
45
48
  class SamlIdpController < SamlIdp::IdpController
46
49
  def idp_authenticate(email, password) # not using params intentionally
47
50
  user = User.by_email(email).first
@@ -69,6 +72,22 @@ end
69
72
 
70
73
  ## Configuration
71
74
 
75
+ #### Signed assertions and Signed Response
76
+
77
+ 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.
78
+
79
+ Signing SAML Response is optional, but some security perspective SP services might require Response message itself must be signed.
80
+ 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)
81
+
82
+ #### Signing algorithm
83
+
84
+ Following algorithms you can set in your response signing algorithm
85
+ :sha1 - RSA-SHA1 default value but not recommended to production environment
86
+ Highly recommended to use one of following algorithm, suit with your computing power.
87
+ :sha256 - RSA-SHA256
88
+ :sha384 - RSA-SHA384
89
+ :sha512 - RSA-SHA512
90
+
72
91
  Be sure to load a file like this during your app initialization:
73
92
 
74
93
  ```ruby
@@ -88,20 +107,21 @@ KEY DATA
88
107
  CERT
89
108
 
90
109
  # config.password = "secret_key_password"
91
- # config.algorithm = :sha256
110
+ # config.algorithm = :sha256 # Default: sha1 only for development.
92
111
  # config.organization_name = "Your Organization"
93
112
  # config.organization_url = "http://example.com"
94
113
  # config.base_saml_location = "#{base}/saml"
95
- # config.reference_id_generator # Default: -> { UUID.generate }
114
+ # config.reference_id_generator # Default: -> { SecureRandom.uuid }
96
115
  # config.single_logout_service_post_location = "#{base}/saml/logout"
97
116
  # config.single_logout_service_redirect_location = "#{base}/saml/logout"
98
117
  # config.attribute_service_location = "#{base}/saml/attributes"
99
118
  # config.single_service_post_location = "#{base}/saml/auth"
100
119
  # config.session_expiry = 86400 # Default: 0 which means never
120
+ # config.signed_message = true # Default: false which means unsigned SAML Response
101
121
 
102
122
  # Principal (e.g. User) is passed in when you `encode_response`
103
123
  #
104
- # config.name_id.formats # =>
124
+ # config.name_id.formats =
105
125
  # { # All 2.0
106
126
  # email_address: -> (principal) { principal.email_address },
107
127
  # transient: -> (principal) { principal.id },
@@ -211,6 +231,7 @@ end
211
231
  ```
212
232
 
213
233
  # Keys and Secrets
234
+
214
235
  To generate the SAML Response it uses a default X.509 certificate and secret key... which isn't so secret.
215
236
  You can find them in `SamlIdp::Default`. The X.509 certificate is valid until year 2032.
216
237
  Obviously you shouldn't use these if you intend to use this in production environments. In that case,
@@ -224,18 +245,28 @@ The fingerprint to use, if you use the default X.509 certificate of this gem, is
224
245
  9E:65:2E:03:06:8D:80:F2:86:C7:6C:77:A1:D9:14:97:0A:4D:F4:4D
225
246
  ```
226
247
 
248
+ # Fingerprint
249
+
250
+ The gem provides an helper to generate a fingerprint for a X.509 certificate.
251
+ The second parameter is optional and default to your configuration `SamlIdp.config.algorithm`
252
+
253
+ ```ruby
254
+ Fingerprint.certificate_digest(x509_cert, :sha512)
255
+ ```
227
256
 
228
257
  # Service Providers
258
+
229
259
  To act as a Service Provider which generates SAML Requests and can react to SAML Responses use the
230
260
  excellent [ruby-saml](https://github.com/onelogin/ruby-saml) gem.
231
261
 
232
-
233
262
  # Author
234
- Jon Phenow, me@jphenow.com
263
+
264
+ Jon Phenow, jon@jphenow.com, jphenow.com, @jphenow
235
265
 
236
266
  Lawrence Pit, lawrence.pit@gmail.com, lawrencepit.com, @lawrencepit
237
267
 
238
268
  # Copyright
269
+
239
270
  Copyright (c) 2012 Sport Ngin.
240
271
  Portions Copyright (c) 2010 OneLogin, LLC
241
272
  Portions Copyright (c) 2012 Lawrence Pit (http://lawrencepit.com)
@@ -8,6 +8,7 @@ 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/fingerprint'
11
12
  require 'saml_idp/engine' if defined?(::Rails) && Rails::VERSION::MAJOR > 2
12
13
 
13
14
  def self.config
@@ -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
@@ -36,10 +36,12 @@ module SamlIdp
36
36
  def validate_saml_request(raw_saml_request = params[:SAMLRequest])
37
37
  decode_request(raw_saml_request)
38
38
  return true if valid_saml_request?
39
- if Rails::VERSION::MAJOR >= 4
40
- head :forbidden
41
- else
42
- render nothing: true, status: :forbidden
39
+ if defined?(::Rails)
40
+ if Rails::VERSION::MAJOR >= 4
41
+ head :forbidden
42
+ else
43
+ render nothing: true, status: :forbidden
44
+ end
43
45
  end
44
46
  false
45
47
  end
@@ -62,6 +64,7 @@ module SamlIdp
62
64
  expiry = opts[:expiry] || 60*60
63
65
  session_expiry = opts[:session_expiry]
64
66
  encryption_opts = opts[:encryption] || nil
67
+ signed_message_opts = opts[:signed_message] || false
65
68
 
66
69
  SamlResponse.new(
67
70
  reference_id,
@@ -75,7 +78,8 @@ module SamlIdp
75
78
  my_authn_context_classref,
76
79
  expiry,
77
80
  encryption_opts,
78
- session_expiry
81
+ session_expiry,
82
+ signed_message_opts
79
83
  ).build
80
84
  end
81
85
 
@@ -122,11 +126,11 @@ module SamlIdp
122
126
  end
123
127
 
124
128
  def get_saml_response_id
125
- UUID.generate
129
+ SecureRandom.uuid
126
130
  end
127
131
 
128
132
  def get_saml_reference_id
129
- UUID.generate
133
+ SecureRandom.uuid
130
134
  end
131
135
 
132
136
  def default_algorithm
@@ -0,0 +1,19 @@
1
+ module SamlIdp
2
+ module Fingerprint
3
+ def self.certificate_digest(cert, sha_size = nil)
4
+ sha_size ||= SamlIdp.config.algorithm
5
+ digest_sha_class(sha_size).hexdigest(OpenSSL::X509::Certificate.new(cert).to_der).scan(/../).join(':')
6
+ end
7
+
8
+ def self.digest_sha_class(sha_size)
9
+ case sha_size
10
+ when :sha256
11
+ Digest::SHA256
12
+ when :sha512
13
+ Digest::SHA512
14
+ else
15
+ raise ArgumentError, "Unsupported sha size parameter: #{sha_size}"
16
+ end
17
+ end
18
+ end
19
+ end
@@ -16,6 +16,11 @@ module SamlIdp
16
16
  @document ||= Saml::XML::Document.parse raw
17
17
  end
18
18
 
19
+ def entity_id
20
+ xpath('//md:EntityDescriptor/@entityID', md: metadata_namespace).first.try(:content).to_s
21
+ end
22
+ hashable :entity_id
23
+
19
24
  def sign_assertions
20
25
  doc = xpath(
21
26
  "//md:SPSSODescriptor",
@@ -29,6 +34,19 @@ module SamlIdp
29
34
  end
30
35
  hashable :sign_assertions
31
36
 
37
+ def sign_authn_request
38
+ doc = xpath(
39
+ "//md:SPSSODescriptor",
40
+ ds: signature_namespace,
41
+ md: metadata_namespace
42
+ ).first
43
+ if (doc && !doc['AuthnRequestsSigned'].nil?)
44
+ return doc['AuthnRequestsSigned'].strip.downcase == 'true'
45
+ end
46
+ return false
47
+ end
48
+ hashable :sign_authn_request
49
+
32
50
  def display_name
33
51
  role_descriptor_document.present? ? role_descriptor_document["ServiceDisplayName"] : ""
34
52
  end
@@ -24,13 +24,15 @@ module SamlIdp
24
24
 
25
25
  entity.IDPSSODescriptor protocolSupportEnumeration: protocol_enumeration do |descriptor|
26
26
  build_key_descriptor descriptor
27
- descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
28
- Location: single_logout_service_post_location
29
- descriptor.SingleLogoutService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
30
- Location: single_logout_service_redirect_location
27
+ build_endpoint descriptor, [
28
+ { tag: 'SingleLogoutService', url: single_logout_service_post_location, bind: 'HTTP-POST' },
29
+ { tag: 'SingleLogoutService', url: single_logout_service_redirect_location, bind: 'HTTP-Redirect'}
30
+ ]
31
31
  build_name_id_formats descriptor
32
- descriptor.SingleSignOnService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
33
- Location: single_service_post_location
32
+ build_endpoint descriptor, [
33
+ { tag: 'SingleSignOnService', url: single_service_post_location, bind: 'HTTP-POST' },
34
+ { tag: 'SingleSignOnService', url: single_service_redirect_location, bind: 'HTTP-Redirect'}
35
+ ]
34
36
  build_attribute descriptor
35
37
  end
36
38
 
@@ -38,8 +40,9 @@ module SamlIdp
38
40
  build_key_descriptor authority_descriptor
39
41
  build_organization authority_descriptor
40
42
  build_contact authority_descriptor
41
- authority_descriptor.AttributeService Binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect",
42
- Location: attribute_service_location
43
+ build_endpoint authority_descriptor, [
44
+ { tag: 'AttributeService', url: attribute_service_location, bind: 'HTTP-Redirect' }
45
+ ]
43
46
  build_name_id_formats authority_descriptor
44
47
  build_attribute authority_descriptor
45
48
  end
@@ -69,6 +72,17 @@ module SamlIdp
69
72
  end
70
73
  private :build_name_id_formats
71
74
 
75
+ def build_endpoint(el, end_points)
76
+ end_points.each do |ep|
77
+ next unless ep[:url].present?
78
+
79
+ el.tag! ep[:tag],
80
+ Binding: "urn:oasis:names:tc:SAML:2.0:bindings:#{ep[:bind]}",
81
+ Location: ep[:url]
82
+ end
83
+ end
84
+ private :build_endpoint
85
+
72
86
  def build_attribute(el)
73
87
  attributes.each do |attribute|
74
88
  el.tag! "saml:Attribute",
@@ -151,6 +165,7 @@ module SamlIdp
151
165
  organization_url
152
166
  attribute_service_location
153
167
  single_service_post_location
168
+ single_service_redirect_location
154
169
  single_logout_service_post_location
155
170
  single_logout_service_redirect_location
156
171
  technical_contact
@@ -6,5 +6,9 @@ module SamlIdp
6
6
  def sign_assertions?
7
7
  !!attributes[:sign_assertions]
8
8
  end
9
+
10
+ def sign_authn_request?
11
+ !!attributes[:sign_authn_request]
12
+ end
9
13
  end
10
14
  end
@@ -106,6 +106,7 @@ module SamlIdp
106
106
  end
107
107
 
108
108
  if !service_provider.acceptable_response_hosts.include?(response_host)
109
+ log "#{service_provider.acceptable_response_hosts} compare to #{response_host}"
109
110
  log "No acceptable AssertionConsumerServiceURL, either configure them via config.service_provider.response_hosts or match to your metadata_url host"
110
111
  return false
111
112
  end
@@ -114,9 +115,14 @@ module SamlIdp
114
115
  end
115
116
 
116
117
  def valid_signature?
117
- # Force signatures for logout requests because there is no other
118
- # protection against a cross-site DoS.
119
- service_provider.valid_signature?(document, logout_request?)
118
+ # Force signatures for logout requests because there is no other protection against a cross-site DoS.
119
+ # Validate signature when metadata specify AuthnRequest should be signed
120
+ metadata = service_provider.current_metadata
121
+ if logout_request? || authn_request? && metadata.respond_to?(:sign_authn_request?) && metadata.sign_authn_request?
122
+ document.valid_signature?(service_provider.fingerprint)
123
+ else
124
+ true
125
+ end
120
126
  end
121
127
 
122
128
  def service_provider?
@@ -1,32 +1,45 @@
1
1
  require 'builder'
2
+ require 'saml_idp/algorithmable'
3
+ require 'saml_idp/signable'
2
4
  module SamlIdp
3
5
  class ResponseBuilder
6
+ include Algorithmable
7
+ include Signable
4
8
  attr_accessor :response_id
5
9
  attr_accessor :issuer_uri
6
10
  attr_accessor :saml_acs_url
7
11
  attr_accessor :saml_request_id
8
12
  attr_accessor :assertion_and_signature
13
+ attr_accessor :raw_algorithm
9
14
 
10
- def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature)
15
+ alias_method :reference_id, :response_id
16
+
17
+ def initialize(response_id, issuer_uri, saml_acs_url, saml_request_id, assertion_and_signature, raw_algorithm)
11
18
  self.response_id = response_id
12
19
  self.issuer_uri = issuer_uri
13
20
  self.saml_acs_url = saml_acs_url
14
21
  self.saml_request_id = saml_request_id
15
22
  self.assertion_and_signature = assertion_and_signature
23
+ self.raw_algorithm = raw_algorithm
16
24
  end
17
25
 
18
- def encoded
19
- @encoded ||= encode
26
+ def encoded(signed_message: false)
27
+ @encoded ||= signed_message ? encode_signed_message : encode_raw_message
20
28
  end
21
29
 
22
30
  def raw
23
31
  build
24
32
  end
25
33
 
26
- def encode
34
+ def encode_raw_message
27
35
  Base64.strict_encode64(raw)
28
36
  end
29
- private :encode
37
+ private :encode_raw_message
38
+
39
+ def encode_signed_message
40
+ Base64.strict_encode64(signed)
41
+ end
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
@@ -17,6 +17,7 @@ module SamlIdp
17
17
  attr_accessor :expiry
18
18
  attr_accessor :encryption_opts
19
19
  attr_accessor :session_expiry
20
+ attr_accessor :signed_message_opts
20
21
 
21
22
  def initialize(reference_id,
22
23
  response_id,
@@ -29,7 +30,8 @@ module SamlIdp
29
30
  authn_context_classref,
30
31
  expiry=60*60,
31
32
  encryption_opts=nil,
32
- session_expiry=0
33
+ session_expiry=0,
34
+ signed_message_opts
33
35
  )
34
36
  self.reference_id = reference_id
35
37
  self.response_id = response_id
@@ -45,10 +47,11 @@ module SamlIdp
45
47
  self.expiry = expiry
46
48
  self.encryption_opts = encryption_opts
47
49
  self.session_expiry = session_expiry
50
+ self.signed_message_opts = signed_message_opts
48
51
  end
49
52
 
50
53
  def build
51
- @built ||= response_builder.encoded
54
+ @built ||= encoded_message
52
55
  end
53
56
 
54
57
  def signed_assertion
@@ -60,8 +63,17 @@ module SamlIdp
60
63
  end
61
64
  private :signed_assertion
62
65
 
66
+ def encoded_message
67
+ if signed_message_opts
68
+ response_builder.encoded(signed_message: true)
69
+ else
70
+ response_builder.encoded(signed_message: false)
71
+ end
72
+ end
73
+ private :encoded_message
74
+
63
75
  def response_builder
64
- ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion)
76
+ ResponseBuilder.new(response_id, issuer_uri, saml_acs_url, saml_request_id, signed_assertion, algorithm)
65
77
  end
66
78
  private :response_builder
67
79
 
@@ -22,18 +22,13 @@ module SamlIdp
22
22
  end
23
23
 
24
24
  def valid_signature?(doc, require_signature = false)
25
- if require_signature || should_validate_signature?
25
+ if require_signature || attributes[:validate_signature]
26
26
  doc.valid_signature?(fingerprint)
27
27
  else
28
28
  true
29
29
  end
30
30
  end
31
31
 
32
- def should_validate_signature?
33
- attributes[:validate_signature] ||
34
- current_metadata.respond_to?(:sign_assertions?) && current_metadata.sign_assertions?
35
- end
36
-
37
32
  def refresh_metadata
38
33
  fresh = fresh_incoming_metadata
39
34
  if valid_signature?(fresh.document)
@@ -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.8.0'
3
+ VERSION = '0.12.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)
@@ -44,19 +44,18 @@ section of the README.
44
44
  INST
45
45
 
46
46
  s.add_dependency('activesupport', '>= 3.2')
47
- s.add_dependency('uuid', '>= 2.3')
48
47
  s.add_dependency('builder', '>= 3.0')
49
48
  s.add_dependency('nokogiri', '>= 1.6.2')
50
49
 
51
50
  s.add_development_dependency('rake')
52
51
  s.add_development_dependency('simplecov')
53
52
  s.add_development_dependency('rspec', '>= 3.7.0')
54
- s.add_development_dependency('ruby-saml', '>= 1.5')
53
+ s.add_development_dependency('ruby-saml', '>= 1.7.2')
55
54
  s.add_development_dependency('rails', '>= 3.2')
56
55
  s.add_development_dependency('activeresource', '>= 3.2')
57
56
  s.add_development_dependency('capybara', '>= 2.16')
58
57
  s.add_development_dependency('timecop', '>= 0.8')
59
58
  s.add_development_dependency('xmlenc', '>= 0.6.4')
60
59
  s.add_development_dependency('appraisal')
60
+ s.add_development_dependency('byebug')
61
61
  end
62
-
@@ -9,6 +9,7 @@ module SamlIdp
9
9
  it { should respond_to :base_saml_location }
10
10
  it { should respond_to :reference_id_generator }
11
11
  it { should respond_to :attribute_service_location }
12
+ it { should respond_to :single_service_redirect_location }
12
13
  it { should respond_to :single_service_post_location }
13
14
  it { should respond_to :single_logout_service_post_location }
14
15
  it { should respond_to :single_logout_service_redirect_location }
@@ -21,6 +21,30 @@ describe SamlIdp::Controller do
21
21
  expect(saml_acs_url).to eq(requested_saml_acs_url)
22
22
  end
23
23
 
24
+ context "When SP metadata required to validate auth request signature" do
25
+ before do
26
+ idp_configure("https://foo.example.com/saml/consume", true)
27
+ params[:SAMLRequest] = make_saml_request("https://foo.example.com/saml/consume", true)
28
+ end
29
+
30
+ it 'SP metadata sign_authn_request attribute should be true' do
31
+ # Signed auth request will be true in the metadata
32
+ expect(SamlIdp.config.service_provider.persisted_metadata_getter.call(nil,nil)[:sign_authn_request]).to eq(true)
33
+ end
34
+
35
+ it 'should call xml signature validation method' do
36
+ signed_doc = SamlIdp::XMLSecurity::SignedDocument.new(params[:SAMLRequest])
37
+ allow(signed_doc).to receive(:validate).and_return(true)
38
+ allow(SamlIdp::XMLSecurity::SignedDocument).to receive(:new).and_return(signed_doc)
39
+ validate_saml_request
40
+ expect(signed_doc).to have_received(:validate).once
41
+ end
42
+
43
+ it 'should successfully validate signature' do
44
+ expect(validate_saml_request).to eq(true)
45
+ end
46
+ end
47
+
24
48
  context "SAML Responses" do
25
49
  let(:principal) { double email_address: "foo@example.com" }
26
50
  let (:encryption_opts) do
@@ -0,0 +1,14 @@
1
+ require 'spec_helper'
2
+
3
+ module SamlIdp
4
+ describe Fingerprint do
5
+ describe "certificate_digest" do
6
+ let(:cert) { sp_x509_cert }
7
+ let(:fingerprint) { "a2:cb:f6:6b:bc:2a:33:b9:4f:f3:c3:7e:26:a4:21:cd:41:83:ef:26:88:fa:ba:71:37:40:07:3e:d5:76:04:b7" }
8
+
9
+ it "returns the fingerprint string" do
10
+ expect(Fingerprint.certificate_digest(cert, :sha256)).to eq(fingerprint)
11
+ end
12
+ end
13
+ end
14
+ end
@@ -3,7 +3,7 @@ module SamlIdp
3
3
 
4
4
  metadata_1 = <<-eos
5
5
  <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
6
- <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="true" WantAssertionsSigned="false">
6
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol" AuthnRequestsSigned="false" WantAssertionsSigned="false">
7
7
  </md:SPSSODescriptor>
8
8
  </md:EntityDescriptor>
9
9
  eos
@@ -22,20 +22,39 @@ module SamlIdp
22
22
  </md:EntityDescriptor>
23
23
  eos
24
24
 
25
+ metadata_4 = <<-eos
26
+ <md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata" xmlns:saml="urn:oasis:names:tc:SAML:2.0:assertion" ID="test" entityID="https://test-saml.com/saml">
27
+ <md:SPSSODescriptor protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol">
28
+ </md:SPSSODescriptor>
29
+ </md:EntityDescriptor>
30
+ eos
31
+
25
32
  describe IncomingMetadata do
26
33
  it 'should properly set sign_assertions to false' do
27
34
  metadata = SamlIdp::IncomingMetadata.new(metadata_1)
28
35
  expect(metadata.sign_assertions).to eq(false)
36
+ expect(metadata.sign_authn_request).to eq(false)
37
+ end
38
+
39
+ it 'should properly set entity_id as https://test-saml.com/saml' do
40
+ metadata = SamlIdp::IncomingMetadata.new(metadata_1)
41
+ expect(metadata.entity_id).to eq('https://test-saml.com/saml')
29
42
  end
30
43
 
31
44
  it 'should properly set sign_assertions to true' do
32
45
  metadata = SamlIdp::IncomingMetadata.new(metadata_2)
33
46
  expect(metadata.sign_assertions).to eq(true)
47
+ expect(metadata.sign_authn_request).to eq(true)
34
48
  end
35
49
 
36
50
  it 'should properly set sign_assertions to false when WantAssertionsSigned is not included' do
37
51
  metadata = SamlIdp::IncomingMetadata.new(metadata_3)
38
52
  expect(metadata.sign_assertions).to eq(false)
39
53
  end
54
+
55
+ it 'should properly set sign_authn_request to false when AuthnRequestsSigned is not included' do
56
+ metadata = SamlIdp::IncomingMetadata.new(metadata_4)
57
+ expect(metadata.sign_authn_request).to eq(false)
58
+ end
40
59
  end
41
60
  end
@@ -11,7 +11,30 @@ module SamlIdp
11
11
 
12
12
  it "includes logout element" do
13
13
  subject.configurator.single_logout_service_post_location = 'https://example.com/saml/logout'
14
+ subject.configurator.single_logout_service_redirect_location = 'https://example.com/saml/logout'
14
15
  expect(subject.fresh).to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/logout"/>')
16
+ expect(subject.fresh).to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/logout"/>')
17
+ end
18
+
19
+ it 'will not includes empty logout endpoint' do
20
+ subject.configurator.single_logout_service_post_location = ''
21
+ subject.configurator.single_logout_service_redirect_location = nil
22
+ expect(subject.fresh).not_to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"')
23
+ expect(subject.fresh).not_to match('<SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"')
24
+ end
25
+
26
+ it 'will includes sso element' do
27
+ subject.configurator.single_service_post_location = 'https://example.com/saml/sso'
28
+ subject.configurator.single_service_redirect_location = 'https://example.com/saml/sso'
29
+ expect(subject.fresh).to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="https://example.com/saml/sso"/>')
30
+ expect(subject.fresh).to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="https://example.com/saml/sso"/>')
31
+ end
32
+
33
+ it 'will not includes empty sso element' do
34
+ subject.configurator.single_service_post_location = ''
35
+ subject.configurator.single_service_redirect_location = nil
36
+ expect(subject.fresh).not_to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"')
37
+ expect(subject.fresh).not_to match('<SingleSignOnService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"')
15
38
  end
16
39
 
17
40
  context "technical contact" do
@@ -6,12 +6,14 @@ module SamlIdp
6
6
  let(:saml_acs_url) { "http://sportngin.com" }
7
7
  let(:saml_request_id) { "134" }
8
8
  let(:assertion_and_signature) { "<Assertion xmlns=\"urn:oasis:names:tc:SAML:2.0:assertion\" ID=\"_abc\" IssueInstant=\"2013-07-31T05:00:00Z\" Version=\"2.0\"><Issuer>http://sportngin.com</Issuer><signature>stuff</signature><Subject><NameID Format=\"urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress\">jon.phenow@sportngin.com</NameID><SubjectConfirmation Method=\"urn:oasis:names:tc:SAML:2.0:cm:bearer\"><SubjectConfirmationData InResponseTo=\"123\" NotOnOrAfter=\"2013-07-31T05:03:00Z\" Recipient=\"http://saml.acs.url\"/></SubjectConfirmation></Subject><Conditions NotBefore=\"2013-07-31T04:59:55Z\" NotOnOrAfter=\"2013-07-31T06:00:00Z\"><AudienceRestriction><Audience>http://example.com</Audience></AudienceRestriction></Conditions><AttributeStatement><Attribute Name=\"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress\"><AttributeValue>jon.phenow@sportngin.com</AttributeValue></Attribute></AttributeStatement><AuthnStatment AuthnInstant=\"2013-07-31T05:00:00Z\" SessionIndex=\"_abc\"><AuthnContext><AuthnContextClassRef>urn:federation:authentication:windows</AuthnContextClassRef></AuthnContext></AuthnStatment></Assertion>" }
9
+ let(:algorithm) { :sha256 }
9
10
  subject { described_class.new(
10
11
  response_id,
11
12
  issuer_uri,
12
13
  saml_acs_url,
13
14
  saml_request_id,
14
- assertion_and_signature
15
+ assertion_and_signature,
16
+ algorithm
15
17
  ) }
16
18
 
17
19
  before do
@@ -24,6 +24,8 @@ module SamlIdp
24
24
  key_transport: 'rsa-oaep-mgf1p',
25
25
  }
26
26
  end
27
+ let(:signed_response_opts) { true }
28
+ let(:unsigned_response_opts) { false }
27
29
  let(:subject_encrypted) { described_class.new(reference_id,
28
30
  response_id,
29
31
  issuer_uri,
@@ -35,7 +37,8 @@ module SamlIdp
35
37
  authn_context_classref,
36
38
  expiry,
37
39
  encryption_opts,
38
- session_expiry
40
+ session_expiry,
41
+ unsigned_response_opts
39
42
  )
40
43
  }
41
44
 
@@ -50,7 +53,8 @@ module SamlIdp
50
53
  authn_context_classref,
51
54
  expiry,
52
55
  nil,
53
- session_expiry
56
+ session_expiry,
57
+ signed_response_opts
54
58
  )
55
59
  }
56
60
 
@@ -77,6 +81,25 @@ module SamlIdp
77
81
  expect(saml_resp.is_valid?).to eq(true)
78
82
  end
79
83
 
84
+ it "will build signed valid response" do
85
+ expect { subject.build }.not_to raise_error
86
+ signed_encoded_xml = subject.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(signed_encoded_xml, settings: resp_settings)
91
+ expect(
92
+ Nokogiri::XML(saml_resp.response).at_xpath(
93
+ "//p:Response//ds:Signature",
94
+ {
95
+ "p" => "urn:oasis:names:tc:SAML:2.0:protocol",
96
+ "ds" => "http://www.w3.org/2000/09/xmldsig#"
97
+ }
98
+ )).to be_present
99
+ expect(saml_resp.send(:validate_signature)).to eq(true)
100
+ expect(saml_resp.is_valid?).to eq(true)
101
+ end
102
+
80
103
  it "sets session expiration" do
81
104
  saml_resp = OneLogin::RubySaml::Response.new(subject.build)
82
105
  expect(saml_resp.session_expires_at).to eq Time.local(1990, "jan", 2).iso8601
@@ -43,6 +43,25 @@ RSpec.configure do |config|
43
43
  }
44
44
  end
45
45
  end
46
+
47
+ # To reset to default config
48
+ config.after do
49
+ SamlIdp.instance_variable_set(:@config, nil)
50
+ SamlIdp.configure do |c|
51
+ c.attributes = {
52
+ emailAddress: {
53
+ name: "email-address",
54
+ getter: ->(p) { "foo@example.com" }
55
+ }
56
+ }
57
+
58
+ c.name_id.formats = {
59
+ "1.1" => {
60
+ email_address: ->(p) { "foo@example.com" }
61
+ }
62
+ }
63
+ end
64
+ end
46
65
  end
47
66
 
48
67
  SamlIdp::Default::SERVICE_PROVIDER[:metadata_url] = 'https://example.com/meta'
@@ -0,0 +1,12 @@
1
+ -----BEGIN CERTIFICATE REQUEST-----
2
+ MIIByTCCATICAQAwgYgxCzAJBgNVBAYTAmpwMQ4wDAYDVQQIDAVUb2t5bzELMAkG
3
+ A1UECgwCR1MxIDAeBgNVBAMMF2h0dHBzOi8vZm9vLmV4YW1wbGUuY29tMQwwCgYD
4
+ VQQHDANGb28xDDAKBgNVBAsMA0JvbzEeMBwGCSqGSIb3DQEJARYPZm9vQGV4YW1w
5
+ bGUuY29tMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC8DVj2mVLQV7AjT+cn
6
+ Lv3kDnQFvAo3RdUeGGhplsYFacYByzNRD/jeguu1ahrvznDyZN8p3yB7OPbmt0r0
7
+ aGr+yYzPh6brgkf5u6FMtWTj94vLQuT/uyQGuzdBkiLb5mAWRMtm43oHXDK0v25J
8
+ tsG1PJnntkXfBDpFP1eWLO+jZwIDAQABoAAwDQYJKoZIhvcNAQENBQADgYEAd/J6
9
+ 5zjrMhgjxuaMuWCiNN7IS4F9SKy+gEmhkpNVCpChbpggruaEIoERjDP/TkZn2dgL
10
+ VUeHTZB92t+wWfQbHNvEfbzqlV3XkuHkxewCwofnIV/k+8zG1Al5ELSKHehItxig
11
+ rnTuBrFYsd2j4HEVqLzm4NyCfL+xzn/D4U2ec50=
12
+ -----END CERTIFICATE REQUEST-----
@@ -0,0 +1,16 @@
1
+ -----BEGIN PRIVATE KEY-----
2
+ MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBALwNWPaZUtBXsCNP
3
+ 5ycu/eQOdAW8CjdF1R4YaGmWxgVpxgHLM1EP+N6C67VqGu/OcPJk3ynfIHs49ua3
4
+ SvRoav7JjM+HpuuCR/m7oUy1ZOP3i8tC5P+7JAa7N0GSItvmYBZEy2bjegdcMrS/
5
+ bkm2wbU8mee2Rd8EOkU/V5Ys76NnAgMBAAECgYEArwclVHCkebIECPnnxbqhKNCj
6
+ AGtifsuKbrZ9CDoDGSq31xeQLdTV6BSm2nVlmOnmilWEuG4qx0Xf2CGlrBI78kmv
7
+ vHCfFdaGnTxbmYnD0HN0u4RK2trsxWO+rEkJk14JE2eVD6ZRPrq1UOSMgGPrQSMb
8
+ SuwAHUu/j94eL8BXuhECQQD3jTlo3Y4VPWttP6XPNqKDP+jRYJs5G0Bch//S9Qy7
9
+ QzmU9/yAUk0BEOyqYcLxinjJhoq6bR2fiIibn+77z3jtAkEAwnhLwkGYOb7Nt3V6
10
+ dQLKx1BP9dnYH7qG/sCmAs7GHPv4LGluaz4zsh2pdEDF/Xar4gwTzUpxYo8FpkCH
11
+ rf4nIwJAVfWnGr/cR4nVVNFGHUcGdXbqvFHEdLb+yWK8NZ+79Qap5w2Zk2GAtb8P
12
+ vzZFQCRqPuhGIegj4jLB5PBLRwtLHQJBAJiWyWL4ExikRUhBTr/HXBL+Sm9u6i0j
13
+ L89unBQx6LNPZhB6/Z/6Y5fLvG2ycWgLGJ06usLnOYaLEHS9x3hXpp8CQQCdtQHw
14
+ xeLBPhRDpfWWbSmFr+bFxyD/4iQHTHToIs3kaecn6OJ4rczIFpGm2Bm7f4X7F3H3
15
+ DDy4jZ0R6iDqCcQD
16
+ -----END PRIVATE KEY-----
@@ -0,0 +1,18 @@
1
+ -----BEGIN CERTIFICATE-----
2
+ MIIC2DCCAkGgAwIBAgIBADANBgkqhkiG9w0BAQ0FADCBiDELMAkGA1UEBhMCanAx
3
+ DjAMBgNVBAgMBVRva3lvMQswCQYDVQQKDAJHUzEgMB4GA1UEAwwXaHR0cHM6Ly9m
4
+ b28uZXhhbXBsZS5jb20xDDAKBgNVBAcMA0ZvbzEMMAoGA1UECwwDQm9vMR4wHAYJ
5
+ KoZIhvcNAQkBFg9mb29AZXhhbXBsZS5jb20wHhcNMjAwMTIzMDYyMzI5WhcNNDcw
6
+ NjA5MDYyMzI5WjCBiDELMAkGA1UEBhMCanAxDjAMBgNVBAgMBVRva3lvMQswCQYD
7
+ VQQKDAJHUzEgMB4GA1UEAwwXaHR0cHM6Ly9mb28uZXhhbXBsZS5jb20xDDAKBgNV
8
+ BAcMA0ZvbzEMMAoGA1UECwwDQm9vMR4wHAYJKoZIhvcNAQkBFg9mb29AZXhhbXBs
9
+ ZS5jb20wgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALwNWPaZUtBXsCNP5ycu
10
+ /eQOdAW8CjdF1R4YaGmWxgVpxgHLM1EP+N6C67VqGu/OcPJk3ynfIHs49ua3SvRo
11
+ av7JjM+HpuuCR/m7oUy1ZOP3i8tC5P+7JAa7N0GSItvmYBZEy2bjegdcMrS/bkm2
12
+ wbU8mee2Rd8EOkU/V5Ys76NnAgMBAAGjUDBOMB0GA1UdDgQWBBQMtOtrh2VS/mh4
13
+ awGbKA37vVnw+zAfBgNVHSMEGDAWgBQMtOtrh2VS/mh4awGbKA37vVnw+zAMBgNV
14
+ HRMEBTADAQH/MA0GCSqGSIb3DQEBDQUAA4GBAHjTTm4Hyx1rfzygknc6q1dYwpEv
15
+ /3AsPiTnF4AfH/5kGIIXNzwg0ADsziFMJYRRR9eMu97CHQbr8gHt99P8uaen6cmJ
16
+ 4VCwJLP2N8gZrycssimA3M83DWRRVZbxZhpuUWNajtYIxwyUbB7eRSJgz3Tc0opF
17
+ 933YwucWuFzKSqn3
18
+ -----END CERTIFICATE-----
@@ -1,9 +1,9 @@
1
1
  require 'saml_idp/logout_request_builder'
2
2
 
3
3
  module SamlRequestMacros
4
- def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume")
4
+ def make_saml_request(requested_saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
5
5
  auth_request = OneLogin::RubySaml::Authrequest.new
6
- auth_url = auth_request.create(saml_settings(requested_saml_acs_url))
6
+ auth_url = auth_request.create(saml_settings(requested_saml_acs_url, enable_secure_options))
7
7
  CGI.unescape(auth_url.split("=").last)
8
8
  end
9
9
 
@@ -18,7 +18,12 @@ module SamlRequestMacros
18
18
  Base64.strict_encode64(request_builder.signed)
19
19
  end
20
20
 
21
- def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume")
21
+ def generate_sp_metadata(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
22
+ sp_metadata = OneLogin::RubySaml::Metadata.new
23
+ sp_metadata.generate(saml_settings(saml_acs_url, enable_secure_options), true)
24
+ end
25
+
26
+ def saml_settings(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
22
27
  settings = OneLogin::RubySaml::Settings.new
23
28
  settings.assertion_consumer_service_url = saml_acs_url
24
29
  settings.issuer = "http://example.com/issuer"
@@ -26,9 +31,63 @@ module SamlRequestMacros
26
31
  settings.assertion_consumer_logout_service_url = 'https://foo.example.com/saml/logout'
27
32
  settings.idp_cert_fingerprint = SamlIdp::Default::FINGERPRINT
28
33
  settings.name_identifier_format = SamlIdp::Default::NAME_ID_FORMAT
34
+ add_securty_options(settings) if enable_secure_options
29
35
  settings
30
36
  end
31
37
 
38
+ def add_securty_options(settings, authn_requests_signed: true,
39
+ embed_sign: true,
40
+ logout_requests_signed: true,
41
+ logout_responses_signed: true,
42
+ digest_method: XMLSecurity::Document::SHA256,
43
+ signature_method: XMLSecurity::Document::RSA_SHA256)
44
+ # Security section
45
+ settings.idp_cert = SamlIdp::Default::X509_CERTIFICATE
46
+ # Signed embedded singature
47
+ settings.security[:authn_requests_signed] = authn_requests_signed
48
+ settings.security[:embed_sign] = embed_sign
49
+ settings.security[:logout_requests_signed] = logout_requests_signed
50
+ settings.security[:logout_responses_signed] = logout_responses_signed
51
+ settings.security[:metadata_signed] = digest_method
52
+ settings.security[:digest_method] = digest_method
53
+ settings.security[:signature_method] = signature_method
54
+ settings.private_key = sp_pv_key
55
+ settings.certificate = sp_x509_cert
56
+ end
57
+
58
+ def idp_configure(saml_acs_url = "https://foo.example.com/saml/consume", enable_secure_options = false)
59
+ SamlIdp.configure do |config|
60
+ config.x509_certificate = SamlIdp::Default::X509_CERTIFICATE
61
+ config.secret_key = SamlIdp::Default::SECRET_KEY
62
+ config.password = nil
63
+ config.algorithm = :sha256
64
+ config.organization_name = 'idp.com'
65
+ config.organization_url = 'http://idp.com'
66
+ config.base_saml_location = 'http://idp.com/saml/idp'
67
+ config.single_logout_service_post_location = 'http://idp.com/saml/idp/logout'
68
+ config.single_logout_service_redirect_location = 'http://idp.com/saml/idp/logout'
69
+ config.attribute_service_location = 'http://idp.com/saml/idp/attribute'
70
+ config.single_service_post_location = 'http://idp.com/saml/idp/sso'
71
+ config.name_id.formats = SamlIdp::Default::NAME_ID_FORMAT
72
+ config.service_provider.metadata_persister = lambda { |_identifier, _service_provider|
73
+ raw_metadata = generate_sp_metadata(saml_acs_url, enable_secure_options)
74
+ SamlIdp::IncomingMetadata.new(raw_metadata).to_h
75
+ }
76
+ config.service_provider.persisted_metadata_getter = lambda { |_identifier, _settings|
77
+ raw_metadata = generate_sp_metadata(saml_acs_url, enable_secure_options)
78
+ SamlIdp::IncomingMetadata.new(raw_metadata).to_h
79
+ }
80
+ config.service_provider.finder = lambda { |_issuer_or_entity_id|
81
+ {
82
+ response_hosts: [URI(saml_acs_url).host],
83
+ acs_url: saml_acs_url,
84
+ cert: sp_x509_cert,
85
+ fingerprint: SamlIdp::Fingerprint.certificate_digest(sp_x509_cert)
86
+ }
87
+ }
88
+ end
89
+ end
90
+
32
91
  def print_pretty_xml(xml_string)
33
92
  doc = REXML::Document.new xml_string
34
93
  outbuf = ""
@@ -58,4 +58,14 @@ module SecurityHelpers
58
58
  def r1_signature_2
59
59
  @signature2 ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'r1_certificate2_base64'))
60
60
  end
61
+
62
+ # Generated by SAML tool https://www.samltool.com/self_signed_certs.php
63
+ def sp_pv_key
64
+ @sp_pv_key ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'sp_private_key.pem'))
65
+ end
66
+
67
+ # Generated by SAML tool https://www.samltool.com/self_signed_certs.php, expired date is 9999
68
+ def sp_x509_cert
69
+ @sp_x509_cert ||= File.read(File.join(File.dirname(__FILE__), 'certificates', 'sp_x509_cert.crt'))
70
+ end
61
71
  end
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.8.0
4
+ version: 0.12.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jon Phenow
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-11-09 00:00:00.000000000 Z
11
+ date: 2020-11-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -24,20 +24,6 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '3.2'
27
- - !ruby/object:Gem::Dependency
28
- name: uuid
29
- requirement: !ruby/object:Gem::Requirement
30
- requirements:
31
- - - ">="
32
- - !ruby/object:Gem::Version
33
- version: '2.3'
34
- type: :runtime
35
- prerelease: false
36
- version_requirements: !ruby/object:Gem::Requirement
37
- requirements:
38
- - - ">="
39
- - !ruby/object:Gem::Version
40
- version: '2.3'
41
27
  - !ruby/object:Gem::Dependency
42
28
  name: builder
43
29
  requirement: !ruby/object:Gem::Requirement
@@ -114,14 +100,14 @@ dependencies:
114
100
  requirements:
115
101
  - - ">="
116
102
  - !ruby/object:Gem::Version
117
- version: '1.5'
103
+ version: 1.7.2
118
104
  type: :development
119
105
  prerelease: false
120
106
  version_requirements: !ruby/object:Gem::Requirement
121
107
  requirements:
122
108
  - - ">="
123
109
  - !ruby/object:Gem::Version
124
- version: '1.5'
110
+ version: 1.7.2
125
111
  - !ruby/object:Gem::Dependency
126
112
  name: rails
127
113
  requirement: !ruby/object:Gem::Requirement
@@ -206,6 +192,20 @@ dependencies:
206
192
  - - ">="
207
193
  - !ruby/object:Gem::Version
208
194
  version: '0'
195
+ - !ruby/object:Gem::Dependency
196
+ name: byebug
197
+ requirement: !ruby/object:Gem::Requirement
198
+ requirements:
199
+ - - ">="
200
+ - !ruby/object:Gem::Version
201
+ version: '0'
202
+ type: :development
203
+ prerelease: false
204
+ version_requirements: !ruby/object:Gem::Requirement
205
+ requirements:
206
+ - - ">="
207
+ - !ruby/object:Gem::Version
208
+ version: '0'
209
209
  description: SAML IdP (Identity Provider) Library for Ruby
210
210
  email: jon.phenow@sportngin.com
211
211
  executables: []
@@ -228,6 +228,7 @@ files:
228
228
  - lib/saml_idp/default.rb
229
229
  - lib/saml_idp/encryptor.rb
230
230
  - lib/saml_idp/engine.rb
231
+ - lib/saml_idp/fingerprint.rb
231
232
  - lib/saml_idp/hashable.rb
232
233
  - lib/saml_idp/incoming_metadata.rb
233
234
  - lib/saml_idp/logout_builder.rb
@@ -254,6 +255,7 @@ files:
254
255
  - spec/lib/saml_idp/configurator_spec.rb
255
256
  - spec/lib/saml_idp/controller_spec.rb
256
257
  - spec/lib/saml_idp/encryptor_spec.rb
258
+ - spec/lib/saml_idp/fingerprint_spec.rb
257
259
  - spec/lib/saml_idp/incoming_metadata_spec.rb
258
260
  - spec/lib/saml_idp/logout_request_builder_spec.rb
259
261
  - spec/lib/saml_idp/logout_response_builder_spec.rb
@@ -319,6 +321,9 @@ files:
319
321
  - spec/spec_helper.rb
320
322
  - spec/support/certificates/certificate1
321
323
  - spec/support/certificates/r1_certificate2_base64
324
+ - spec/support/certificates/sp_cert_req.csr
325
+ - spec/support/certificates/sp_private_key.pem
326
+ - spec/support/certificates/sp_x509_cert.crt
322
327
  - spec/support/responses/adfs_response_sha1.xml
323
328
  - spec/support/responses/adfs_response_sha256.xml
324
329
  - spec/support/responses/adfs_response_sha384.xml
@@ -347,7 +352,7 @@ metadata:
347
352
  homepage_uri: https://github.com/saml-idp/saml_idp
348
353
  source_code_uri: https://github.com/saml-idp/saml_idp
349
354
  bug_tracker_uri: https://github.com/saml-idp/saml_idp/issues
350
- documentation_uri: http://rdoc.info/gems/saml_idp/0.8.0
355
+ documentation_uri: http://rdoc.info/gems/saml_idp/0.12.0
351
356
  post_install_message: |
352
357
  If you're just recently updating saml_idp - please be aware we've changed the default
353
358
  certificate. See the PR and a description of why we've done this here:
@@ -378,8 +383,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
378
383
  - !ruby/object:Gem::Version
379
384
  version: '0'
380
385
  requirements: []
381
- rubyforge_project:
382
- rubygems_version: 2.7.6
386
+ rubygems_version: 3.1.2
383
387
  signing_key:
384
388
  specification_version: 4
385
389
  summary: SAML Indentity Provider for Ruby
@@ -392,6 +396,7 @@ test_files:
392
396
  - spec/lib/saml_idp/configurator_spec.rb
393
397
  - spec/lib/saml_idp/controller_spec.rb
394
398
  - spec/lib/saml_idp/encryptor_spec.rb
399
+ - spec/lib/saml_idp/fingerprint_spec.rb
395
400
  - spec/lib/saml_idp/incoming_metadata_spec.rb
396
401
  - spec/lib/saml_idp/logout_request_builder_spec.rb
397
402
  - spec/lib/saml_idp/logout_response_builder_spec.rb
@@ -457,6 +462,9 @@ test_files:
457
462
  - spec/spec_helper.rb
458
463
  - spec/support/certificates/certificate1
459
464
  - spec/support/certificates/r1_certificate2_base64
465
+ - spec/support/certificates/sp_cert_req.csr
466
+ - spec/support/certificates/sp_private_key.pem
467
+ - spec/support/certificates/sp_x509_cert.crt
460
468
  - spec/support/responses/adfs_response_sha1.xml
461
469
  - spec/support/responses/adfs_response_sha256.xml
462
470
  - spec/support/responses/adfs_response_sha384.xml