saml-kit 0.2.17 → 0.2.18
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
![SAML-Kit](https://github.com/saml-kit/saml-kit/raw/master/spec/examples/saml-kit.gif)
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/saml-kit.svg)](https://rubygems.org/gems/saml-kit)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/saml-kit/saml-kit.svg)](https://codeclimate.com/github/saml-kit/saml-kit)
|
5
|
+
[![Build Status](https://travis-ci.org/saml-kit/saml-kit.svg)](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
|