BigW Consortium Gitlab

20160302152808_remove_wrong_import_url_from_projects.rb 4.92 KB
# 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