Skip to content

Commit 225cc78

Browse files
Merge pull request rails#45689 from jonathanhefner/encrypted-config-preserve-invalid-yaml
Save encrypted config even if YAML is invalid
2 parents 73909f4 + 772c462 commit 225cc78

File tree

6 files changed

+65
-12
lines changed

6 files changed

+65
-12
lines changed

activesupport/lib/active_support/encrypted_configuration.rb

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@
88

99
module ActiveSupport
1010
class EncryptedConfiguration < EncryptedFile
11+
class InvalidContentError < RuntimeError
12+
def initialize(content_path)
13+
super "Invalid YAML in '#{content_path}'."
14+
end
15+
16+
def message
17+
cause.is_a?(Psych::SyntaxError) ? "#{super}\n\n #{cause.message}" : super
18+
end
19+
end
20+
1121
delegate :[], :fetch, to: :config
1222
delegate_missing_to :options
1323

@@ -23,10 +33,8 @@ def read
2333
""
2434
end
2535

26-
def write(contents)
27-
deserialize(contents)
28-
29-
super
36+
def validate! # :nodoc:
37+
deserialize(read)
3038
end
3139

3240
def config
@@ -48,9 +56,14 @@ def options
4856
@options ||= ActiveSupport::InheritableOptions.new(deep_transform(config))
4957
end
5058

51-
def deserialize(config)
52-
doc = YAML.respond_to?(:unsafe_load) ? YAML.unsafe_load(config) : YAML.load(config)
53-
doc.presence || {}
59+
def deserialize(content)
60+
config = YAML.respond_to?(:unsafe_load) ?
61+
YAML.unsafe_load(content, filename: content_path) :
62+
YAML.load(content, filename: content_path)
63+
64+
config.presence || {}
65+
rescue Psych::SyntaxError
66+
raise InvalidContentError.new(content_path)
5467
end
5568
end
5669
end

activesupport/test/encrypted_configuration_test.rb

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -66,11 +66,19 @@ class EncryptedConfigurationTest < ActiveSupport::TestCase
6666
assert_equal "things", @credentials[:new]
6767
end
6868

69-
test "raise error when writing an invalid format value" do
70-
assert_raise(Psych::SyntaxError) do
71-
@credentials.change do |config_file|
72-
config_file.write "login: *login\n username: dummy"
73-
end
69+
test "raises helpful error when loading invalid content" do
70+
@credentials.write("key: value\nbad")
71+
72+
assert_raise(ActiveSupport::EncryptedConfiguration::InvalidContentError) do
73+
@credentials.config
74+
end
75+
end
76+
77+
test "raises helpful error when validating invalid content" do
78+
@credentials.write("key: value\nbad")
79+
80+
assert_raise(ActiveSupport::EncryptedConfiguration::InvalidContentError) do
81+
@credentials.validate!
7482
end
7583
end
7684

railties/lib/rails/commands/credentials/credentials_command.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ def edit
4141
end
4242

4343
say "File encrypted and saved."
44+
warn_if_credentials_are_invalid
4445
rescue ActiveSupport::MessageEncryptor::InvalidMessage
4546
say "Couldn't decrypt #{content_path}. Perhaps you passed the wrong key?"
4647
end
@@ -103,6 +104,14 @@ def change_credentials_in_system_editor
103104
end
104105
end
105106

107+
def warn_if_credentials_are_invalid
108+
credentials.validate!
109+
rescue ActiveSupport::EncryptedConfiguration::InvalidContentError => error
110+
say "WARNING: #{error.message}", :red
111+
say ""
112+
say "Your application will not be able to load '#{content_path}' until the error has been fixed.", :red
113+
end
114+
106115
def missing_credentials_message
107116
if !credentials.key?
108117
"Missing '#{key_path}' to decrypt credentials. See `bin/rails credentials:help`"

railties/lib/rails/commands/encrypted/encrypted_command.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ def edit(*)
3232
end
3333

3434
say "File encrypted and saved."
35+
warn_if_encrypted_configuration_is_invalid
3536
rescue ActiveSupport::MessageEncryptor::InvalidMessage
3637
say "Couldn't decrypt #{content_path}. Perhaps you passed the wrong key?"
3738
end
@@ -71,6 +72,13 @@ def change_encrypted_configuration_in_system_editor
7172
end
7273
end
7374

75+
def warn_if_encrypted_configuration_is_invalid
76+
encrypted_configuration.validate!
77+
rescue ActiveSupport::EncryptedConfiguration::InvalidContentError => error
78+
say "WARNING: #{error.message}", :red
79+
say ""
80+
say "Your application will not be able to load '#{content_path}' until the error has been fixed.", :red
81+
end
7482

7583
def encryption_key_file_generator
7684
require "rails/generators"

railties/test/commands/credentials_test.rb

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
142142
assert_match %r/provides_secret_key_base: true/, run_edit_command
143143
end
144144

145+
test "edit command preserves user's content even if it contains invalid YAML" do
146+
write_invalid_yaml = %(ruby -e "File.write ARGV[0], 'foo: bar: bad'")
147+
148+
assert_match %r/WARNING: Invalid YAML/, run_edit_command(editor: write_invalid_yaml)
149+
assert_match %r/foo: bar: bad/, run_edit_command
150+
end
151+
145152

146153
test "show credentials" do
147154
assert_match DEFAULT_CREDENTIALS_PATTERN, run_show_command

railties/test/commands/encrypted_test.rb

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,14 @@ class Rails::Command::EncryptedCommandTest < ActiveSupport::TestCase
8282
assert_match(/access_key_id: 123/, run_edit_command(key: "config/tokens.key"))
8383
end
8484

85+
test "edit command preserves user's content even if it contains invalid YAML" do
86+
write_invalid_yaml = %(ruby -e "File.write ARGV[0], 'foo: bar: bad'")
87+
88+
assert_match %r/WARNING: Invalid YAML/, run_edit_command(editor: write_invalid_yaml)
89+
assert_match %r/foo: bar: bad/, run_edit_command
90+
end
91+
92+
8593
test "show encrypted file with custom key" do
8694
run_edit_command(key: "config/tokens.key")
8795

0 commit comments

Comments
 (0)