diff --git a/app/models/concerns/case_sensitivity.rb b/app/models/concerns/case_sensitivity.rb
new file mode 100644
index 0000000000000000000000000000000000000000..49d350e092bdf73c856ff1931097c673d6c62cb9
--- /dev/null
+++ b/app/models/concerns/case_sensitivity.rb
@@ -0,0 +1,28 @@
+# Concern for querying columns with specific case sensitivity handling.
+module CaseSensitivity
+  extend ActiveSupport::Concern
+
+  module ClassMethods
+    # Queries the given columns regardless of the casing used.
+    #
+    # Unlike other ActiveRecord methods this method only operates on a Hash.
+    def case_insensitive_where(params)
+      criteria   = self
+      cast_lower = Gitlab::Database.postgresql?
+
+      params.each do |key, value|
+        column = ActiveRecord::Base.connection.quote_table_name(key)
+
+        if cast_lower
+          condition = "LOWER(#{column}) = LOWER(:value)"
+        else
+          condition = "#{column} = :value"
+        end
+
+        criteria = criteria.where(condition, value: value)
+      end
+
+      criteria
+    end
+  end
+end
diff --git a/spec/models/concerns/case_sensitivity_spec.rb b/spec/models/concerns/case_sensitivity_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..8b9f50aada78315e0f697e0b188ed4b4a329bc92
--- /dev/null
+++ b/spec/models/concerns/case_sensitivity_spec.rb
@@ -0,0 +1,176 @@
+require 'spec_helper'
+
+describe CaseSensitivity do
+  describe '.case_insensitive_where' do
+    let(:connection) { ActiveRecord::Base.connection }
+    let(:model)      { Class.new { include CaseSensitivity } }
+
+    describe 'using PostgreSQL' do
+      describe 'with a single column/value pair' do
+        it 'returns the criteria for a column and a value' do
+          criteria = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:foo).
+            and_return('"foo"')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
+            and_return(criteria)
+
+          expect(model.case_insensitive_where(foo: 'bar')).to eq(criteria)
+        end
+
+        it 'returns the criteria for a column with a table, and a value' do
+          criteria = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.bar').
+            and_return('"foo"."bar"')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
+            and_return(criteria)
+
+          expect(model.case_insensitive_where('foo.bar': 'bar')).to eq(criteria)
+        end
+      end
+
+      describe 'with multiple column/value pairs' do
+        it 'returns the criteria for a column and a value' do
+          initial = double(:criteria)
+          final   = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:foo).
+            and_return('"foo"')
+
+          expect(connection).to receive(:quote_table_name).
+            with(:bar).
+            and_return('"bar"')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER("foo") = LOWER(:value)}, value: 'bar').
+            and_return(initial)
+
+          expect(initial).to receive(:where).
+            with(%q{LOWER("bar") = LOWER(:value)}, value: 'baz').
+            and_return(final)
+
+          got = model.case_insensitive_where(foo: 'bar', bar: 'baz')
+
+          expect(got).to eq(final)
+        end
+
+        it 'returns the criteria for a column with a table, and a value' do
+          initial = double(:criteria)
+          final   = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.bar').
+            and_return('"foo"."bar"')
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.baz').
+            and_return('"foo"."baz"')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER("foo"."bar") = LOWER(:value)}, value: 'bar').
+            and_return(initial)
+
+          expect(initial).to receive(:where).
+            with(%q{LOWER("foo"."baz") = LOWER(:value)}, value: 'baz').
+            and_return(final)
+
+          got = model.case_insensitive_where('foo.bar': 'bar', 'foo.baz': 'baz')
+
+          expect(got).to eq(final)
+        end
+      end
+    end
+
+    describe 'using MySQL' do
+      describe 'with a single column/value pair' do
+        it 'returns the criteria for a column and a value' do
+          criteria = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:foo).
+            and_return('`foo`')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER(`foo`) = LOWER(:value)}, value: 'bar').
+            and_return(criteria)
+
+          expect(model.case_insensitive_where(foo: 'bar')).to eq(criteria)
+        end
+
+        it 'returns the criteria for a column with a table, and a value' do
+          criteria = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.bar').
+            and_return('`foo`.`bar`')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER(`foo`.`bar`) = LOWER(:value)}, value: 'bar').
+            and_return(criteria)
+
+          expect(model.case_insensitive_where('foo.bar': 'bar')).to eq(criteria)
+        end
+      end
+
+      describe 'with multiple column/value pairs' do
+        it 'returns the criteria for a column and a value' do
+          initial = double(:criteria)
+          final   = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:foo).
+            and_return('`foo`')
+
+          expect(connection).to receive(:quote_table_name).
+            with(:bar).
+            and_return('`bar`')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER(`foo`) = LOWER(:value)}, value: 'bar').
+            and_return(initial)
+
+          expect(initial).to receive(:where).
+            with(%q{LOWER(`bar`) = LOWER(:value)}, value: 'baz').
+            and_return(final)
+
+          got = model.case_insensitive_where(foo: 'bar', bar: 'baz')
+
+          expect(got).to eq(final)
+        end
+
+        it 'returns the criteria for a column with a table, and a value' do
+          initial = double(:criteria)
+          final   = double(:criteria)
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.bar').
+            and_return('`foo`.`bar`')
+
+          expect(connection).to receive(:quote_table_name).
+            with(:'foo.baz').
+            and_return('`foo`.`baz`')
+
+          expect(model).to receive(:where).
+            with(%q{LOWER(`foo`.`bar`) = LOWER(:value)}, value: 'bar').
+            and_return(initial)
+
+          expect(initial).to receive(:where).
+            with(%q{LOWER(`foo`.`baz`) = LOWER(:value)}, value: 'baz').
+            and_return(final)
+
+          got = model.case_insensitive_where('foo.bar': 'bar', 'foo.baz': 'baz')
+
+          expect(got).to eq(final)
+        end
+      end
+    end
+  end
+end