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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2e7c4ec8a1921995d89ce16ce8161b20763cb0b3
4
- data.tar.gz: fc40b97c8a0da3edd71c1470867087cde7a11750
3
+ metadata.gz: 1523b17e9ef3134f4d110be787f4a63f600fcaff
4
+ data.tar.gz: f69357bc2618c53524aab2061fb1dec4044f210c
5
5
  SHA512:
6
- metadata.gz: 192bc0456858f0efefd328fdf666cb335aeb84c8e1b83bc993925526263381e5ee5d5547bc6fc47dcaee72f2b4fa46dfc386abcc8adb560f07d87f0c09618a5f
7
- data.tar.gz: be310f063a48d7108c2d6ed35090ccc5d7fcbc9858941e5ec9f5571a1e369ed90e2991a20025bb9e58a3f4a0bab836005be9652a7ab55f4192022c85ed66a982
6
+ metadata.gz: e2cd4dc82b98c2c763edf356607efda096740760b131c89b9a249bee156d9e0dc314c321a087047a99a36850c561d44cb3f3fe3c7b761fa8e9ad265236eba1b2
7
+ data.tar.gz: f7c66a56428fd31d7919881b8c55f0711068ef33c0afc1db30cdf2cbb110766fdf5a80c279b62d2143ee88cdaaa1b867c71fb62fb6f1932f27ceebe0b94a96b8
data/README.md CHANGED
@@ -1,4 +1,8 @@
1
- # Saml::Kit
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.generate_key_pair_for(use: :signing)
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
- sp = Saml::Kit::IdentityProviderMetadata.new(xml)
220
- url, saml_params = sp.logout_request_for(user, binding: :http_post)
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
  ```
@@ -27,12 +27,12 @@ module Saml
27
27
  xml_hash ? Signature.new(xml_hash) : nil
28
28
  end
29
29
 
30
- def expired?
31
- Time.current > expired_at
30
+ def expired?(now = Time.current)
31
+ now > expired_at
32
32
  end
33
33
 
34
- def active?
35
- Time.current > started_at && !expired?
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: @configuration).decrypt(@xml_hash['Response']['EncryptedAssertion'])
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?(@configuration.issuer)
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/binding_spec.rb}
4
5
  class Binding
5
6
  attr_reader :binding, :location
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Bindings
4
+ # {include:file:spec/saml/bindings/http_post_spec.rb}
4
5
  class HttpPost < Binding
5
6
  include Serializable
6
7
 
@@ -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
- if document.provider.verify(algorithm_for(params[:SigAlg]), signature, canonical_form)
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
- if params.respond_to? :inject
64
- params.inject({}) do |memo, (key, value)|
65
- memo[key.to_sym] = value
66
- memo
67
- end
68
- else
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 Bindings
4
+ # {include:file:spec/saml/bindings/url_builder_spec.rb}
4
5
  class UrlBuilder
5
6
  include Serializable
6
7
  attr_reader :configuration
@@ -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
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/identity_provider_metadata_spec.rb}
4
5
  class IdentityProviderMetadata
5
6
  include Saml::Kit::Templatable
6
7
  extend Forwardable
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/logout_request_spec.rb}
4
5
  class LogoutRequest
5
6
  include Saml::Kit::Templatable
6
7
  attr_accessor :id, :destination, :issuer, :name_id_format, :now
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/logout_response_spec.rb}
4
5
  class LogoutResponse
5
6
  include Saml::Kit::Templatable
6
7
  attr_accessor :id, :issuer, :version, :status_code, :now, :destination
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/metadata_spec.rb}
4
5
  class Metadata
5
6
  include Templatable
6
7
 
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/response_spec.rb}
4
5
  class Response
5
6
  include Templatable
6
7
  attr_reader :user, :request
@@ -1,6 +1,7 @@
1
1
  module Saml
2
2
  module Kit
3
3
  module Builders
4
+ # {include:file:spec/saml/builders/service_provider_metadata_spec.rb}
4
5
  class ServiceProviderMetadata
5
6
  include Saml::Kit::Templatable
6
7
  extend Forwardable
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/certificate_spec.rb}
3
4
  class Certificate
4
5
  BEGIN_CERT=/-----BEGIN CERTIFICATE-----/
5
6
  END_CERT=/-----END CERTIFICATE-----/
@@ -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
- @signature_method = :SHA256
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
- @logger = Logger.new(STDOUT)
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
@@ -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
- to_h.fetch(name, {}).fetch('ID', nil)
24
+ root.fetch('ID', nil)
26
25
  end
27
26
 
28
27
  # Returns the Issuer for the SAML document.
29
28
  def issuer
30
- to_h.fetch(name, {}).fetch('Issuer', nil)
29
+ root.fetch('Issuer', nil)
31
30
  end
32
31
 
33
32
  # Returns the Version of the SAML document.
34
33
  def version
35
- to_h.fetch(name, {}).fetch('Version', {})
34
+ root.fetch('Version', {})
36
35
  end
37
36
 
38
37
  # Returns the Destination of the SAML document.
39
38
  def destination
40
- to_h.fetch(name, {}).fetch('Destination', nil)
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(to_h[name]['IssueInstant'])
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
- hash = Hash.from_xml(xml)
77
- if hash['Response'].present?
78
- Response.new(xml, configuration: configuration)
79
- elsif hash['LogoutResponse'].present?
80
- LogoutResponse.new(xml, configuration: configuration)
81
- elsif hash['AuthnRequest'].present?
82
- AuthenticationRequest.new(xml, configuration: configuration)
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
 
@@ -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?
@@ -3,6 +3,8 @@ module Saml
3
3
  # This class is used to parse a LogoutResponse SAML document.
4
4
  #
5
5
  # document = Saml::Kit::LogoutResponse.new(raw_xml)
6
+ #
7
+ # {include:file:spec/examples/logout_response_spec.rb}
6
8
  class LogoutResponse < Document
7
9
  include Respondable
8
10
 
@@ -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
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/examples/response_spec.rb}
3
4
  class Response < Document
4
5
  include Respondable
5
6
  extend Forwardable
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/examples/service_provider_metadata_spec.rb}
3
4
  class ServiceProviderMetadata < Metadata
4
5
  def initialize(xml)
5
6
  super("SPSSODescriptor", xml)
@@ -1,5 +1,5 @@
1
1
  module Saml
2
2
  module Kit
3
- VERSION = "0.2.17"
3
+ VERSION = "0.2.18"
4
4
  end
5
5
  end
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 unless document.at_xpath('//ds:Signature', Xmldsig::NAMESPACES).present?
63
+ return if find_by('//ds:Signature').nil?
63
64
 
64
65
  x509_certificates.each do |certificate|
65
- if now < certificate.not_before
66
- errors.add(:certificate, "Not valid before #{certificate.not_before}")
67
- end
66
+ inactive = now < certificate.not_before
67
+ errors.add(:certificate, "Not valid before #{certificate.not_before}") if inactive
68
68
 
69
- if now > certificate.not_after
70
- errors.add(:certificate, "Not valid after #{certificate.not_after}")
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
- document.search(xpath, Xmldsig::NAMESPACES).map do |item|
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
@@ -1,5 +1,6 @@
1
1
  module Saml
2
2
  module Kit
3
+ # {include:file:spec/saml/xml_decryption_spec.rb}
3
4
  class XmlDecryption
4
5
  # The list of private keys to use to attempt to decrypt the document.
5
6
  attr_reader :private_keys
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.17
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-22 00:00:00.000000000 Z
11
+ date: 2017-12-24 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activemodel