saml2 2.0.2 → 2.1.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 +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
|