saml-kit 0.2.17 → 0.2.18
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 +4 -4
- data/README.md +9 -4
- data/lib/saml/kit/assertion.rb +8 -6
- data/lib/saml/kit/authentication_request.rb +4 -0
- data/lib/saml/kit/bindings/binding.rb +1 -0
- data/lib/saml/kit/bindings/http_post.rb +1 -0
- data/lib/saml/kit/bindings/http_redirect.rb +21 -22
- data/lib/saml/kit/bindings/url_builder.rb +1 -0
- data/lib/saml/kit/builders/authentication_request.rb +1 -0
- data/lib/saml/kit/builders/identity_provider_metadata.rb +1 -0
- data/lib/saml/kit/builders/logout_request.rb +1 -0
- data/lib/saml/kit/builders/logout_response.rb +1 -0
- data/lib/saml/kit/builders/metadata.rb +1 -0
- data/lib/saml/kit/builders/response.rb +1 -0
- data/lib/saml/kit/builders/service_provider_metadata.rb +1 -0
- data/lib/saml/kit/certificate.rb +1 -0
- data/lib/saml/kit/configuration.rb +6 -3
- data/lib/saml/kit/default_registry.rb +2 -1
- data/lib/saml/kit/document.rb +25 -21
- data/lib/saml/kit/fingerprint.rb +2 -0
- data/lib/saml/kit/identity_provider_metadata.rb +4 -0
- data/lib/saml/kit/invalid_document.rb +8 -1
- data/lib/saml/kit/logout_request.rb +2 -0
- data/lib/saml/kit/logout_response.rb +2 -0
- data/lib/saml/kit/metadata.rb +1 -0
- data/lib/saml/kit/response.rb +1 -0
- data/lib/saml/kit/service_provider_metadata.rb +1 -0
- data/lib/saml/kit/version.rb +1 -1
- data/lib/saml/kit/xml.rb +7 -10
- data/lib/saml/kit/xml_decryption.rb +1 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1523b17e9ef3134f4d110be787f4a63f600fcaff
|
4
|
+
data.tar.gz: f69357bc2618c53524aab2061fb1dec4044f210c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e2cd4dc82b98c2c763edf356607efda096740760b131c89b9a249bee156d9e0dc314c321a087047a99a36850c561d44cb3f3fe3c7b761fa8e9ad265236eba1b2
|
7
|
+
data.tar.gz: f7c66a56428fd31d7919881b8c55f0711068ef33c0afc1db30cdf2cbb110766fdf5a80c279b62d2143ee88cdaaa1b867c71fb62fb6f1932f27ceebe0b94a96b8
|
data/README.md
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
-
|
1
|
+

