summaryrefslogtreecommitdiff
path: root/spec/lib/gitlab/otp_key_rotator_spec.rb
blob: f5a567d5ea03c6a766334d7a8226e0ea2964ef72 (plain)
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
# frozen_string_literal: true

require 'spec_helper'

describe Gitlab::OtpKeyRotator do
  let(:file) { Tempfile.new("otp-key-rotator-test") }
  let(:filename) { file.path }
  let(:old_key) { Gitlab::Application.secrets.otp_key_base }
  let(:new_key) { "00" * 32 }
  let!(:users) { create_list(:user, 5, :two_factor) }

  after do
    file.close
    file.unlink
  end

  def data
    CSV.read(filename)
  end

  def build_row(user, applied = false)
    [user.id.to_s, encrypt_otp(user, old_key), encrypt_otp(user, new_key)]
  end

  def encrypt_otp(user, key)
    opts = {
      value: user.otp_secret,
      iv: user.encrypted_otp_secret_iv.unpack("m").join,
      salt: user.encrypted_otp_secret_salt.unpack("m").join,
      algorithm: 'aes-256-cbc',
      insecure_mode: true,
      key: key
    }
    [Encryptor.encrypt(opts)].pack("m")
  end

  subject(:rotator) { described_class.new(filename) }

  describe '#rotate!' do
    subject(:rotation) { rotator.rotate!(old_key: old_key, new_key: new_key) }

    it 'stores the calculated values in a spreadsheet' do
      rotation

      expect(data).to match_array(users.map {|u| build_row(u) })
    end

    context 'new key is too short' do
      let(:new_key) { "00" * 31 }

      it { expect { rotation }.to raise_error(ArgumentError) }
    end

    context 'new key is the same as the old key' do
      let(:new_key) { old_key }

      it { expect { rotation }.to raise_error(ArgumentError) }
    end
  end

  describe '#rollback!' do
    it 'updates rows to the old value' do
      file.puts("#{users[0].id},old,new")
      file.close

      rotator.rollback!

      expect(users[0].reload.encrypted_otp_secret).to eq('old')
      expect(users[1].reload.encrypted_otp_secret).not_to eq('old')
    end
  end
end