encrypted_cookie_store-instructure 1.0.8 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/encrypted_cookie_store-instructure.gemspec +9 -3
- data/lib/encrypted_cookie_store.rb +159 -169
- metadata +90 -8
- checksums.yaml +0 -15
@@ -1,9 +1,9 @@
|
|
1
1
|
Gem::Specification.new do |s|
|
2
2
|
s.name = %q{encrypted_cookie_store-instructure}
|
3
|
-
s.version = "1.0
|
3
|
+
s.version = "1.1.0"
|
4
4
|
|
5
5
|
s.authors = ["Cody Cutrer", "Jacob Fugal", "James Williams"]
|
6
|
-
s.date = %q{2013-
|
6
|
+
s.date = %q{2013-11-13}
|
7
7
|
s.extra_rdoc_files = [
|
8
8
|
"LICENSE.txt"
|
9
9
|
]
|
@@ -15,6 +15,12 @@ Gem::Specification.new do |s|
|
|
15
15
|
]
|
16
16
|
s.homepage = %q{http://github.com/ccutrer/encrypted_cookie_store}
|
17
17
|
s.require_paths = ["lib"]
|
18
|
-
s.summary = %q{EncryptedCookieStore for Ruby on Rails 2
|
18
|
+
s.summary = %q{EncryptedCookieStore for Ruby on Rails 3.2}
|
19
19
|
s.description = %q{A secure version of Rails' built in CookieStore}
|
20
|
+
|
21
|
+
s.add_dependency "actionpack", "~> 3.2"
|
22
|
+
s.add_development_dependency "bundler", "~> 1.3"
|
23
|
+
s.add_development_dependency "rake"
|
24
|
+
s.add_development_dependency "rspec-rails", "~> 2.0"
|
25
|
+
s.add_development_dependency "debugger"
|
20
26
|
end
|
@@ -1,204 +1,194 @@
|
|
1
1
|
require 'openssl'
|
2
2
|
require 'zlib'
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
require 'active_support/core_ext/hash/deep_dup'
|
5
|
+
require 'active_support/core_ext/numeric/time'
|
6
|
+
require 'action_dispatch'
|
7
|
+
|
8
|
+
module ActionDispatch
|
9
|
+
module Session
|
10
|
+
class EncryptedCookieStore < CookieStore
|
11
|
+
class << self
|
12
|
+
attr_accessor :data_cipher_type
|
13
|
+
end
|
14
|
+
self.data_cipher_type = "aes-128-cbc".freeze
|
6
15
|
|
7
|
-
|
16
|
+
OpenSSLCipherError = OpenSSL::Cipher.const_defined?(:CipherError) ? OpenSSL::Cipher::CipherError : OpenSSL::CipherError
|
8
17
|
|
9
|
-
|
10
|
-
|
11
|
-
end
|
18
|
+
def initialize(app, options = {})
|
19
|
+
@digest = options.delete(:digest) || 'SHA1'
|
12
20
|
|
13
|
-
|
14
|
-
|
15
|
-
def initialize(app, options = {})
|
16
|
-
options[:secret] = options[:secret].call if options[:secret].respond_to?(:call)
|
17
|
-
@logger = options[:logger]
|
18
|
-
ensure_encryption_key_secure(options[:secret])
|
19
|
-
@encryption_key = unhex(options[:secret]).freeze
|
20
|
-
@compress = options[:compress]
|
21
|
-
@compress = true if @compress.nil?
|
22
|
-
@data_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.data_cipher_type)
|
23
|
-
@options = options
|
24
|
-
options[:refresh_interval] ||= 5.minutes
|
25
|
-
super(app, options)
|
26
|
-
end
|
21
|
+
@compress = options[:compress]
|
22
|
+
@compress = true if @compress.nil?
|
27
23
|
|
28
|
-
|
29
|
-
|
30
|
-
|
24
|
+
@secret = options.delete(:secret)
|
25
|
+
@secret = @secret.call if @secret.respond_to?(:call)
|
26
|
+
@secret.freeze
|
27
|
+
@encryption_key = unhex(@secret).freeze
|
28
|
+
ensure_encryption_key_secure
|
31
29
|
|
32
|
-
|
33
|
-
|
34
|
-
old_session_data = Marshal.load(raw_old_session_data) if raw_old_session_data
|
35
|
-
env['encrypted_cookie_store.session_refreshed_at'] ||= session_refreshed_at(old_timestamp)
|
30
|
+
@data_cipher = OpenSSL::Cipher::Cipher.new(EncryptedCookieStore.data_cipher_type)
|
31
|
+
options[:refresh_interval] ||= 5.minutes
|
36
32
|
|
37
|
-
|
33
|
+
super(app, options)
|
34
|
+
end
|
38
35
|
|
39
|
-
|
40
|
-
|
41
|
-
|
36
|
+
private
|
37
|
+
|
38
|
+
# overrides method in ActionDispatch::Session::CookieStore
|
39
|
+
def unpacked_cookie_data(env)
|
40
|
+
env['encrypted_cookie_store.cookie'] ||= begin
|
41
|
+
stale_session_check! do
|
42
|
+
request = ActionDispatch::Request.new(env)
|
43
|
+
if data = unmarshal(request.cookie_jar.signed[@key])
|
44
|
+
data.stringify_keys!
|
45
|
+
end
|
46
|
+
data ||= {}
|
47
|
+
env['encrypted_cookie_store.original_cookie'] = data.deep_dup.except(:timestamp)
|
48
|
+
data
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
42
52
|
|
43
|
-
|
44
|
-
|
53
|
+
# overrides method in ActionDispatch::Session::CookieStore
|
54
|
+
def set_session(env, sid, session_data, options)
|
55
|
+
session_data = super
|
56
|
+
session_data.delete(:timestamp)
|
57
|
+
marshal(session_data, options)
|
58
|
+
end
|
45
59
|
|
46
|
-
|
47
|
-
|
60
|
+
# overrides method in Rack::Session::Cookie
|
61
|
+
def load_session(env)
|
62
|
+
if time = timestamp(env)
|
63
|
+
env['encrypted_cookie_store.session_refreshed_at'] ||= Time.at(time).utc
|
64
|
+
end
|
65
|
+
super
|
66
|
+
end
|
48
67
|
|
49
|
-
|
68
|
+
# overrides method in Rack::Session::Abstract::ID
|
69
|
+
def commit_session?(env, session, options)
|
70
|
+
can_commit = super
|
71
|
+
can_commit && (session_changed?(env, session) || refresh_session?(env, options))
|
72
|
+
end
|
50
73
|
|
51
|
-
old_session_data = nil if options[:expire_after] && old_timestamp && Time.now.utc.to_i > old_timestamp + options[:refresh_interval]
|
52
|
-
return [status, headers, body] if session_data == old_session_data
|
53
74
|
|
54
|
-
|
75
|
+
def timestamp(env)
|
76
|
+
unpacked_cookie_data(env)["timestamp"]
|
77
|
+
end
|
55
78
|
|
56
|
-
|
79
|
+
def session_changed?(env, session)
|
80
|
+
(session || {}).to_hash.stringify_keys.except(:timestamp) != (env['encrypted_cookie_store.original_cookie'] || {})
|
81
|
+
end
|
57
82
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
83
|
+
def refresh_session?(env, options)
|
84
|
+
if options[:expire_after] && options[:refresh_interval] && time = timestamp(env)
|
85
|
+
Time.now.utc.to_i > time + options[:refresh_interval]
|
86
|
+
else
|
87
|
+
false
|
88
|
+
end
|
62
89
|
end
|
63
90
|
|
64
|
-
|
65
|
-
|
91
|
+
def marshal(data, options={})
|
92
|
+
@data_cipher.encrypt
|
93
|
+
@data_cipher.key = @encryption_key
|
66
94
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
95
|
+
session_data = Marshal.dump(data)
|
96
|
+
iv = @data_cipher.random_iv
|
97
|
+
if @compress
|
98
|
+
compressed_session_data = deflate(session_data, 5)
|
99
|
+
compressed_session_data = session_data if compressed_session_data.length >= session_data.length
|
100
|
+
else
|
101
|
+
compressed_session_data = session_data
|
102
|
+
end
|
103
|
+
encrypted_session_data = @data_cipher.update(compressed_session_data) << @data_cipher.final
|
104
|
+
timestamp = Time.now.utc.to_i if options[:expire_after]
|
105
|
+
digest = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), @secret, session_data + timestamp.to_s)
|
73
106
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
session_data = Marshal.dump(session)
|
79
|
-
iv = @data_cipher.random_iv
|
80
|
-
if @compress
|
81
|
-
compressed_session_data = deflate(session_data, 5)
|
82
|
-
compressed_session_data = session_data if compressed_session_data.length >= session_data.length
|
83
|
-
else
|
84
|
-
compressed_session_data = session_data
|
85
|
-
end
|
86
|
-
encrypted_session_data = @data_cipher.update(compressed_session_data) << @data_cipher.final
|
87
|
-
timestamp = Time.now.utc.to_i if @options[:expire_after]
|
88
|
-
digest = OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), secret, session_data + timestamp.to_s)
|
107
|
+
result = "#{base64(iv)}#{compressed_session_data == session_data ? '.' : ' '}#{base64(encrypted_session_data)}.#{base64(digest)}"
|
108
|
+
result << ".#{base64([timestamp].pack('N'))}" if options[:expire_after]
|
109
|
+
result
|
110
|
+
end
|
89
111
|
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
112
|
+
def unmarshal(data, options={})
|
113
|
+
return nil unless data
|
114
|
+
compressed = !!data.index(' ')
|
115
|
+
b64_iv, b64_encrypted_session_data, b64_digest, b64_timestamp = data.split(/\.| /, 4)
|
116
|
+
if b64_iv && b64_encrypted_session_data && b64_digest
|
117
|
+
iv = unbase64(b64_iv)
|
118
|
+
encrypted_session_data = unbase64(b64_encrypted_session_data)
|
119
|
+
digest = unbase64(b64_digest)
|
120
|
+
timestamp = unbase64(b64_timestamp).unpack('N').first if b64_timestamp
|
121
|
+
|
122
|
+
@data_cipher.decrypt
|
123
|
+
@data_cipher.key = @encryption_key
|
124
|
+
@data_cipher.iv = iv
|
125
|
+
session_data = @data_cipher.update(encrypted_session_data) << @data_cipher.final
|
126
|
+
session_data = inflate(session_data) if compressed
|
127
|
+
return nil unless digest == OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), @secret, session_data + timestamp.to_s)
|
128
|
+
if options[:expire_after]
|
129
|
+
return nil unless timestamp && Time.now.utc.to_i <= timestamp + options[:expire_after]
|
130
|
+
end
|
131
|
+
|
132
|
+
loaded_data = Marshal.load(session_data) || nil
|
133
|
+
loaded_data[:timestamp] = timestamp if loaded_data && timestamp
|
134
|
+
loaded_data
|
135
|
+
else
|
136
|
+
nil
|
137
|
+
end
|
138
|
+
rescue Zlib::DataError, OpenSSLCipherError
|
139
|
+
nil
|
140
|
+
end
|
94
141
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
timestamp = unbase64(b64_timestamp).unpack('N').first if b64_timestamp
|
104
|
-
|
105
|
-
@data_cipher.decrypt
|
106
|
-
@data_cipher.key = @encryption_key
|
107
|
-
@data_cipher.iv = iv
|
108
|
-
session_data = @data_cipher.update(encrypted_session_data) << @data_cipher.final
|
109
|
-
session_data = inflate(session_data) if compressed
|
110
|
-
return [nil, nil, nil] unless digest == OpenSSL::HMAC.digest(OpenSSL::Digest::Digest.new(@digest), secret, session_data + timestamp.to_s)
|
111
|
-
if @options[:expire_after]
|
112
|
-
return [nil, nil, nil] unless timestamp
|
113
|
-
return [nil, nil, timestamp] unless Time.now.utc.to_i - timestamp < @options[:expire_after]
|
142
|
+
# To prevent users from using an insecure encryption key like "Password" we make sure that the
|
143
|
+
# encryption key they've provided is at least 30 characters in length.
|
144
|
+
def ensure_encryption_key_secure
|
145
|
+
if @encryption_key.blank?
|
146
|
+
raise ArgumentError, "An encryption key is required for encrypting the " +
|
147
|
+
"cookie session data. Please set config.action_controller.session = { " +
|
148
|
+
"..., :encryption_key => \"some random string of at least " +
|
149
|
+
"16 bytes\", ... } in config/environment.rb"
|
114
150
|
end
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
151
|
+
|
152
|
+
if @encryption_key.size < 16 * 2
|
153
|
+
raise ArgumentError, "The EncryptedCookieStore encryption key must be a " +
|
154
|
+
"hexadecimal string of at least 16 bytes. " +
|
155
|
+
"The value that you've provided, \"#{@encryption_key}\", is " +
|
156
|
+
"#{@encryption_key.size / 2} bytes. You could use the following (randomly " +
|
157
|
+
"generated) string as encryption key: " +
|
158
|
+
ActiveSupport::SecureRandom.hex(16)
|
120
159
|
end
|
121
|
-
[loaded_data, session_data, timestamp]
|
122
|
-
else
|
123
|
-
[nil, nil, nil]
|
124
160
|
end
|
125
|
-
else
|
126
|
-
[nil, nil, nil]
|
127
|
-
end
|
128
|
-
rescue Zlib::DataError
|
129
|
-
[nil, nil, nil]
|
130
|
-
rescue OpenSSLCipherError
|
131
|
-
[nil, nil, nil]
|
132
|
-
end
|
133
161
|
|
134
|
-
|
135
|
-
|
136
|
-
stale_session_check! do
|
137
|
-
request = Rack::Request.new(env)
|
138
|
-
session_data = request.cookies[@key]
|
139
|
-
unmarshal(session_data) || {}
|
162
|
+
def base64(data)
|
163
|
+
::Base64.encode64(data).tr('+/', '-_').gsub(/=|\n/, '')
|
140
164
|
end
|
141
|
-
end
|
142
|
-
end
|
143
165
|
|
144
|
-
|
145
|
-
|
146
|
-
|
166
|
+
def unbase64(data)
|
167
|
+
::Base64.decode64(data.tr('-_', '+/').ljust((data.length + 4 - 1) / 4 * 4, '='))
|
168
|
+
end
|
147
169
|
|
148
|
-
|
149
|
-
|
150
|
-
|
170
|
+
# compress
|
171
|
+
def deflate(string, level)
|
172
|
+
z = Zlib::Deflate.new(level)
|
173
|
+
dst = z.deflate(string, Zlib::FINISH)
|
174
|
+
z.close
|
175
|
+
dst
|
176
|
+
end
|
151
177
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
end
|
178
|
+
# decompress
|
179
|
+
def inflate(string)
|
180
|
+
zstream = Zlib::Inflate.new
|
181
|
+
buf = zstream.inflate(string)
|
182
|
+
zstream.finish
|
183
|
+
zstream.close
|
184
|
+
buf
|
185
|
+
end
|
161
186
|
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
"The value that you've provided, \"#{encryption_key}\", is " +
|
166
|
-
"#{encryption_key.size / 2} bytes. You could use the following (randomly " +
|
167
|
-
"generated) string as encryption key: " +
|
168
|
-
ActiveSupport::SecureRandom.hex(16)
|
187
|
+
def unhex(hex_data)
|
188
|
+
[hex_data].pack("H*")
|
189
|
+
end
|
169
190
|
end
|
170
191
|
end
|
171
|
-
|
172
|
-
def verifier_for(secret, digest)
|
173
|
-
nil
|
174
|
-
end
|
175
|
-
|
176
|
-
def base64(data)
|
177
|
-
ActiveSupport::Base64.encode64(data).tr('+/', '-_').gsub(/=|\n/, '')
|
178
|
-
end
|
179
|
-
|
180
|
-
def unbase64(data)
|
181
|
-
ActiveSupport::Base64.decode64(data.tr('-_', '+/').ljust((data.length + 4 - 1) / 4 * 4, '='))
|
182
|
-
end
|
183
|
-
|
184
|
-
# aka compress
|
185
|
-
def deflate(string, level)
|
186
|
-
z = Zlib::Deflate.new(level)
|
187
|
-
dst = z.deflate(string, Zlib::FINISH)
|
188
|
-
z.close
|
189
|
-
dst
|
190
|
-
end
|
191
|
-
|
192
|
-
# aka decompress
|
193
|
-
def inflate(string)
|
194
|
-
zstream = Zlib::Inflate.new
|
195
|
-
buf = zstream.inflate(string)
|
196
|
-
zstream.finish
|
197
|
-
zstream.close
|
198
|
-
buf
|
199
|
-
end
|
200
|
-
|
201
|
-
def unhex(hex_data)
|
202
|
-
[hex_data].pack("H*")
|
203
|
-
end
|
204
192
|
end
|
193
|
+
|
194
|
+
EncryptedCookieStore = ActionDispatch::Session::EncryptedCookieStore
|
metadata
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: encrypted_cookie_store-instructure
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
|
+
prerelease:
|
5
6
|
platform: ruby
|
6
7
|
authors:
|
7
8
|
- Cody Cutrer
|
@@ -10,8 +11,88 @@ authors:
|
|
10
11
|
autorequire:
|
11
12
|
bindir: bin
|
12
13
|
cert_chain: []
|
13
|
-
date: 2013-
|
14
|
-
dependencies:
|
14
|
+
date: 2013-11-13 00:00:00.000000000 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: actionpack
|
18
|
+
requirement: !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: '3.2'
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
none: false
|
28
|
+
requirements:
|
29
|
+
- - ~>
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: '3.2'
|
32
|
+
- !ruby/object:Gem::Dependency
|
33
|
+
name: bundler
|
34
|
+
requirement: !ruby/object:Gem::Requirement
|
35
|
+
none: false
|
36
|
+
requirements:
|
37
|
+
- - ~>
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '1.3'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
none: false
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.3'
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: rake
|
50
|
+
requirement: !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: !ruby/object:Gem::Requirement
|
59
|
+
none: false
|
60
|
+
requirements:
|
61
|
+
- - ! '>='
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '0'
|
64
|
+
- !ruby/object:Gem::Dependency
|
65
|
+
name: rspec-rails
|
66
|
+
requirement: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ~>
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: '2.0'
|
72
|
+
type: :development
|
73
|
+
prerelease: false
|
74
|
+
version_requirements: !ruby/object:Gem::Requirement
|
75
|
+
none: false
|
76
|
+
requirements:
|
77
|
+
- - ~>
|
78
|
+
- !ruby/object:Gem::Version
|
79
|
+
version: '2.0'
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: debugger
|
82
|
+
requirement: !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
none: false
|
92
|
+
requirements:
|
93
|
+
- - ! '>='
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0'
|
15
96
|
description: A secure version of Rails' built in CookieStore
|
16
97
|
email:
|
17
98
|
executables: []
|
@@ -21,30 +102,31 @@ extra_rdoc_files:
|
|
21
102
|
files:
|
22
103
|
- LICENSE.txt
|
23
104
|
- README.markdown
|
24
|
-
- encrypted_cookie_store-instructure.gemspec
|
25
105
|
- lib/encrypted_cookie_store.rb
|
106
|
+
- encrypted_cookie_store-instructure.gemspec
|
26
107
|
homepage: http://github.com/ccutrer/encrypted_cookie_store
|
27
108
|
licenses: []
|
28
|
-
metadata: {}
|
29
109
|
post_install_message:
|
30
110
|
rdoc_options: []
|
31
111
|
require_paths:
|
32
112
|
- lib
|
33
113
|
required_ruby_version: !ruby/object:Gem::Requirement
|
114
|
+
none: false
|
34
115
|
requirements:
|
35
116
|
- - ! '>='
|
36
117
|
- !ruby/object:Gem::Version
|
37
118
|
version: '0'
|
38
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
39
121
|
requirements:
|
40
122
|
- - ! '>='
|
41
123
|
- !ruby/object:Gem::Version
|
42
124
|
version: '0'
|
43
125
|
requirements: []
|
44
126
|
rubyforge_project:
|
45
|
-
rubygems_version:
|
127
|
+
rubygems_version: 1.8.23
|
46
128
|
signing_key:
|
47
|
-
specification_version:
|
48
|
-
summary: EncryptedCookieStore for Ruby on Rails 2
|
129
|
+
specification_version: 3
|
130
|
+
summary: EncryptedCookieStore for Ruby on Rails 3.2
|
49
131
|
test_files: []
|
50
132
|
has_rdoc:
|
checksums.yaml
DELETED
@@ -1,15 +0,0 @@
|
|
1
|
-
---
|
2
|
-
!binary "U0hBMQ==":
|
3
|
-
metadata.gz: !binary |-
|
4
|
-
OTNlMTI1ODU0MzA1OWU5NGFmOTFkMTk4NjEyNTVjNzVkZjAxOTAxNA==
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
N2Q3MGExZTg4MDgyMWZhMmMxNjc2ZWFlNGVhY2I5MTRmZmRjNDM5OQ==
|
7
|
-
SHA512:
|
8
|
-
metadata.gz: !binary |-
|
9
|
-
MDNiZjdkOTQ3YzMyMGU2N2I5NzI0Zjg5N2ExNGJmM2M5ZjdkNjFlNTFlZThm
|
10
|
-
MWRjNjU1ZGFiNWZlNjRlZWFjMWMzY2VlYjVjZjZlOGQzMWNjMjdjMzM4Zjk1
|
11
|
-
MDA5Yzc5YmFhZDg2YzE5ZTA1MGExOWYyODEyZDY0ZDc4YWU1MGI=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
ZWQwNzg1MjFlZWY0ZGRjYzBlZWM0MWZlMzJiNmJhNDc4NDY0OGIwMTJmYzRi
|
14
|
-
NzU1OTlhYzQ5Yzg0MDQ5NDFlMjM2NTE4OTFhODE2MGU4ZTU4ZDVjZDlkMTk1
|
15
|
-
ODExNjQzZjg1MDcwOTM1MzdhM2JiOTNlODM2NDc0YTFlYjRlMTg=
|