aboutsummaryrefslogtreecommitdiffstats
path: root/railties/test/commands/credentials_test.rb
blob: 3dec6fbe104a4cb35f487d721be0fccd6fcf8844 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
# frozen_string_literal: true

require "isolation/abstract_unit"
require "env_helpers"
require "rails/command"
require "rails/commands/credentials/credentials_command"
require "fileutils"
require "tempfile"

class Rails::Command::CredentialsCommandTest < ActiveSupport::TestCase
  include ActiveSupport::Testing::Isolation, EnvHelpers

  setup { build_app }

  teardown { teardown_app }

  test "edit without editor gives hint" do
    run_edit_command(editor: "").tap do |output|
      assert_match "No $EDITOR to open file in", output
      assert_match "rails credentials:edit", output
    end
  end

  test "edit credentials" do
    # Run twice to ensure credentials can be reread after first edit pass.
    2.times do
      assert_match(/access_key_id: 123/, run_edit_command)
    end
  end

  test "edit command does not add master key to gitignore when already exist" do
    run_edit_command

    Dir.chdir(app_path) do
      gitignore = File.read(".gitignore")
      assert_equal 1, gitignore.scan(%r|config/master\.key|).length
    end
  end

  test "edit command does not overwrite by default if credentials already exists" do
    run_edit_command(editor: "eval echo api_key: abc >")
    assert_match(/api_key: abc/, run_show_command)

    run_edit_command
    assert_match(/api_key: abc/, run_show_command)
  end

  test "edit command does not add master key when `RAILS_MASTER_KEY` env specified" do
    Dir.chdir(app_path) do
      key = IO.binread("config/master.key").strip
      FileUtils.rm("config/master.key")

      switch_env("RAILS_MASTER_KEY", key) do
        assert_match(/access_key_id: 123/, run_edit_command)
        assert_not File.exist?("config/master.key")
      end
    end
  end

  test "edit command modifies file specified by environment option" do
    assert_match(/access_key_id: 123/, run_edit_command(environment: "production"))
    Dir.chdir(app_path) do
      assert File.exist?("config/credentials/production.key")
      assert File.exist?("config/credentials/production.yml.enc")
    end
  end

  test "edit command properly expands environment option" do
    assert_match(/access_key_id: 123/, run_edit_command(environment: "prod"))
    Dir.chdir(app_path) do
      assert File.exist?("config/credentials/production.key")
      assert File.exist?("config/credentials/production.yml.enc")
    end
  end

  test "edit command does not raise when an initializer tries to access non-existent credentials" do
    app_file "config/initializers/raise_when_loaded.rb", <<-RUBY
      Rails.application.credentials.missing_key!
    RUBY

    assert_match(/access_key_id: 123/, run_edit_command(environment: "qa"))
  end

  test "edit command generates template file when the file does not exist" do
    FileUtils.rm("#{app_path}/config/credentials.yml.enc")
    run_edit_command

    output = run_show_command
    assert_match(/access_key_id: 123/, output)
    assert_match(/secret_key_base/, output)
  end

  test "edit ask the user to opt in to pretty credentials" do
    assert_match(/Would you like to make the credentials diff from git/, run_edit_command)
  end

  test "edit doesn't ask the user to opt in to pretty credentials when alreasy asked" do
    app_file("tmp/rails_pretty_credentials", "")

    assert_no_match(/Would you like to make the credentials diff from git/, run_edit_command)
  end

  test "edit doesn't ask the user to opt in when user already opted in" do
    content = <<~EOM
      [diff "rails_credentials"]
          textconv = bin/rails credentials:show
    EOM
    app_file(".git/config", content)

    assert_no_match(/Would you like to make the credentials diff from git/, run_edit_command)
  end

  test "edit ask the user to opt in to pretty credentials, user accepts" do
    file = Tempfile.open("credentials_test")
    file.write("y")
    file.rewind

    run_edit_command(stdin: file.path)

    git_attributes = app_path(".gitattributes")
    expected = <<~EOM
      config/credentials/*.yml.enc diff=rails_credentials
      config/credentials.yml.enc diff=rails_credentials
    EOM
    assert(File.exist?(git_attributes))
    assert_equal(expected, File.read(git_attributes))
    Dir.chdir(app_path) do
      assert_equal("bin/rails credentials:show\n", `git config --get 'diff.rails_credentials.textconv'`)
    end
  ensure
    file.close!
  end

  test "edit ask the user to opt in to pretty credentials, user refuses" do
    file = Tempfile.open("credentials_test")
    file.write("n")
    file.rewind

    run_edit_command(stdin: file.path)

    git_attributes = app_path(".gitattributes")
    assert_not(File.exist?(git_attributes))
  ensure
    file.close!
  end

  test "show credentials" do
    assert_match(/access_key_id: 123/, run_show_command)
  end

  test "show command when argument is provided (from git diff left file)" do
    run_edit_command(environment: "development")

    assert_match(/access_key_id: 123/, run_show_command("config/credentials/development.yml.enc"))
  end

  test "show command when argument is provided (from git diff right file)" do
    run_edit_command(environment: "development")

    dir = Dir.mktmpdir
    file_path = File.join(dir, "KnAM4a_development.yml.enc")
    file_content = File.read(app_path("config", "credentials", "development.yml.enc"))
    File.write(file_path, file_content)

    assert_match(/access_key_id: 123/, run_show_command(file_path))
  ensure
    FileUtils.rm_rf(dir)
  end

  test "show command when argument is provided (git diff) and filename is the master credentials" do
    assert_match(/access_key_id: 123/, run_show_command("config/credentials.yml.enc"))
  end

  test "show command when argument is provided (git diff) and master key is not available" do
    remove_file "config/master.key"

    raw_content = File.read(app_path("config", "credentials.yml.enc"))
    assert_match(raw_content, run_show_command("config/credentials.yml.enc"))
  end

  test "show command when argument is provided (git diff) return the raw encrypted content in an error occurs" do
    run_edit_command(environment: "development")

    dir = Dir.mktmpdir
    file_path = File.join(dir, "20190807development.yml.enc")
    file_content = File.read(app_path("config", "credentials", "development.yml.enc"))
    File.write(file_path, file_content)

    assert_match(file_content, run_show_command(file_path))
  ensure
    FileUtils.rm_rf(dir)
  end

  test "show command raises error when require_master_key is specified and key does not exist" do
    remove_file "config/master.key"
    add_to_config "config.require_master_key = true"

    assert_match(/Missing encryption key to decrypt file with/, run_show_command(allow_failure: true))
  end

  test "show command does not raise error when require_master_key is false and master key does not exist" do
    remove_file "config/master.key"
    add_to_config "config.require_master_key = false"

    assert_match(/Missing 'config\/master\.key' to decrypt credentials/, run_show_command)
  end

  test "show command displays content specified by environment option" do
    run_edit_command(environment: "production")

    assert_match(/access_key_id: 123/, run_show_command(environment: "production"))
  end

  test "show command properly expands environment option" do
    run_edit_command(environment: "production")

    output = run_show_command(environment: "prod")
    assert_match(/access_key_id: 123/, output)
    assert_no_match(/secret_key_base/, output)
  end

  private
    def run_edit_command(editor: "cat", environment: nil, **options)
      switch_env("EDITOR", editor) do
        args = environment ? ["--environment", environment] : []
        rails "credentials:edit", args, **options
      end
    end

    def run_show_command(path = nil, environment: nil, **options)
      args = environment ? ["--environment", environment] : []
      args.unshift(path)
      rails "credentials:show", args, **options
    end
end