saml2 2.0.2 → 2.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/saml2.rb +2 -0
- data/lib/saml2/assertion.rb +6 -0
- data/lib/saml2/attribute.rb +45 -13
- data/lib/saml2/attribute/x500.rb +32 -19
- data/lib/saml2/attribute_consuming_service.rb +52 -4
- data/lib/saml2/authn_request.rb +39 -3
- data/lib/saml2/authn_statement.rb +23 -11
- data/lib/saml2/base.rb +36 -0
- data/lib/saml2/bindings.rb +3 -1
- data/lib/saml2/bindings/http_post.rb +17 -1
- data/lib/saml2/bindings/http_redirect.rb +54 -9
- data/lib/saml2/conditions.rb +43 -16
- data/lib/saml2/contact.rb +17 -6
- data/lib/saml2/endpoint.rb +13 -0
- data/lib/saml2/engine.rb +2 -0
- data/lib/saml2/entity.rb +20 -0
- data/lib/saml2/identity_provider.rb +11 -1
- data/lib/saml2/indexed_object.rb +13 -3
- data/lib/saml2/key.rb +89 -32
- data/lib/saml2/localized_name.rb +8 -0
- data/lib/saml2/logout_request.rb +12 -3
- data/lib/saml2/logout_response.rb +9 -0
- data/lib/saml2/message.rb +38 -7
- data/lib/saml2/name_id.rb +42 -16
- data/lib/saml2/namespaces.rb +10 -8
- data/lib/saml2/organization.rb +5 -0
- data/lib/saml2/organization_and_contacts.rb +5 -0
- data/lib/saml2/request.rb +3 -0
- data/lib/saml2/requested_authn_context.rb +7 -1
- data/lib/saml2/response.rb +20 -2
- data/lib/saml2/role.rb +12 -2
- data/lib/saml2/schemas.rb +2 -0
- data/lib/saml2/service_provider.rb +6 -0
- data/lib/saml2/signable.rb +32 -2
- data/lib/saml2/sso.rb +7 -0
- data/lib/saml2/status.rb +8 -1
- data/lib/saml2/status_response.rb +7 -1
- data/lib/saml2/subject.rb +22 -5
- data/lib/saml2/version.rb +3 -1
- data/spec/lib/bindings/http_redirect_spec.rb +23 -2
- data/spec/lib/conditions_spec.rb +10 -11
- data/spec/lib/identity_provider_spec.rb +1 -1
- data/spec/lib/service_provider_spec.rb +7 -2
- metadata +5 -5
data/lib/saml2/schemas.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'nokogiri'
|
2
4
|
|
3
5
|
require 'saml2/endpoint'
|
@@ -11,12 +13,14 @@ module SAML2
|
|
11
13
|
@attribute_consuming_services = AttributeConsumingService::Array.new
|
12
14
|
end
|
13
15
|
|
16
|
+
# (see Base#from_xml)
|
14
17
|
def from_xml(node)
|
15
18
|
super
|
16
19
|
@assertion_consumer_services = nil
|
17
20
|
@attribute_consuming_services = nil
|
18
21
|
end
|
19
22
|
|
23
|
+
# @return [Endpoint::Indexed::Array]
|
20
24
|
def assertion_consumer_services
|
21
25
|
@assertion_consumer_services ||= begin
|
22
26
|
nodes = xml.xpath('md:AssertionConsumerService', Namespaces::ALL)
|
@@ -24,6 +28,7 @@ module SAML2
|
|
24
28
|
end
|
25
29
|
end
|
26
30
|
|
31
|
+
# @return [AttributeConsumingService::Array]
|
27
32
|
def attribute_consuming_services
|
28
33
|
@attribute_consuming_services ||= begin
|
29
34
|
nodes = xml.xpath('md:AttributeConsumingService', Namespaces::ALL)
|
@@ -31,6 +36,7 @@ module SAML2
|
|
31
36
|
end
|
32
37
|
end
|
33
38
|
|
39
|
+
# (see Base#build)
|
34
40
|
def build(builder)
|
35
41
|
builder['md'].SPSSODescriptor do |sp_sso_descriptor|
|
36
42
|
super(sp_sso_descriptor)
|
data/lib/saml2/signable.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/key'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
module Signable
|
7
|
+
# @return [Nokogiri::XML::Element, nil]
|
5
8
|
def signature
|
6
9
|
unless instance_variable_defined?(:@signature)
|
7
10
|
@signature = xml.at_xpath('dsig:Signature', Namespaces::ALL)
|
@@ -19,14 +22,26 @@ module SAML2
|
|
19
22
|
@signature
|
20
23
|
end
|
21
24
|
|
25
|
+
# @return [KeyInfo, nil]
|
22
26
|
def signing_key
|
23
|
-
@signing_key ||=
|
27
|
+
@signing_key ||= KeyInfo.from_xml(signature)
|
24
28
|
end
|
25
29
|
|
26
30
|
def signed?
|
27
31
|
!!signature
|
28
32
|
end
|
29
33
|
|
34
|
+
# Validate the signature on this object.
|
35
|
+
#
|
36
|
+
# Either +fingerprint+ or +cert+ must be provided.
|
37
|
+
#
|
38
|
+
# @param fingerprint optional [Array<String>, String]
|
39
|
+
# SHA1 fingerprints of trusted certificates. If provided, they will be
|
40
|
+
# checked against the {#signing_key} embedded in the {#signature}, and if
|
41
|
+
# a match is found, the certificate embedded in the signature will be
|
42
|
+
# added to the list of certificates used for verifying the signature.
|
43
|
+
# @param cert optional [Array<String>, String]
|
44
|
+
# @return [Array<String>] An empty array on success, details of errors on failure.
|
30
45
|
def validate_signature(fingerprint: nil, cert: nil, verification_time: nil)
|
31
46
|
return ["not signed"] unless signed?
|
32
47
|
|
@@ -34,7 +49,7 @@ module SAML2
|
|
34
49
|
# see if any given fingerprints match the certificate embedded in the XML;
|
35
50
|
# if so, extract the certificate, and add it to the allowed certificates list
|
36
51
|
Array(fingerprint)&.each do |fp|
|
37
|
-
certs << signing_key.certificate if signing_key&.fingerprint ==
|
52
|
+
certs << signing_key.certificate if signing_key&.fingerprint == KeyInfo.format_fingerprint(fp)
|
38
53
|
end
|
39
54
|
certs = certs.uniq
|
40
55
|
return ["no certificate found"] if certs.empty?
|
@@ -49,10 +64,25 @@ module SAML2
|
|
49
64
|
end
|
50
65
|
end
|
51
66
|
|
67
|
+
# Check if the signature on this object is valid.
|
68
|
+
#
|
69
|
+
# Either +fingerprint+ or +cert+ must be provided.
|
70
|
+
#
|
71
|
+
# @param (see #validate_signature)
|
72
|
+
# @return [Boolean]
|
52
73
|
def valid_signature?(fingerprint: nil, cert: nil, verification_time: nil)
|
53
74
|
validate_signature(fingerprint: fingerprint, cert: cert, verification_time: verification_time).empty?
|
54
75
|
end
|
55
76
|
|
77
|
+
# Sign this object.
|
78
|
+
#
|
79
|
+
# @param x509_certificate [String]
|
80
|
+
# The certificate corresponding to +private_key+, to be embedded in the
|
81
|
+
# signature.
|
82
|
+
# @param private_key [String]
|
83
|
+
# The key to use to sign.
|
84
|
+
# @param algorithm_name [Symbol]
|
85
|
+
# @return [self]
|
56
86
|
def sign(x509_certificate, private_key, algorithm_name = :sha256)
|
57
87
|
to_xml
|
58
88
|
|
data/lib/saml2/sso.rb
CHANGED
@@ -1,6 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/role'
|
2
4
|
|
3
5
|
module SAML2
|
6
|
+
# @abstract
|
4
7
|
class SSO < Role
|
5
8
|
def initialize
|
6
9
|
super
|
@@ -8,21 +11,25 @@ module SAML2
|
|
8
11
|
@name_id_formats = []
|
9
12
|
end
|
10
13
|
|
14
|
+
# (see Base#from_xml)
|
11
15
|
def from_xml(node)
|
12
16
|
super
|
13
17
|
@single_logout_services = nil
|
14
18
|
@name_id_formats = nil
|
15
19
|
end
|
16
20
|
|
21
|
+
# @return [Array<Endpoint>]
|
17
22
|
def single_logout_services
|
18
23
|
@single_logout_services ||= load_object_array(xml, 'md:SingleLogoutService', Endpoint)
|
19
24
|
end
|
20
25
|
|
26
|
+
# @return [Array<String>]
|
21
27
|
def name_id_formats
|
22
28
|
@name_id_formats ||= load_string_array(xml, 'md:NameIDFormat')
|
23
29
|
end
|
24
30
|
|
25
31
|
protected
|
32
|
+
|
26
33
|
# should be called from inside the role element
|
27
34
|
def build(builder)
|
28
35
|
super
|
data/lib/saml2/status.rb
CHANGED
@@ -1,15 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/base'
|
2
4
|
|
3
5
|
module SAML2
|
4
6
|
class Status < Base
|
5
|
-
SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
7
|
+
SUCCESS = "urn:oasis:names:tc:SAML:2.0:status:Success"
|
6
8
|
|
9
|
+
# @return [String]
|
7
10
|
attr_accessor :code, :message
|
8
11
|
|
12
|
+
# @param code [String]
|
13
|
+
# @param message [String, nil]
|
9
14
|
def initialize(code = SUCCESS, message = nil)
|
10
15
|
@code, @message = code, message
|
11
16
|
end
|
12
17
|
|
18
|
+
# (see Base#from_xml)
|
13
19
|
def from_xml(node)
|
14
20
|
super
|
15
21
|
self.code = node.at_xpath('samlp:StatusCode', Namespaces::ALL)['Value']
|
@@ -20,6 +26,7 @@ module SAML2
|
|
20
26
|
code == SUCCESS
|
21
27
|
end
|
22
28
|
|
29
|
+
# (see Base#build)
|
23
30
|
def build(builder)
|
24
31
|
builder['samlp'].Status do |status|
|
25
32
|
status['samlp'].StatusCode(Value: code)
|
@@ -1,21 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/message'
|
2
4
|
require 'saml2/status'
|
3
5
|
|
4
6
|
module SAML2
|
5
7
|
class StatusResponse < Message
|
6
|
-
|
8
|
+
# @return [String]
|
9
|
+
attr_accessor :in_response_to
|
10
|
+
attr_writer :status
|
7
11
|
|
8
12
|
def initialize
|
9
13
|
super
|
10
14
|
@status = Status.new
|
11
15
|
end
|
12
16
|
|
17
|
+
# (see Base#from_xml)
|
13
18
|
def from_xml(node)
|
14
19
|
super
|
15
20
|
@status = nil
|
16
21
|
remove_instance_variable(:@status)
|
17
22
|
end
|
18
23
|
|
24
|
+
# @return [Status]
|
19
25
|
def status
|
20
26
|
@status ||= Status.from_xml(xml.at_xpath('samlp:Status', Namespaces::ALL))
|
21
27
|
end
|
data/lib/saml2/subject.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'saml2/name_id'
|
2
4
|
require 'saml2/namespaces'
|
3
5
|
|
@@ -10,11 +12,13 @@ module SAML2
|
|
10
12
|
@confirmations = []
|
11
13
|
end
|
12
14
|
|
15
|
+
# (see Base#from_xml)
|
13
16
|
def from_xml(node)
|
14
17
|
super
|
15
18
|
@confirmations = nil
|
16
19
|
end
|
17
20
|
|
21
|
+
# @return [NameID]
|
18
22
|
def name_id
|
19
23
|
if xml && !instance_variable_defined?(:@name_id)
|
20
24
|
@name_id = NameID.from_xml(xml.at_xpath('saml:NameID', Namespaces::ALL))
|
@@ -22,18 +26,23 @@ module SAML2
|
|
22
26
|
@name_id
|
23
27
|
end
|
24
28
|
|
29
|
+
# @return [Confirmation, nil]
|
25
30
|
def confirmation
|
26
31
|
Array.wrap(confirmations).first
|
27
32
|
end
|
28
33
|
|
34
|
+
# @return [Confirmation, nil]
|
29
35
|
def confirmation=(value)
|
30
|
-
@confirmations = [value]
|
36
|
+
@confirmations = value.nil? ? [] : [value]
|
37
|
+
confirmation
|
31
38
|
end
|
32
39
|
|
40
|
+
# @return [Confirmation, Array<Confirmation>]
|
33
41
|
def confirmations
|
34
42
|
@confirmations ||= load_object_array(xml, 'saml:SubjectConfirmation', Confirmation)
|
35
43
|
end
|
36
44
|
|
45
|
+
# (see Base#build)
|
37
46
|
def build(builder)
|
38
47
|
builder['saml'].Subject do |subject|
|
39
48
|
name_id.build(subject) if name_id
|
@@ -45,13 +54,20 @@ module SAML2
|
|
45
54
|
|
46
55
|
class Confirmation < Base
|
47
56
|
module Methods
|
48
|
-
BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'
|
49
|
-
HOLDER_OF_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key'
|
50
|
-
SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches'
|
57
|
+
BEARER = 'urn:oasis:names:tc:SAML:2.0:cm:bearer'
|
58
|
+
HOLDER_OF_KEY = 'urn:oasis:names:tc:SAML:2.0:cm:holder-of-key'
|
59
|
+
SENDER_VOUCHES = 'urn:oasis:names:tc:SAML:2.0:cm:sender-vouches'
|
51
60
|
end
|
52
61
|
|
53
|
-
|
62
|
+
# @see Methods
|
63
|
+
# @return [String]
|
64
|
+
attr_accessor :method
|
65
|
+
# @return [Time, nil]
|
66
|
+
attr_accessor :not_before, :not_on_or_after
|
67
|
+
# @return [String, nil]
|
68
|
+
attr_accessor :recipient, :in_response_to
|
54
69
|
|
70
|
+
# (see Base#from_xml)
|
55
71
|
def from_xml(node)
|
56
72
|
super
|
57
73
|
self.method = node['Method']
|
@@ -64,6 +80,7 @@ module SAML2
|
|
64
80
|
end
|
65
81
|
end
|
66
82
|
|
83
|
+
# (see Base#build)
|
67
84
|
def build(builder)
|
68
85
|
builder['saml'].SubjectConfirmation('Method' => method) do |subject_confirmation|
|
69
86
|
if in_response_to ||
|
data/lib/saml2/version.rb
CHANGED
@@ -146,15 +146,36 @@ module SAML2
|
|
146
146
|
allow(message).to receive(:destination).and_return("http://somewhere/")
|
147
147
|
allow(message).to receive(:to_s).and_return("hi")
|
148
148
|
key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
|
149
|
-
url = Bindings::HTTPRedirect.encode(message,
|
149
|
+
url = Bindings::HTTPRedirect.encode(message,
|
150
|
+
relay_state: "abc",
|
151
|
+
private_key: key)
|
150
152
|
|
151
153
|
# verify the signature
|
152
154
|
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
153
155
|
Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
|
154
|
-
expect(sig_alg).
|
156
|
+
expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA1
|
155
157
|
OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
|
156
158
|
end
|
157
159
|
end
|
160
|
+
|
161
|
+
it 'signs a message with RSA-SHA256' do
|
162
|
+
message = double()
|
163
|
+
allow(message).to receive(:destination).and_return("http://somewhere/")
|
164
|
+
allow(message).to receive(:to_s).and_return("hi")
|
165
|
+
key = OpenSSL::PKey::RSA.new(fixture('privatekey.key'))
|
166
|
+
url = Bindings::HTTPRedirect.encode(message,
|
167
|
+
relay_state: "abc",
|
168
|
+
private_key: key,
|
169
|
+
sig_alg: Bindings::HTTPRedirect::SigAlgs::RSA_SHA256)
|
170
|
+
|
171
|
+
# verify the signature
|
172
|
+
allow(Message).to receive(:parse).with("hi").and_return("parsed")
|
173
|
+
Bindings::HTTPRedirect.decode(url) do |_message, sig_alg|
|
174
|
+
expect(sig_alg).to eq Bindings::HTTPRedirect::SigAlgs::RSA_SHA256
|
175
|
+
OpenSSL::X509::Certificate.new(fixture('certificate.pem')).public_key
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
158
179
|
end
|
159
180
|
end
|
160
181
|
end
|
data/spec/lib/conditions_spec.rb
CHANGED
@@ -3,13 +3,13 @@ require_relative '../spec_helper'
|
|
3
3
|
module SAML2
|
4
4
|
describe Conditions do
|
5
5
|
it "empty should be valid" do
|
6
|
-
expect(Conditions.new.valid?).to eq
|
6
|
+
expect(Conditions.new.valid?).to eq true
|
7
7
|
end
|
8
8
|
|
9
9
|
it "should be valid with unknown condition" do
|
10
10
|
conditions = Conditions.new
|
11
11
|
conditions << Conditions::Condition.new
|
12
|
-
expect(conditions.valid?).to eq
|
12
|
+
expect(conditions.valid?).to eq nil
|
13
13
|
end
|
14
14
|
|
15
15
|
it "should be valid with timestamps" do
|
@@ -17,7 +17,7 @@ module SAML2
|
|
17
17
|
now = Time.now.utc
|
18
18
|
conditions.not_before = now - 5
|
19
19
|
conditions.not_on_or_after = now + 30
|
20
|
-
expect(conditions.valid?).to eq
|
20
|
+
expect(conditions.valid?).to eq true
|
21
21
|
end
|
22
22
|
|
23
23
|
it "should be invalid with out of range timestamps" do
|
@@ -25,7 +25,7 @@ module SAML2
|
|
25
25
|
now = Time.now.utc
|
26
26
|
conditions.not_before = now - 35
|
27
27
|
conditions.not_on_or_after = now - 5
|
28
|
-
expect(conditions.valid?).to eq
|
28
|
+
expect(conditions.valid?).to eq false
|
29
29
|
end
|
30
30
|
|
31
31
|
it "should allow passing now" do
|
@@ -33,7 +33,7 @@ module SAML2
|
|
33
33
|
now = Time.now.utc
|
34
34
|
conditions.not_before = now - 35
|
35
35
|
conditions.not_on_or_after = now - 5
|
36
|
-
expect(conditions.valid?(now: now - 10)).to eq
|
36
|
+
expect(conditions.valid?(now: now - 10)).to eq true
|
37
37
|
end
|
38
38
|
|
39
39
|
it "should be invalid before indeterminate" do
|
@@ -41,29 +41,28 @@ module SAML2
|
|
41
41
|
now = Time.now.utc
|
42
42
|
conditions.not_before = now + 5
|
43
43
|
conditions << Conditions::Condition.new
|
44
|
-
expect(conditions.valid?).to eq
|
44
|
+
expect(conditions.valid?).to eq false
|
45
45
|
end
|
46
46
|
|
47
47
|
it "should be invalid before indeterminate (actual conditions)" do
|
48
48
|
conditions = Conditions.new
|
49
49
|
conditions << Conditions::Condition.new
|
50
50
|
conditions << Conditions::AudienceRestriction.new('audience')
|
51
|
-
expect(conditions.valid?).to eq
|
51
|
+
expect(conditions.valid?).to eq false
|
52
52
|
end
|
53
|
-
|
54
53
|
end
|
55
54
|
|
56
55
|
describe Conditions::AudienceRestriction do
|
57
56
|
it "should be invalid" do
|
58
|
-
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual')).to eq
|
57
|
+
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'actual')).to eq false
|
59
58
|
end
|
60
59
|
|
61
60
|
it "should be valid" do
|
62
|
-
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'expected')).to eq
|
61
|
+
expect(Conditions::AudienceRestriction.new('expected').valid?(audience: 'expected')).to eq true
|
63
62
|
end
|
64
63
|
|
65
64
|
it "should be valid with an array" do
|
66
|
-
expect(Conditions::AudienceRestriction.new(['expected', 'actual']).valid?(audience: 'actual')).to eq
|
65
|
+
expect(Conditions::AudienceRestriction.new(['expected', 'actual']).valid?(audience: 'actual')).to eq true
|
67
66
|
end
|
68
67
|
end
|
69
68
|
end
|
@@ -14,7 +14,7 @@ module SAML2
|
|
14
14
|
idp = IdentityProvider.new
|
15
15
|
idp.name_id_formats << NameID::Format::PERSISTENT
|
16
16
|
idp.single_sign_on_services << Endpoint.new('https://sso.canvaslms.com/SAML2/Login')
|
17
|
-
idp.keys <<
|
17
|
+
idp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::SIGNING)
|
18
18
|
|
19
19
|
entity.roles << idp
|
20
20
|
expect(Schemas.metadata.validate(Nokogiri::XML(entity.to_s))).to eq []
|
@@ -16,8 +16,8 @@ module SAML2
|
|
16
16
|
Bindings::HTTPRedirect::URN)
|
17
17
|
sp.assertion_consumer_services << Endpoint::Indexed.new('https://sso.canvaslms.com/SAML2/Login1')
|
18
18
|
sp.assertion_consumer_services << Endpoint::Indexed.new('https://sso.canvaslms.com/SAML2/Login2')
|
19
|
-
sp.keys <<
|
20
|
-
sp.keys <<
|
19
|
+
sp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::ENCRYPTION, [KeyDescriptor::EncryptionMethod.new])
|
20
|
+
sp.keys << KeyDescriptor.new('somedata', KeyDescriptor::Type::SIGNING)
|
21
21
|
acs = AttributeConsumingService.new
|
22
22
|
acs.name[:en] = 'service'
|
23
23
|
acs.requested_attributes << RequestedAttribute.create('uid')
|
@@ -59,6 +59,11 @@ module SAML2
|
|
59
59
|
expect(acs.requested_attributes.length).to eq 1
|
60
60
|
expect(acs.requested_attributes.first.name).to eq 'urn:oid:2.5.4.42'
|
61
61
|
end
|
62
|
+
|
63
|
+
it "loads the key info" do
|
64
|
+
expect(sp.keys.first.encryption_methods.first.algorithm).to eq KeyDescriptor::EncryptionMethod::Algorithm::AES128_CBC
|
65
|
+
expect(sp.keys.first.encryption_methods.first.key_size).to eq 128
|
66
|
+
end
|
62
67
|
end
|
63
68
|
end
|
64
69
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: saml2
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.0
|
4
|
+
version: 2.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cody Cutrer
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-02
|
11
|
+
date: 2018-03-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: nokogiri
|
@@ -31,7 +31,7 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: '1.9'
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name: nokogiri-xmlsec-
|
34
|
+
name: nokogiri-xmlsec-instructure
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
@@ -39,7 +39,7 @@ dependencies:
|
|
39
39
|
version: '0.9'
|
40
40
|
- - ">="
|
41
41
|
- !ruby/object:Gem::Version
|
42
|
-
version: 0.9.
|
42
|
+
version: 0.9.4
|
43
43
|
type: :runtime
|
44
44
|
prerelease: false
|
45
45
|
version_requirements: !ruby/object:Gem::Requirement
|
@@ -49,7 +49,7 @@ dependencies:
|
|
49
49
|
version: '0.9'
|
50
50
|
- - ">="
|
51
51
|
- !ruby/object:Gem::Version
|
52
|
-
version: 0.9.
|
52
|
+
version: 0.9.4
|
53
53
|
- !ruby/object:Gem::Dependency
|
54
54
|
name: activesupport
|
55
55
|
requirement: !ruby/object:Gem::Requirement
|