couchbase-orm 2.0.5 → 2.0.6
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/README.md +5 -2
- data/couchbase-orm.gemspec +1 -0
- data/docusaurus/docs/tutorial-ruby-couchbase-orm/11-encryption.md +136 -48
- data/lib/couchbase-orm/base.rb +1 -1
- data/lib/couchbase-orm/types/nested.rb +17 -2
- data/lib/couchbase-orm/version.rb +1 -1
- data/spec/type_nested_spec.rb +60 -0
- metadata +15 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 8c4e1b3c58f8a68383f5e24c548e93d5a7d8ed5fd178ec932f856e64486dbe44
|
|
4
|
+
data.tar.gz: 2bc1a8a24e83a0267a114995587254e760ccc81f779382d40a7415cf832f4b6b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0164b5c46981c5303fcc7e5820918cafb073d788027aa7e5492d321073cdd1d17df8a6491ba6f1fb91ed6f2ba28ed1f03a3d7ac62da3e4905034c2b1950ce10b
|
|
7
|
+
data.tar.gz: bf5feabaaa6c4c8bec79f5ef1906519ee9da5861ecce359d25055ca806fcbcd781f6c291a98393a79ef4a17f0a85e0000536abaed6185dfcbefaab82d62d6c9d
|
data/README.md
CHANGED
|
@@ -113,8 +113,11 @@ The following types have been tested :
|
|
|
113
113
|
- :datetime (stored as iso8601, use precision: n to store more decimal precision)
|
|
114
114
|
- :timestamp (stored as integer)
|
|
115
115
|
- :encrypted
|
|
116
|
-
-
|
|
117
|
-
-
|
|
116
|
+
- Provides storage format compatible with Couchbase Lite field-level encryption
|
|
117
|
+
- See <https://docs.couchbase.com/couchbase-lite/current/c/field-level-encryption.html>
|
|
118
|
+
- **Important**: CouchbaseOrm does not perform encryption/decryption - your application must encrypt data before storing it
|
|
119
|
+
- Values must be Base64-encoded strings containing pre-encrypted ciphertext
|
|
120
|
+
- See the [encryption documentation](https://couchbase-ruby-orm.com/docs/tutorial-ruby-couchbase-orm/encryption) for details
|
|
118
121
|
- :array (see below)
|
|
119
122
|
- :nested (see below)
|
|
120
123
|
|
data/couchbase-orm.gemspec
CHANGED
|
@@ -23,6 +23,7 @@ Gem::Specification.new do |gem|
|
|
|
23
23
|
gem.add_runtime_dependency 'couchbase', '>= 3.4.2'
|
|
24
24
|
gem.add_runtime_dependency 'radix', '~> 2.2' # converting numbers to and from any base
|
|
25
25
|
gem.add_runtime_dependency 'json-schema', '>= 3' # validating JSON against a schema
|
|
26
|
+
gem.add_runtime_dependency 'logger', '~> 1.6' # Required from Ruby 3.5.0+
|
|
26
27
|
|
|
27
28
|
gem.add_development_dependency 'rake', '~> 12.2'
|
|
28
29
|
gem.add_development_dependency 'rspec', '~> 3.7'
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Encryption
|
|
2
2
|
|
|
3
|
-
CouchbaseOrm provides built-in support for
|
|
3
|
+
CouchbaseOrm provides built-in support for storing encrypted data in your Couchbase documents using a structured format. The `:encrypted` type provides a standardized storage format compatible with Couchbase Lite's field-level encryption, but **does not perform encryption/decryption itself**. Your application is responsible for encrypting data before storing it and decrypting it after retrieval.
|
|
4
4
|
|
|
5
5
|
## 11.1. Encrypted Attributes
|
|
6
6
|
|
|
7
7
|
To mark an attribute as encrypted, you can use the `:encrypted` type when defining the attribute in your model.
|
|
8
8
|
|
|
9
9
|
```ruby
|
|
10
|
-
# Define the Bank model with
|
|
10
|
+
# Define the Bank model with encrypted attributes
|
|
11
11
|
class Bank < CouchbaseOrm::Base
|
|
12
12
|
attribute :name, :string
|
|
13
13
|
attribute :account_number, :encrypted
|
|
@@ -15,7 +15,7 @@ class Bank < CouchbaseOrm::Base
|
|
|
15
15
|
end
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
In this example, the `account_number` and `routing_number` attributes are marked as encrypted.
|
|
18
|
+
In this example, the `account_number` and `routing_number` attributes are marked as encrypted. The `alg` option specifies the encryption algorithm identifier that will be stored in the document metadata (default is `"CB_MOBILE_CUSTOM"`). This identifier is for documentation purposes and Couchbase Lite compatibility - CouchbaseOrm does not use it for actual encryption.
|
|
19
19
|
|
|
20
20
|
```plaintext
|
|
21
21
|
{
|
|
@@ -32,83 +32,171 @@ In this example, the `account_number` and `routing_number` attributes are marked
|
|
|
32
32
|
}
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
When a document is saved, CouchbaseOrm stores
|
|
35
|
+
When a document is saved, CouchbaseOrm stores encrypted attributes in the document with a prefix of `encrypted$`. The values are stored as JSON objects containing the encryption algorithm identifier (`alg`) and the ciphertext (`ciphertext`).
|
|
36
36
|
|
|
37
|
-
You
|
|
37
|
+
**Important**: You must provide **pre-encrypted** values to encrypted attributes. CouchbaseOrm stores these values as-is in the `ciphertext` field without performing any encryption.
|
|
38
38
|
|
|
39
39
|
```ruby
|
|
40
|
-
|
|
40
|
+
# You must encrypt the data BEFORE assigning it to the attribute
|
|
41
|
+
require 'base64'
|
|
42
|
+
|
|
43
|
+
# Assuming you have an encryption method (e.g., AES, Tanker, etc.)
|
|
44
|
+
encrypted_account = MyEncryptor.encrypt('123456789')
|
|
45
|
+
encrypted_routing = MyEncryptor.encrypt('987654321')
|
|
46
|
+
|
|
47
|
+
# Values must be Base64-encoded strings
|
|
48
|
+
bank = Bank.new(
|
|
49
|
+
name: 'My Bank',
|
|
50
|
+
account_number: Base64.strict_encode64(encrypted_account),
|
|
51
|
+
routing_number: Base64.strict_encode64(encrypted_routing)
|
|
52
|
+
)
|
|
41
53
|
```
|
|
42
54
|
|
|
43
|
-
|
|
55
|
+
## 11.2. Complete Example with Encryption
|
|
44
56
|
|
|
45
|
-
|
|
57
|
+
Here's a complete example showing how to handle encryption in your application:
|
|
46
58
|
|
|
47
59
|
```ruby
|
|
48
60
|
require 'base64'
|
|
49
|
-
require '
|
|
61
|
+
require 'openssl'
|
|
62
|
+
|
|
63
|
+
# Example encryption helper (you should use a proper encryption library)
|
|
64
|
+
class SimpleEncryptor
|
|
65
|
+
def self.encrypt(plaintext)
|
|
66
|
+
# This is a simplified example - use a proper encryption library in production
|
|
67
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
68
|
+
cipher.encrypt
|
|
69
|
+
cipher.key = ENV['ENCRYPTION_KEY'] # Store securely, never commit to git
|
|
70
|
+
cipher.iv = iv = cipher.random_iv
|
|
71
|
+
|
|
72
|
+
encrypted = cipher.update(plaintext) + cipher.final
|
|
73
|
+
# Prepend IV for decryption (in real implementation, handle this properly)
|
|
74
|
+
iv + encrypted
|
|
75
|
+
end
|
|
50
76
|
|
|
51
|
-
|
|
77
|
+
def self.decrypt(ciphertext_with_iv)
|
|
78
|
+
cipher = OpenSSL::Cipher.new('AES-256-CBC')
|
|
79
|
+
cipher.decrypt
|
|
80
|
+
cipher.key = ENV['ENCRYPTION_KEY']
|
|
52
81
|
|
|
53
|
-
#
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
json_attrs = JSON.parse(bank.to_json)
|
|
60
|
-
json_attrs.each do |key, value|
|
|
61
|
-
puts "#{key}: #{value}"
|
|
62
|
-
end
|
|
63
|
-
bank.as_json.each do |key, value|
|
|
64
|
-
puts "#{key}: #{value}"
|
|
82
|
+
# Extract IV and ciphertext
|
|
83
|
+
iv = ciphertext_with_iv[0..15]
|
|
84
|
+
ciphertext = ciphertext_with_iv[16..]
|
|
85
|
+
|
|
86
|
+
cipher.iv = iv
|
|
87
|
+
cipher.update(ciphertext) + cipher.final
|
|
65
88
|
end
|
|
66
89
|
end
|
|
67
90
|
|
|
68
|
-
# Create a
|
|
91
|
+
# Create a bank record with encrypted attributes
|
|
92
|
+
plaintext_account = "123456789"
|
|
93
|
+
plaintext_routing = "987654321"
|
|
94
|
+
|
|
95
|
+
# 1. Encrypt the sensitive data
|
|
96
|
+
encrypted_account = SimpleEncryptor.encrypt(plaintext_account)
|
|
97
|
+
encrypted_routing = SimpleEncryptor.encrypt(plaintext_routing)
|
|
98
|
+
|
|
99
|
+
# 2. Encode as Base64 for storage
|
|
69
100
|
bank = Bank.new(
|
|
70
101
|
name: "Test Bank",
|
|
71
|
-
account_number: Base64.strict_encode64(
|
|
72
|
-
routing_number: Base64.strict_encode64(
|
|
102
|
+
account_number: Base64.strict_encode64(encrypted_account),
|
|
103
|
+
routing_number: Base64.strict_encode64(encrypted_routing)
|
|
73
104
|
)
|
|
74
105
|
|
|
75
|
-
#
|
|
76
|
-
expect_serialized_attributes(bank)
|
|
77
|
-
|
|
78
|
-
# Save the bank record to Couchbase
|
|
106
|
+
# 3. Save to Couchbase
|
|
79
107
|
bank.save!
|
|
80
108
|
|
|
81
|
-
#
|
|
82
|
-
bank.
|
|
109
|
+
# 4. Retrieve and decrypt
|
|
110
|
+
found_bank = Bank.find(bank.id)
|
|
83
111
|
|
|
84
|
-
#
|
|
85
|
-
|
|
112
|
+
# 5. Decode Base64 and decrypt
|
|
113
|
+
account_encrypted = Base64.strict_decode64(found_bank.account_number)
|
|
114
|
+
routing_encrypted = Base64.strict_decode64(found_bank.routing_number)
|
|
86
115
|
|
|
87
|
-
|
|
88
|
-
|
|
116
|
+
decrypted_account = SimpleEncryptor.decrypt(account_encrypted)
|
|
117
|
+
decrypted_routing = SimpleEncryptor.decrypt(routing_encrypted)
|
|
89
118
|
|
|
90
|
-
|
|
91
|
-
|
|
119
|
+
puts "Decrypted account: #{decrypted_account}" # => "123456789"
|
|
120
|
+
puts "Decrypted routing: #{decrypted_routing}" # => "987654321"
|
|
92
121
|
```
|
|
93
122
|
|
|
94
|
-
## 11.3.
|
|
123
|
+
## 11.3. Storage Format
|
|
124
|
+
|
|
125
|
+
CouchbaseOrm handles the storage format for encrypted attributes but does not perform encryption/decryption. Here's what happens:
|
|
95
126
|
|
|
96
|
-
When
|
|
127
|
+
**When saving:**
|
|
128
|
+
1. You assign a Base64-encoded ciphertext to the encrypted attribute
|
|
129
|
+
2. CouchbaseOrm wraps it in the `encrypted$` format with `alg` and `ciphertext` fields
|
|
130
|
+
3. The document is stored in Couchbase with this structure
|
|
97
131
|
|
|
98
|
-
When
|
|
132
|
+
**When loading:**
|
|
133
|
+
1. CouchbaseOrm reads the document from Couchbase
|
|
134
|
+
2. It unwraps the `encrypted$` format and extracts the `ciphertext` value
|
|
135
|
+
3. The Base64-encoded ciphertext is assigned to the attribute
|
|
136
|
+
4. Your application must decode and decrypt the value
|
|
99
137
|
|
|
100
|
-
|
|
138
|
+
**Key Points:**
|
|
139
|
+
- CouchbaseOrm does **not** require or use any encryption key
|
|
140
|
+
- The `alg` field is purely informational (for compatibility with Couchbase Lite)
|
|
141
|
+
- All actual encryption/decryption is your application's responsibility
|
|
142
|
+
- Values must be valid Base64-encoded strings
|
|
101
143
|
|
|
102
144
|
## 11.4. Considerations and Best Practices
|
|
103
145
|
|
|
104
|
-
When using
|
|
146
|
+
When using encrypted attributes in CouchbaseOrm, consider the following best practices:
|
|
147
|
+
|
|
148
|
+
### Security
|
|
149
|
+
- **Encryption is your responsibility**: CouchbaseOrm only provides the storage format. Choose a robust encryption library (e.g., `rbnacl`, `openssl`, or a service like AWS KMS)
|
|
150
|
+
- **Key management**: Store encryption keys securely using environment variables, secret managers (AWS Secrets Manager, HashiCorp Vault), or key management services
|
|
151
|
+
- **Never commit keys**: Keep encryption keys out of version control systems
|
|
152
|
+
- **Key rotation**: Implement a key rotation strategy and maintain the ability to decrypt data encrypted with old keys
|
|
153
|
+
- **Use authenticated encryption**: Prefer AEAD modes (like AES-GCM) that provide both confidentiality and integrity
|
|
154
|
+
|
|
155
|
+
### Performance and Querying
|
|
156
|
+
- **Cannot query encrypted fields**: Encrypted attributes cannot be used in WHERE clauses or indexed effectively
|
|
157
|
+
- **Consider searchable encryption**: If you need to search encrypted data, investigate specialized solutions like searchable encryption schemes or external encrypted search indexes
|
|
158
|
+
- **Selective encryption**: Only encrypt truly sensitive fields to minimize performance overhead
|
|
159
|
+
|
|
160
|
+
### Implementation Patterns
|
|
161
|
+
- **Wrap in accessors**: Create getter/setter methods that automatically handle encryption/decryption:
|
|
162
|
+
```ruby
|
|
163
|
+
class Bank < CouchbaseOrm::Base
|
|
164
|
+
attribute :account_number, :encrypted
|
|
165
|
+
|
|
166
|
+
def account_number=(plaintext)
|
|
167
|
+
encrypted = MyEncryptor.encrypt(plaintext)
|
|
168
|
+
super(Base64.strict_encode64(encrypted))
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def account_number
|
|
172
|
+
encrypted = Base64.strict_decode64(super)
|
|
173
|
+
MyEncryptor.decrypt(encrypted)
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
- **Separate concerns**: Consider using a concern or module to encapsulate encryption logic:
|
|
179
|
+
```ruby
|
|
180
|
+
module EncryptedAttributes
|
|
181
|
+
def encrypted_attribute(name)
|
|
182
|
+
define_method("#{name}=") do |plaintext|
|
|
183
|
+
encrypted = MyEncryptor.encrypt(plaintext)
|
|
184
|
+
super(Base64.strict_encode64(encrypted))
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
define_method(name) do
|
|
188
|
+
encrypted = Base64.strict_decode64(super())
|
|
189
|
+
MyEncryptor.decrypt(encrypted)
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
```
|
|
105
194
|
|
|
106
|
-
|
|
107
|
-
-
|
|
108
|
-
-
|
|
109
|
-
-
|
|
110
|
-
- Ensure that the encryption key is properly rotated and managed. If the encryption key is compromised, you should generate a new key and re-encrypt the affected data.
|
|
195
|
+
### Compatibility
|
|
196
|
+
- The `encrypted$` format is compatible with Couchbase Lite's field-level encryption
|
|
197
|
+
- The `alg` field helps document which encryption algorithm was used, aiding in key rotation and auditing
|
|
198
|
+
- Ensure your encryption implementation is compatible across all platforms that access the data (web, mobile, etc.)
|
|
111
199
|
|
|
112
|
-
Encryption is a powerful tool for protecting sensitive data, but it should be used judiciously.
|
|
200
|
+
Encryption is a powerful tool for protecting sensitive data, but it should be used judiciously. Focus on encrypting the most sensitive and confidential data while balancing the trade-offs between security, performance, and functionality.
|
|
113
201
|
|
|
114
202
|
In the next section, we'll explore logging in CouchbaseOrm and how you can configure and customize logging to monitor and debug your application.
|
data/lib/couchbase-orm/base.rb
CHANGED
|
@@ -46,6 +46,7 @@ module CouchbaseOrm
|
|
|
46
46
|
include Encrypt
|
|
47
47
|
|
|
48
48
|
extend Enum
|
|
49
|
+
extend IgnoredProperties
|
|
49
50
|
|
|
50
51
|
define_model_callbacks :initialize, :only => :after
|
|
51
52
|
|
|
@@ -134,7 +135,6 @@ module CouchbaseOrm
|
|
|
134
135
|
extend EnsureUnique
|
|
135
136
|
extend HasMany
|
|
136
137
|
extend Index
|
|
137
|
-
extend IgnoredProperties
|
|
138
138
|
extend JsonSchema::Validation
|
|
139
139
|
extend PropertiesAlwaysExistsInDocument
|
|
140
140
|
|
|
@@ -26,14 +26,29 @@ module CouchbaseOrm
|
|
|
26
26
|
def cast(value)
|
|
27
27
|
return nil if value.nil?
|
|
28
28
|
return value if value.is_a?(@model_class)
|
|
29
|
-
|
|
29
|
+
|
|
30
|
+
if value.is_a?(Hash)
|
|
31
|
+
# Filter out ignored properties before creating the nested instance
|
|
32
|
+
# Optimization: only call .except if there are properties to ignore
|
|
33
|
+
ignored = @model_class.ignored_properties
|
|
34
|
+
filtered_value = ignored.empty? ? value : value.except(*ignored)
|
|
35
|
+
return @model_class.new(filtered_value)
|
|
36
|
+
end
|
|
30
37
|
|
|
31
38
|
raise ArgumentError, "Nested: #{value.inspect} (#{value.class}) is not supported for cast"
|
|
32
39
|
end
|
|
33
40
|
|
|
34
41
|
def serialize(value)
|
|
35
42
|
return nil if value.nil?
|
|
36
|
-
|
|
43
|
+
|
|
44
|
+
if value.is_a?(Hash)
|
|
45
|
+
# Filter out ignored properties before creating the nested instance
|
|
46
|
+
# Optimization: only call .except if there are properties to ignore
|
|
47
|
+
ignored = @model_class.ignored_properties
|
|
48
|
+
filtered_value = ignored.empty? ? value : value.except(*ignored)
|
|
49
|
+
value = @model_class.new(filtered_value)
|
|
50
|
+
end
|
|
51
|
+
|
|
37
52
|
return value.send(:serialized_attributes) if value.is_a?(@model_class)
|
|
38
53
|
|
|
39
54
|
raise ArgumentError, "Nested: #{value.inspect} (#{value.class}) is not supported for serialization"
|
data/spec/type_nested_spec.rb
CHANGED
|
@@ -188,4 +188,64 @@ describe CouchbaseOrm::Types::Nested do
|
|
|
188
188
|
expect(obj.child.child.errors[:name]).to eq ["can't be blank"]
|
|
189
189
|
end
|
|
190
190
|
end
|
|
191
|
+
|
|
192
|
+
describe "Ignored Properties" do
|
|
193
|
+
class SubTypeWithIgnoredProperties < CouchbaseOrm::NestedDocument
|
|
194
|
+
self.ignored_properties = [:deprecated_property]
|
|
195
|
+
attribute :name, :string
|
|
196
|
+
attribute :value, :string
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
class ParentWithNestedIgnoredProperties < CouchbaseOrm::Base
|
|
200
|
+
self.ignored_properties = [:deprecated_at_root]
|
|
201
|
+
attribute :title, :string
|
|
202
|
+
attribute :nested, :nested, type: SubTypeWithIgnoredProperties
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
it "should ignore deprecated properties in nested documents on reload" do
|
|
206
|
+
# Create and save a parent with nested document
|
|
207
|
+
parent = ParentWithNestedIgnoredProperties.new
|
|
208
|
+
parent.title = "Test Parent"
|
|
209
|
+
parent.nested = SubTypeWithIgnoredProperties.new(name: "Nested", value: "Valid")
|
|
210
|
+
parent.save!
|
|
211
|
+
|
|
212
|
+
# Manually add a deprecated property to the nested document in the database
|
|
213
|
+
doc_id = parent.id
|
|
214
|
+
raw_doc = ParentWithNestedIgnoredProperties.bucket.default_collection.get(doc_id).content
|
|
215
|
+
raw_doc["nested"]["deprecated_property"] = "This should be ignored"
|
|
216
|
+
ParentWithNestedIgnoredProperties.bucket.default_collection.replace(doc_id, raw_doc)
|
|
217
|
+
|
|
218
|
+
# Reload the parent
|
|
219
|
+
parent.reload
|
|
220
|
+
|
|
221
|
+
# The deprecated property should NOT be present in the nested document
|
|
222
|
+
expect(parent.nested.attributes.keys).not_to include("deprecated_property")
|
|
223
|
+
expect(parent.nested.name).to eq("Nested")
|
|
224
|
+
expect(parent.nested.value).to eq("Valid")
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
it "should ignore deprecated properties in deeply nested documents" do
|
|
228
|
+
# Create a parent with nested documents that have a child
|
|
229
|
+
parent = ParentWithNestedIgnoredProperties.new
|
|
230
|
+
parent.title = "Test Parent"
|
|
231
|
+
parent.nested = SubTypeWithIgnoredProperties.new(name: "Parent Nested", value: "Parent Value")
|
|
232
|
+
parent.save!
|
|
233
|
+
|
|
234
|
+
# Manually add deprecated properties at multiple levels
|
|
235
|
+
doc_id = parent.id
|
|
236
|
+
raw_doc = ParentWithNestedIgnoredProperties.bucket.default_collection.get(doc_id).content
|
|
237
|
+
raw_doc["deprecated_at_root"] = "Should be ignored at root level"
|
|
238
|
+
raw_doc["nested"]["deprecated_property"] = "Should be ignored in nested"
|
|
239
|
+
ParentWithNestedIgnoredProperties.bucket.default_collection.replace(doc_id, raw_doc)
|
|
240
|
+
|
|
241
|
+
# Reload the parent
|
|
242
|
+
parent.reload
|
|
243
|
+
|
|
244
|
+
# Deprecated properties should not be present at any level
|
|
245
|
+
expect(parent.attributes.keys).not_to include("deprecated_at_root")
|
|
246
|
+
expect(parent.nested.attributes.keys).not_to include("deprecated_property")
|
|
247
|
+
expect(parent.nested.name).to eq("Parent Nested")
|
|
248
|
+
expect(parent.nested.value).to eq("Parent Value")
|
|
249
|
+
end
|
|
250
|
+
end
|
|
191
251
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: couchbase-orm
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.0.
|
|
4
|
+
version: 2.0.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Stephen von Takach
|
|
@@ -68,6 +68,20 @@ dependencies:
|
|
|
68
68
|
- - ">="
|
|
69
69
|
- !ruby/object:Gem::Version
|
|
70
70
|
version: '3'
|
|
71
|
+
- !ruby/object:Gem::Dependency
|
|
72
|
+
name: logger
|
|
73
|
+
requirement: !ruby/object:Gem::Requirement
|
|
74
|
+
requirements:
|
|
75
|
+
- - "~>"
|
|
76
|
+
- !ruby/object:Gem::Version
|
|
77
|
+
version: '1.6'
|
|
78
|
+
type: :runtime
|
|
79
|
+
prerelease: false
|
|
80
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
81
|
+
requirements:
|
|
82
|
+
- - "~>"
|
|
83
|
+
- !ruby/object:Gem::Version
|
|
84
|
+
version: '1.6'
|
|
71
85
|
- !ruby/object:Gem::Dependency
|
|
72
86
|
name: rake
|
|
73
87
|
requirement: !ruby/object:Gem::Requirement
|