|
2
|
+
|
3
|
+
[](https://rubygems.org/gems/saml-kit)
|
4
|
+
[](https://codeclimate.com/github/saml-kit/saml-kit)
|
5
|
+
[](https://travis-ci.org/saml-kit/saml-kit)
|
2
6
|
|
3
7
|
Saml::Kit is a library with the purpose of creating and consuming SAML
|
4
8
|
documents. It supports the HTTP Post and HTTP Redirect bindings. It can
|
@@ -30,7 +34,8 @@ To specify a global configuration: (useful for a rails application)
|
|
30
34
|
Saml::Kit.configure do |configuration|
|
31
35
|
configuration.issuer = ENV['ISSUER']
|
32
36
|
configuration.generate_key_pair_for(use: :signing)
|
33
|
-
configuration.
|
37
|
+
configuration.add_key_pair(ENV["CERTIFICATE"], ENV["PRIVATE_KEY"], passphrase: ENV['PASSPHRASE'], use: :signing)
|
38
|
+
configuration.generate_key_pair_for(use: :encryption)
|
34
39
|
end
|
35
40
|
```
|
36
41
|
|
@@ -216,8 +221,8 @@ class User
|
|
216
221
|
end
|
217
222
|
|
218
223
|
user = User.new(id: SecureRandom.uuid, email: "hello@example.com")
|
219
|
-
|
220
|
-
url, saml_params =
|
224
|
+
idp = Saml::Kit::IdentityProviderMetadata.new(xml)
|
225
|
+
url, saml_params = idp.logout_request_for(user, binding: :http_post)
|
221
226
|
puts [url, saml_params].inspect
|
222
227
|
# ["https://www.example.com/logout", {"SAMLRequest"=>"PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48TG9nb3V0UmVxdWVzdCBJRD0iXzg3NjZiNTYyLTc2MzQtNDU4Zi04MzJmLTE4ODkwMjRlZDQ0MyIgVmVyc2lvbj0iMi4wIiBJc3N1ZUluc3RhbnQ9IjIwMTctMTItMTlUMDQ6NTg6MThaIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly93d3cuZXhhbXBsZS5jb20vbG9nb3V0IiB4bWxucz0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIj48SXNzdWVyIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIi8+PE5hbWVJRCBGb3JtYXQ9InVybjpvYXNpczpuYW1lczp0YzpTQU1MOjIuMDpuYW1laWQtZm9ybWF0OnBlcnNpc3RlbnQiIHhtbG5zPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5kODc3YWEzZS01YTUyLTRhODAtYTA3ZC1lM2U5YzBjNTA1Nzk8L05hbWVJRD48L0xvZ291dFJlcXVlc3Q+"}]
|
223
228
|
```
|
data/lib/saml/kit/assertion.rb
CHANGED
@@ -27,12 +27,12 @@ module Saml
|
|
27
27
|
xml_hash ? Signature.new(xml_hash) : nil
|
28
28
|
end
|
29
29
|
|
30
|
-
def expired?
|
31
|
-
|
30
|
+
def expired?(now = Time.current)
|
31
|
+
now > expired_at
|
32
32
|
end
|
33
33
|
|
34
|
-
def active?
|
35
|
-
|
34
|
+
def active?(now = Time.current)
|
35
|
+
now > configuration.clock_drift.before(started_at) && !expired?
|
36
36
|
end
|
37
37
|
|
38
38
|
def attributes
|
@@ -69,9 +69,11 @@ module Saml
|
|
69
69
|
|
70
70
|
private
|
71
71
|
|
72
|
+
attr_reader :configuration
|
73
|
+
|
72
74
|
def assertion
|
73
75
|
@assertion ||= if encrypted?
|
74
|
-
decrypted = XmlDecryption.new(configuration:
|
76
|
+
decrypted = XmlDecryption.new(configuration: configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
|
75
77
|
Saml::Kit.logger.debug(decrypted)
|
76
78
|
Hash.from_xml(decrypted)['Assertion']
|
77
79
|
else
|
@@ -91,7 +93,7 @@ module Saml
|
|
91
93
|
end
|
92
94
|
|
93
95
|
def must_match_issuer
|
94
|
-
unless audiences.include?(
|
96
|
+
unless audiences.include?(configuration.issuer)
|
95
97
|
errors[:audience] << error_message(:must_match_issuer)
|
96
98
|
end
|
97
99
|
end
|
@@ -13,6 +13,10 @@ module Saml
|
|
13
13
|
# <saml:Issuer>Day of the Dangerous Cousins</saml:Issuer>
|
14
14
|
# <samlp:NameIDPolicy Format="urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"/>
|
15
15
|
# </samlp:AuthnRequest>
|
16
|
+
#
|
17
|
+
# Example:
|
18
|
+
#
|
19
|
+
# {include:file:spec/examples/authentication_request_spec.rb}
|
16
20
|
class AuthenticationRequest < Document
|
17
21
|
include Requestable
|
18
22
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
module Bindings
|
4
|
+
# {include:file:spec/saml/bindings/http_redirect_spec.rb}
|
4
5
|
class HttpRedirect < Binding
|
5
6
|
include Serializable
|
6
7
|
|
@@ -31,21 +32,26 @@ module Saml
|
|
31
32
|
|
32
33
|
def ensure_valid_signature!(params, document)
|
33
34
|
return if params[:Signature].blank? || params[:SigAlg].blank?
|
34
|
-
|
35
|
-
signature = decode(params[:Signature])
|
36
|
-
canonical_form = [:SAMLRequest, :SAMLResponse, :RelayState, :SigAlg].map do |key|
|
37
|
-
value = params[key]
|
38
|
-
value.present? ? "#{key}=#{value}" : nil
|
39
|
-
end.compact.join('&')
|
40
|
-
|
41
35
|
return if document.provider.nil?
|
42
|
-
|
36
|
+
|
37
|
+
if document.provider.verify(
|
38
|
+
algorithm_for(params[:SigAlg]),
|
39
|
+
decode(params[:Signature]),
|
40
|
+
canonicalize(params)
|
41
|
+
)
|
43
42
|
document.signature_verified!
|
44
43
|
else
|
45
44
|
raise ArgumentError.new("Invalid Signature")
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
48
|
+
def canonicalize(params)
|
49
|
+
[:SAMLRequest, :SAMLResponse, :RelayState, :SigAlg].map do |key|
|
50
|
+
value = params[key]
|
51
|
+
value.present? ? "#{key}=#{value}" : nil
|
52
|
+
end.compact.join('&')
|
53
|
+
end
|
54
|
+
|
49
55
|
def algorithm_for(algorithm)
|
50
56
|
case algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
|
51
57
|
when 256
|
@@ -60,20 +66,13 @@ module Saml
|
|
60
66
|
end
|
61
67
|
|
62
68
|
def normalize(params)
|
63
|
-
|
64
|
-
params
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
SAMLRequest: params['SAMLRequest'] || params[:SAMLRequest],
|
71
|
-
SAMLResponse: params['SAMLResponse'] || params[:SAMLResponse],
|
72
|
-
RelayState: params['RelayState'] || params[:RelayState],
|
73
|
-
Signature: params['Signature'] || params[:Signature],
|
74
|
-
SigAlg: params['SigAlg'] || params[:SigAlg],
|
75
|
-
}
|
76
|
-
end
|
69
|
+
{
|
70
|
+
SAMLRequest: params['SAMLRequest'] || params[:SAMLRequest],
|
71
|
+
SAMLResponse: params['SAMLResponse'] || params[:SAMLResponse],
|
72
|
+
RelayState: params['RelayState'] || params[:RelayState],
|
73
|
+
Signature: params['Signature'] || params[:Signature],
|
74
|
+
SigAlg: params['SigAlg'] || params[:SigAlg],
|
75
|
+
}
|
77
76
|
end
|
78
77
|
end
|
79
78
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
3
|
module Builders
|
4
|
+
# {include:file:spec/saml/builders/authentication_request_spec.rb}
|
4
5
|
class AuthenticationRequest
|
5
6
|
include Saml::Kit::Templatable
|
6
7
|
attr_accessor :id, :now, :issuer, :assertion_consumer_service_url, :name_id_format, :destination
|
data/lib/saml/kit/certificate.rb
CHANGED
@@ -32,14 +32,17 @@ module Saml
|
|
32
32
|
attr_accessor :session_timeout
|
33
33
|
# The logger to write log messages to.
|
34
34
|
attr_accessor :logger
|
35
|
+
# The total allowable clock drift for session timeout validation.
|
36
|
+
attr_accessor :clock_drift
|
35
37
|
|
36
38
|
def initialize # :yields configuration
|
37
|
-
@
|
39
|
+
@clock_drift = 30.seconds
|
38
40
|
@digest_method = :SHA256
|
41
|
+
@key_pairs = []
|
42
|
+
@logger = Logger.new(STDOUT)
|
39
43
|
@registry = DefaultRegistry.new
|
40
44
|
@session_timeout = 3.hours
|
41
|
-
@
|
42
|
-
@key_pairs = []
|
45
|
+
@signature_method = :SHA256
|
43
46
|
yield self if block_given?
|
44
47
|
end
|
45
48
|
|
@@ -26,7 +26,8 @@ module Saml
|
|
26
26
|
# configuration.registry = OnDemandRegistry.new(configuration.registry)
|
27
27
|
# configuration.logger = Rails.logger
|
28
28
|
# end
|
29
|
-
|
29
|
+
#
|
30
|
+
# {include:file:spec/saml/default_registry.rb}
|
30
31
|
class DefaultRegistry
|
31
32
|
def initialize(items = {})
|
32
33
|
@items = items
|
data/lib/saml/kit/document.rb
CHANGED
@@ -17,37 +17,36 @@ module Saml
|
|
17
17
|
@configuration = configuration
|
18
18
|
@content = xml
|
19
19
|
@name = name
|
20
|
-
@xml_hash = Hash.from_xml(xml) || {}
|
21
20
|
end
|
22
21
|
|
23
22
|
# Returns the ID for the SAML document.
|
24
23
|
def id
|
25
|
-
|
24
|
+
root.fetch('ID', nil)
|
26
25
|
end
|
27
26
|
|
28
27
|
# Returns the Issuer for the SAML document.
|
29
28
|
def issuer
|
30
|
-
|
29
|
+
root.fetch('Issuer', nil)
|
31
30
|
end
|
32
31
|
|
33
32
|
# Returns the Version of the SAML document.
|
34
33
|
def version
|
35
|
-
|
34
|
+
root.fetch('Version', {})
|
36
35
|
end
|
37
36
|
|
38
37
|
# Returns the Destination of the SAML document.
|
39
38
|
def destination
|
40
|
-
|
39
|
+
root.fetch('Destination', nil)
|
41
40
|
end
|
42
41
|
|
43
42
|
# Returns the Destination of the SAML document.
|
44
43
|
def issue_instant
|
45
|
-
Time.parse(
|
44
|
+
Time.parse(root['IssueInstant'])
|
46
45
|
end
|
47
46
|
|
48
47
|
# Returns the SAML document returned as a Hash.
|
49
48
|
def to_h
|
50
|
-
@xml_hash
|
49
|
+
@xml_hash ||= Hash.from_xml(content) || {}
|
51
50
|
end
|
52
51
|
|
53
52
|
# Returns the SAML document as an XML string.
|
@@ -68,24 +67,28 @@ module Saml
|
|
68
67
|
end
|
69
68
|
|
70
69
|
class << self
|
70
|
+
XPATH = [
|
71
|
+
"/samlp:AuthnRequest",
|
72
|
+
"/samlp:LogoutRequest",
|
73
|
+
"/samlp:LogoutResponse",
|
74
|
+
"/samlp:Response",
|
75
|
+
].join("|")
|
76
|
+
|
71
77
|
# Returns the raw xml as a Saml::Kit SAML document.
|
72
78
|
#
|
73
79
|
# @param xml [String] the raw xml string.
|
74
80
|
# @param configuration [Saml::Kit::Configuration] the configuration to use for unpacking the document.
|
75
81
|
def to_saml_document(xml, configuration: Saml::Kit.configuration)
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
elsif hash['LogoutRequest'].present?
|
84
|
-
LogoutRequest.new(xml, configuration: configuration)
|
85
|
-
end
|
82
|
+
constructor = {
|
83
|
+
"AuthnRequest" => Saml::Kit::AuthenticationRequest,
|
84
|
+
"LogoutRequest" => Saml::Kit::LogoutRequest,
|
85
|
+
"LogoutResponse" => Saml::Kit::LogoutResponse,
|
86
|
+
"Response" => Saml::Kit::Response,
|
87
|
+
}[Saml::Kit::Xml.new(xml).find_by(XPATH).name] || InvalidDocument
|
88
|
+
constructor.new(xml, configuration: configuration)
|
86
89
|
rescue => error
|
87
90
|
Saml::Kit.logger.error(error)
|
88
|
-
InvalidDocument.new(xml)
|
91
|
+
InvalidDocument.new(xml, configuration: configuration)
|
89
92
|
end
|
90
93
|
|
91
94
|
# @!visibility private
|
@@ -109,18 +112,19 @@ module Saml
|
|
109
112
|
|
110
113
|
attr_reader :content, :name, :configuration
|
111
114
|
|
115
|
+
def root
|
116
|
+
to_h.fetch(name, {})
|
117
|
+
end
|
118
|
+
|
112
119
|
def must_match_xsd
|
113
120
|
matches_xsd?(PROTOCOL_XSD)
|
114
121
|
end
|
115
122
|
|
116
123
|
def must_be_expected_type
|
117
|
-
return if to_h.nil?
|
118
|
-
|
119
124
|
errors[:base] << error_message(:invalid) unless expected_type?
|
120
125
|
end
|
121
126
|
|
122
127
|
def expected_type?
|
123
|
-
return false if to_xml.blank?
|
124
128
|
to_h[name].present?
|
125
129
|
end
|
126
130
|
|
data/lib/saml/kit/fingerprint.rb
CHANGED
@@ -6,6 +6,8 @@ module Saml
|
|
6
6
|
#
|
7
7
|
# puts Saml::Kit::Fingerprint.new(certificate).to_s
|
8
8
|
# # B7:AB:DC:BD:4D:23:58:65:FD:1A:99:0C:5F:89:EA:87:AD:F1:D7:83:34:7A:E9:E4:88:12:DD:46:1F:38:05:93
|
9
|
+
#
|
10
|
+
# {include:file:spec/saml/fingerprint_spec.rb}
|
9
11
|
class Fingerprint
|
10
12
|
# The OpenSSL::X509::Certificate
|
11
13
|
attr_reader :x509
|
@@ -26,6 +26,10 @@ module Saml
|
|
26
26
|
# puts metadata.to_xml
|
27
27
|
#
|
28
28
|
# For more details on generating metadata see {Saml::Kit::Metadata}.
|
29
|
+
#
|
30
|
+
# Example:
|
31
|
+
#
|
32
|
+
# {include:file:spec/examples/identity_provider_metadata_spec.rb}
|
29
33
|
class IdentityProviderMetadata < Metadata
|
30
34
|
def initialize(xml)
|
31
35
|
super("IDPSSODescriptor", xml)
|
@@ -1,13 +1,20 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# {include:file:spec/saml/invalid_document_spec.rb}
|
3
4
|
class InvalidDocument < Document
|
4
5
|
validate do |model|
|
5
6
|
model.errors[:base] << model.error_message(:invalid)
|
6
7
|
end
|
7
8
|
|
8
|
-
def initialize(xml)
|
9
|
+
def initialize(xml, configuration: nil)
|
9
10
|
super(xml, name: "InvalidDocument")
|
10
11
|
end
|
12
|
+
|
13
|
+
def to_h
|
14
|
+
super
|
15
|
+
rescue
|
16
|
+
{}
|
17
|
+
end
|
11
18
|
end
|
12
19
|
end
|
13
20
|
end
|
@@ -20,6 +20,8 @@ module Saml
|
|
20
20
|
# url, saml_params = document.response_for(binding: :http_post)
|
21
21
|
#
|
22
22
|
# See {#response_for} for more information.
|
23
|
+
#
|
24
|
+
# {include:file:spec/examples/logout_request_spec.rb}
|
23
25
|
class LogoutRequest < Document
|
24
26
|
include Requestable
|
25
27
|
validates_presence_of :single_logout_service, if: :expected_type?
|
data/lib/saml/kit/metadata.rb
CHANGED
@@ -21,6 +21,7 @@ module Saml
|
|
21
21
|
#
|
22
22
|
# See {Saml::Kit::Builders::ServiceProviderMetadata} and {Saml::Kit::Builders::IdentityProviderMetadata}
|
23
23
|
# for a list of options that can be specified.
|
24
|
+
# {include:file:spec/examples/metadata_spec.rb}
|
24
25
|
class Metadata
|
25
26
|
METADATA_XSD = File.expand_path("./xsd/saml-schema-metadata-2.0.xsd", File.dirname(__FILE__)).freeze
|
26
27
|
include ActiveModel::Validations
|
data/lib/saml/kit/response.rb
CHANGED
data/lib/saml/kit/version.rb
CHANGED
data/lib/saml/kit/xml.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
module Saml
|
2
2
|
module Kit
|
3
|
+
# {include:file:spec/saml/xml_spec.rb}
|
3
4
|
class Xml # :nodoc:
|
4
5
|
include ActiveModel::Validations
|
5
6
|
NAMESPACES = {
|
@@ -59,24 +60,20 @@ module Saml
|
|
59
60
|
end
|
60
61
|
|
61
62
|
def validate_certificates(now = Time.current)
|
62
|
-
return
|
63
|
+
return if find_by('//ds:Signature').nil?
|
63
64
|
|
64
65
|
x509_certificates.each do |certificate|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
66
|
+
inactive = now < certificate.not_before
|
67
|
+
errors.add(:certificate, "Not valid before #{certificate.not_before}") if inactive
|
68
68
|
|
69
|
-
|
70
|
-
|
71
|
-
end
|
69
|
+
expired = now > certificate.not_after
|
70
|
+
errors.add(:certificate, "Not valid after #{certificate.not_after}") if expired
|
72
71
|
end
|
73
72
|
end
|
74
73
|
|
75
74
|
def x509_certificates
|
76
75
|
xpath = "//ds:KeyInfo/ds:X509Data/ds:X509Certificate"
|
77
|
-
|
78
|
-
Certificate.to_x509(item.text)
|
79
|
-
end
|
76
|
+
find_all(xpath).map { |item| Certificate.to_x509(item.text) }
|
80
77
|
end
|
81
78
|
end
|
82
79
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saml-kit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.18
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- mo khan
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-12-
|
11
|
+
date: 2017-12-24 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activemodel
|