summaryrefslogtreecommitdiff
path: root/db/migrate/20160302152808_remove_wrong_import_url_from_projects.rb
blob: 8a351cf27a399193f73864ebe3473d65a0a46f18 (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
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
# Loops through old importer projects that kept a token/password in the import URL
# and encrypts the credentials into a separate field in project#import_data
# #down method not supported
class RemoveWrongImportUrlFromProjects < ActiveRecord::Migration

  class ProjectImportDataFake
    extend AttrEncrypted
    attr_accessor :credentials
    attr_encrypted :credentials, key: Gitlab::Application.secrets.db_key_base, marshal: true, encode: true, :mode => :per_attribute_iv_and_salt
  end

  def up
    say("Encrypting and migrating project import credentials...")

    # This should cover GitHub, GitLab, Bitbucket user:password, token@domain, and other similar URLs.
    in_transaction(message: "Projects including GitHub and GitLab projects with an unsecured URL.") { process_projects_with_wrong_url }

    in_transaction(message: "Migrating Bitbucket credentials...") { process_project(import_type: 'bitbucket', credentials_keys: ['bb_session']) }

    in_transaction(message: "Migrating FogBugz credentials...") { process_project(import_type: 'fogbugz', credentials_keys: ['fb_session']) }

  end

  def process_projects_with_wrong_url
    projects_with_wrong_import_url.each do |project|
      begin
        import_url = Gitlab::ImportUrl.new(project["import_url"])

        update_import_url(import_url, project)
        update_import_data(import_url, project)
      rescue URI::InvalidURIError
        nullify_import_url(project)
      end
    end
  end

  def process_project(import_type:, credentials_keys: [])
    unencrypted_import_data(import_type: import_type).each do |data|
      replace_data_credentials(data, credentials_keys)
    end
  end

  def replace_data_credentials(data, credentials_keys)
    data_hash = JSON.load(data['data']) if data['data']
    unless data_hash.blank?
      encrypted_data_hash = encrypt_data(data_hash, credentials_keys)
      unencrypted_data = data_hash.empty? ? ' NULL ' :  quote(data_hash.to_json)
      update_with_encrypted_data(encrypted_data_hash, data['id'], unencrypted_data)
    end
  end

  def encrypt_data(data_hash, credentials_keys)
    new_data_hash = {}
    credentials_keys.each do |key|
      new_data_hash[key.to_sym] = data_hash.delete(key) if data_hash[key]
    end
    new_data_hash.deep_symbolize_keys
  end

  def in_transaction(message:)
    say_with_time(message) do
      ActiveRecord::Base.transaction do
        yield
      end
    end
  end

  def update_import_data(import_url, project)
    fake_import_data = ProjectImportDataFake.new
    fake_import_data.credentials = import_url.credentials
    import_data_id = project['import_data_id']
    if import_data_id
      execute(update_import_data_sql(import_data_id, fake_import_data))
    else
      execute(insert_import_data_sql(project['id'], fake_import_data))
    end
  end

  def update_with_encrypted_data(data_hash, import_data_id, unencrypted_data = ' NULL ')
    fake_import_data = ProjectImportDataFake.new
    fake_import_data.credentials = data_hash
    execute(update_import_data_sql(import_data_id, fake_import_data, unencrypted_data))
  end

  def update_import_url(import_url, project)
    execute("UPDATE projects SET import_url = #{quote(import_url.sanitized_url)} WHERE id = #{project['id']}")
  end

  def nullify_import_url(project)
    execute("UPDATE projects SET import_url = NULL WHERE id = #{project['id']}")
  end

  def insert_import_data_sql(project_id, fake_import_data)
    %(
      INSERT INTO project_import_data
                  (encrypted_credentials,
                   project_id,
                   encrypted_credentials_iv,
                   encrypted_credentials_salt)
      VALUES      ( #{quote(fake_import_data.encrypted_credentials)},
                    '#{project_id}',
                    #{quote(fake_import_data.encrypted_credentials_iv)},
                    #{quote(fake_import_data.encrypted_credentials_salt)})
    ).squish
  end

  def update_import_data_sql(id, fake_import_data, data = 'NULL')
    %(
      UPDATE project_import_data
      SET    encrypted_credentials = #{quote(fake_import_data.encrypted_credentials)},
             encrypted_credentials_iv = #{quote(fake_import_data.encrypted_credentials_iv)},
             encrypted_credentials_salt = #{quote(fake_import_data.encrypted_credentials_salt)},
             data = #{data}
      WHERE  id = '#{id}'
    ).squish
  end

  #GitHub projects with token, and any user:password@ based URL
  def projects_with_wrong_import_url
    select_all("SELECT p.id, p.import_url, i.id as import_data_id FROM projects p LEFT JOIN project_import_data i on p.id = i.project_id WHERE p.import_url <> '' AND p.import_url LIKE '%//%@%'")
  end

  # All imports with data for import_type
  def unencrypted_import_data(import_type: )
    select_all("SELECT i.id, p.import_url, i.data FROM projects p INNER JOIN project_import_data i ON p.id = i.project_id WHERE p.import_url <> '' AND p.import_type = '#{import_type}' ")
  end

  def quote(value)
    ActiveRecord::Base.connection.quote(value)
  end
end