diff --git a/app/controllers/admin/application_controller.rb b/app/controllers/admin/application_controller.rb
index 9083bfb41cfdb494dcba62e0bb6ec25186dbdfbd..cf795d977ce4ddf7b2d50e3fd98620e91bbd5406 100644
--- a/app/controllers/admin/application_controller.rb
+++ b/app/controllers/admin/application_controller.rb
@@ -6,12 +6,6 @@ class Admin::ApplicationController < ApplicationController
   layout 'admin'
 
   def authenticate_admin!
-    return render_404 unless current_user.is_admin?
-  end
-
-  def authorize_impersonator!
-    if session[:impersonator_id]
-      User.find_by!(username: session[:impersonator_id]).admin?
-    end
+    render_404 unless current_user.is_admin?
   end
 end
diff --git a/app/controllers/admin/impersonation_controller.rb b/app/controllers/admin/impersonation_controller.rb
deleted file mode 100644
index bf98af786158ce693956a442142a899b48d0cdf2..0000000000000000000000000000000000000000
--- a/app/controllers/admin/impersonation_controller.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-class Admin::ImpersonationController < Admin::ApplicationController
-  skip_before_action :authenticate_admin!, only: :destroy
-
-  before_action :user
-  before_action :authorize_impersonator!
-
-  def create
-    if @user.blocked?
-      flash[:alert] = "You cannot impersonate a blocked user"
-
-      redirect_to admin_user_path(@user)
-    else
-      session[:impersonator_id] = current_user.username
-      session[:impersonator_return_to] = admin_user_path(@user)
-
-      warden.set_user(user, scope: 'user')
-
-      flash[:alert] = "You are impersonating #{user.username}."
-
-      redirect_to root_path
-    end
-  end
-
-  def destroy
-    redirect = session[:impersonator_return_to]
-
-    warden.set_user(user, scope: 'user')
-
-    session[:impersonator_return_to] = nil
-    session[:impersonator_id] = nil
-
-    redirect_to redirect || root_path
-  end
-
-  def user
-    @user ||= User.find_by!(username: params[:id] || session[:impersonator_id])
-  end
-end
diff --git a/app/controllers/admin/impersonations_controller.rb b/app/controllers/admin/impersonations_controller.rb
new file mode 100644
index 0000000000000000000000000000000000000000..2db824c87ef823a451df0091f6a514350a73fae5
--- /dev/null
+++ b/app/controllers/admin/impersonations_controller.rb
@@ -0,0 +1,24 @@
+class Admin::ImpersonationsController < Admin::ApplicationController
+  skip_before_action :authenticate_admin!
+  before_action :authenticate_impersonator!
+
+  def destroy
+    original_user = current_user
+
+    warden.set_user(impersonator, scope: :user)
+
+    session[:impersonator_id] = nil
+
+    redirect_to admin_user_path(original_user)
+  end
+
+  private
+
+  def impersonator
+    @impersonator ||= User.find(session[:impersonator_id]) if session[:impersonator_id]
+  end
+
+  def authenticate_impersonator!
+    render_404 unless impersonator && impersonator.is_admin? && !impersonator.blocked?
+  end
+end
diff --git a/app/controllers/admin/users_controller.rb b/app/controllers/admin/users_controller.rb
index 9abf08d0e19a462d5b8f1c5664964ac300f82f40..b8976fa09a9b83a2e6984f0593b03b048a27746e 100644
--- a/app/controllers/admin/users_controller.rb
+++ b/app/controllers/admin/users_controller.rb
@@ -31,6 +31,22 @@ def edit
     user
   end
 
+  def impersonate
+    if user.blocked?
+      flash[:alert] = "You cannot impersonate a blocked user"
+
+      redirect_to admin_user_path(user)
+    else
+      session[:impersonator_id] = current_user.id
+
+      warden.set_user(user, scope: :user)
+
+      flash[:alert] = "You are now impersonating #{user.username}"
+
+      redirect_to root_path
+    end
+  end
+
   def block
     if user.block
       redirect_back_or_admin_user(notice: "Successfully blocked")
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index a41172816b817515d01bc7a3a98af0068ce2c8ba..01cbf91c658b8a9a5d7a2d2652dce5eba14925ef 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -51,7 +51,7 @@ def by_project(current_user, project)
     snippets = project.snippets.fresh
 
     if current_user
-      if project.team.member?(current_user.id)
+      if project.team.member?(current_user.id) || current_user.admin?
         snippets
       else
         snippets.public_and_internal
diff --git a/app/helpers/issues_helper.rb b/app/helpers/issues_helper.rb
index afe1e11a0da6ad5cb9f576cd309e63d9d6bf9fc0..198d39455d7651333992af7cfdcbb6a41ad9b12e 100644
--- a/app/helpers/issues_helper.rb
+++ b/app/helpers/issues_helper.rb
@@ -16,31 +16,49 @@ def unassigned_filter
   def url_for_project_issues(project = @project, options = {})
     return '' if project.nil?
 
-    if options[:only_path]
-      project.issues_tracker.project_path
-    else
-      project.issues_tracker.project_url
-    end
+    url =
+      if options[:only_path]
+        project.issues_tracker.project_path
+      else
+        project.issues_tracker.project_url
+      end
+
+    # Ensure we return a valid URL to prevent possible XSS.
+    URI.parse(url).to_s
+  rescue URI::InvalidURIError
+    ''
   end
 
   def url_for_new_issue(project = @project, options = {})
     return '' if project.nil?
 
-    if options[:only_path]
-      project.issues_tracker.new_issue_path
-    else
-      project.issues_tracker.new_issue_url
-    end
+    url =
+      if options[:only_path]
+        project.issues_tracker.new_issue_path
+      else
+        project.issues_tracker.new_issue_url
+      end
+
+    # Ensure we return a valid URL to prevent possible XSS.
+    URI.parse(url).to_s
+  rescue URI::InvalidURIError
+    ''
   end
 
   def url_for_issue(issue_iid, project = @project, options = {})
     return '' if project.nil?
 
-    if options[:only_path]
-      project.issues_tracker.issue_path(issue_iid)
-    else
-      project.issues_tracker.issue_url(issue_iid)
-    end
+    url =
+      if options[:only_path]
+        project.issues_tracker.issue_path(issue_iid)
+      else
+        project.issues_tracker.issue_url(issue_iid)
+      end
+
+    # Ensure we return a valid URL to prevent possible XSS.
+    URI.parse(url).to_s
+  rescue URI::InvalidURIError
+    ''
   end
 
   def bulk_update_milestone_options
diff --git a/app/models/project_services/buildkite_service.rb b/app/models/project_services/buildkite_service.rb
index 3efbfd2eec3abbd68b318d0a0701485476fdd6de..861cc974ec415ddb28e652f8a8be2f87a19e1f2c 100644
--- a/app/models/project_services/buildkite_service.rb
+++ b/app/models/project_services/buildkite_service.rb
@@ -26,7 +26,7 @@ class BuildkiteService < CiService
 
   prop_accessor :project_url, :token, :enable_ssl_verification
 
-  validates :project_url, presence: true, if: :activated?
+  validates :project_url, presence: true, url: true, if: :activated?
   validates :token, presence: true, if: :activated?
 
   after_save :compose_service_hook, if: :activated?
@@ -91,7 +91,7 @@ def fields
       { type: 'text',
         name: 'project_url',
         placeholder: "#{ENDPOINT}/example/project" },
-      
+
       { type: 'checkbox',
         name: 'enable_ssl_verification',
         title: "Enable SSL verification" }
diff --git a/app/models/project_services/issue_tracker_service.rb b/app/models/project_services/issue_tracker_service.rb
index 25045224ce5ac2c47b15ee17ca45f4ed6ca47f78..c5501e06411bb8ea7d88c52b947694a0bd563875 100644
--- a/app/models/project_services/issue_tracker_service.rb
+++ b/app/models/project_services/issue_tracker_service.rb
@@ -21,7 +21,7 @@
 
 class IssueTrackerService < Service
 
-  validates :project_url, :issues_url, :new_issue_url, presence: true, if: :activated?
+  validates :project_url, :issues_url, :new_issue_url, presence: true, url: true, if: :activated?
 
   default_value_for :category, 'issue_tracker'
 
diff --git a/app/models/project_services/jira_service.rb b/app/models/project_services/jira_service.rb
index 1ed42c4f3e767b36de1c198a3a2bd2753717ad19..b4418ba9284b1a6dc5fd8522f641800d9a254d38 100644
--- a/app/models/project_services/jira_service.rb
+++ b/app/models/project_services/jira_service.rb
@@ -28,6 +28,8 @@ class JiraService < IssueTrackerService
   prop_accessor :username, :password, :api_url, :jira_issue_transition_id,
                 :title, :description, :project_url, :issues_url, :new_issue_url
 
+  validates :api_url, presence: true, url: true, if: :activated?
+
   before_validation :set_api_url, :set_jira_issue_transition_id
 
   before_update :reset_password
diff --git a/app/models/project_services/slack_service.rb b/app/models/project_services/slack_service.rb
index fd65027f08476a6cebd1170a8f4a9f30b2c9b3eb..7092b757549ea93265d6ab5c1601e94048678edb 100644
--- a/app/models/project_services/slack_service.rb
+++ b/app/models/project_services/slack_service.rb
@@ -22,7 +22,7 @@
 class SlackService < Service
   prop_accessor :webhook, :username, :channel
   boolean_accessor :notify_only_broken_builds
-  validates :webhook, presence: true, if: :activated?
+  validates :webhook, presence: true, url: true, if: :activated?
 
   def initialize_properties
     if properties.nil?
diff --git a/app/services/merge_requests/build_service.rb b/app/services/merge_requests/build_service.rb
index fa34753c4fd2d3e4f5b4c2fe0a98d30438a1df9f..3544752d47a77441671542d2b51f684bdbb67938 100644
--- a/app/services/merge_requests/build_service.rb
+++ b/app/services/merge_requests/build_service.rb
@@ -7,6 +7,9 @@ def execute
       merge_request.can_be_created = false
       merge_request.compare_commits = []
       merge_request.source_project = project unless merge_request.source_project
+
+      merge_request.target_project = nil unless can?(current_user, :read_project, merge_request.target_project)
+
       merge_request.target_project ||= (project.forked_from_project || project)
       merge_request.target_branch ||= merge_request.target_project.default_branch
 
diff --git a/app/services/notes/create_service.rb b/app/services/notes/create_service.rb
index 2bb312bb252ca4096d0b9c22180bca22fcea42e5..01586994813cca5dcca3fe8f628c058d256425dd 100644
--- a/app/services/notes/create_service.rb
+++ b/app/services/notes/create_service.rb
@@ -5,6 +5,8 @@ def execute
       note.author = current_user
       note.system = false
 
+      return unless valid_project?(note)
+
       if note.save
         # Finish the harder work in the background
         NewNoteWorker.perform_in(2.seconds, note.id, params)
@@ -13,5 +15,14 @@ def execute
 
       note
     end
+
+    private
+
+    def valid_project?(note)
+      return false unless project
+      return true if note.for_commit?
+
+      note.noteable.try(:project) == project
+    end
   end
 end
diff --git a/app/views/layouts/header/_default.html.haml b/app/views/layouts/header/_default.html.haml
index 3beb8ff7c0daa7a49386a1d14220b1f7d267de08..cde9e1b918b29bd0e5b2c27c13ae1a3080009102 100644
--- a/app/views/layouts/header/_default.html.haml
+++ b/app/views/layouts/header/_default.html.haml
@@ -15,7 +15,7 @@
           - if current_user
             - if session[:impersonator_id]
               %li.impersonation
-                = link_to stop_impersonation_admin_users_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
+                = link_to admin_impersonation_path, method: :delete, title: 'Stop Impersonation', data: { toggle: 'tooltip', placement: 'bottom', container: 'body' } do
                   = icon('user-secret fw')
             - if current_user.is_admin?
               %li
diff --git a/config/routes.rb b/config/routes.rb
index 5ce1f49ec6ae47e5d7126c9ecfca3869f3b0f76d..dafecc9464894f89e9d7657065613459c8ef4fbe 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -214,8 +214,6 @@
       resources :keys, only: [:show, :destroy]
       resources :identities, except: [:show]
 
-      delete 'stop_impersonation' => 'impersonation#destroy', on: :collection
-
       member do
         get :projects
         get :keys
@@ -225,12 +223,14 @@
         put :unblock
         put :unlock
         put :confirm
-        post 'impersonate' => 'impersonation#create'
+        post :impersonate
         patch :disable_two_factor
         delete 'remove/:email_id', action: 'remove_email', as: 'remove_email'
       end
     end
 
+    resource :impersonation, only: :destroy
+
     resources :abuse_reports, only: [:index, :destroy]
     resources :spam_logs, only: [:index, :destroy]
 
diff --git a/lib/api/milestones.rb b/lib/api/milestones.rb
index 84b4d4cdd6dc2dbfede61517bafa6ea2e18263e3..132043cf3f7d6a917950cd571d5717cbdd00b877 100644
--- a/lib/api/milestones.rb
+++ b/lib/api/milestones.rb
@@ -105,7 +105,15 @@ def filter_milestones_state(milestones, state)
         authorize! :read_milestone, user_project
 
         @milestone = user_project.milestones.find(params[:milestone_id])
-        present paginate(@milestone.issues), with: Entities::Issue, current_user: current_user
+
+        finder_params = {
+          project_id: user_project.id,
+          milestone_title: @milestone.title,
+          state: 'all'
+        }
+
+        issues = IssuesFinder.new(current_user, finder_params).execute
+        present paginate(issues), with: Entities::Issue, current_user: current_user
       end
 
     end
diff --git a/lib/api/project_snippets.rb b/lib/api/project_snippets.rb
index 22ce3c6a0668ebfa228a0558575c657077704b91..ce1bf0d26d2a56ff53d5522b429c52cac8391242 100644
--- a/lib/api/project_snippets.rb
+++ b/lib/api/project_snippets.rb
@@ -11,6 +11,11 @@ def handle_project_member_errors(errors)
           end
           not_found!
         end
+
+        def snippets_for_current_user
+          finder_params = { filter: :by_project, project: user_project }
+          SnippetsFinder.new.execute(current_user, finder_params)
+        end
       end
 
       # Get a project snippets
@@ -20,7 +25,7 @@ def handle_project_member_errors(errors)
       # Example Request:
       #   GET /projects/:id/snippets
       get ":id/snippets" do
-        present paginate(user_project.snippets), with: Entities::ProjectSnippet
+        present paginate(snippets_for_current_user), with: Entities::ProjectSnippet
       end
 
       # Get a project snippet
@@ -31,7 +36,7 @@ def handle_project_member_errors(errors)
       # Example Request:
       #   GET /projects/:id/snippets/:snippet_id
       get ":id/snippets/:snippet_id" do
-        @snippet = user_project.snippets.find(params[:snippet_id])
+        @snippet = snippets_for_current_user.find(params[:snippet_id])
         present @snippet, with: Entities::ProjectSnippet
       end
 
@@ -73,7 +78,7 @@ def handle_project_member_errors(errors)
       # Example Request:
       #   PUT /projects/:id/snippets/:snippet_id
       put ":id/snippets/:snippet_id" do
-        @snippet = user_project.snippets.find(params[:snippet_id])
+        @snippet = snippets_for_current_user.find(params[:snippet_id])
         authorize! :update_project_snippet, @snippet
 
         attrs = attributes_for_keys [:title, :file_name, :visibility_level]
@@ -97,7 +102,7 @@ def handle_project_member_errors(errors)
       #   DELETE /projects/:id/snippets/:snippet_id
       delete ":id/snippets/:snippet_id" do
         begin
-          @snippet = user_project.snippets.find(params[:snippet_id])
+          @snippet = snippets_for_current_user.find(params[:snippet_id])
           authorize! :update_project_snippet, @snippet
           @snippet.destroy
         rescue
@@ -113,7 +118,7 @@ def handle_project_member_errors(errors)
       # Example Request:
       #   GET /projects/:id/snippets/:snippet_id/raw
       get ":id/snippets/:snippet_id/raw" do
-        @snippet = user_project.snippets.find(params[:snippet_id])
+        @snippet = snippets_for_current_user.find(params[:snippet_id])
 
         env['api.format'] = :txt
         content_type 'text/plain'
diff --git a/spec/controllers/admin/impersonation_controller_spec.rb b/spec/controllers/admin/impersonation_controller_spec.rb
deleted file mode 100644
index d7a7ba1c5b6601194dac6a17ca0e7e58e3c40e00..0000000000000000000000000000000000000000
--- a/spec/controllers/admin/impersonation_controller_spec.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-require 'spec_helper'
-
-describe Admin::ImpersonationController do
-  let(:admin) { create(:admin) }
-
-  before do
-    sign_in(admin)
-  end
-
-  describe 'CREATE #impersonation when blocked' do
-    let(:blocked_user) { create(:user, state: :blocked) }
-
-    it 'does not allow impersonation' do
-      post :create, id: blocked_user.username
-
-      expect(flash[:alert]).to eq 'You cannot impersonate a blocked user'
-    end
-  end
-end
diff --git a/spec/controllers/admin/impersonations_controller_spec.rb b/spec/controllers/admin/impersonations_controller_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..eb82476b179b56db1f455015863d52be2a94a76d
--- /dev/null
+++ b/spec/controllers/admin/impersonations_controller_spec.rb
@@ -0,0 +1,95 @@
+require 'spec_helper'
+
+describe Admin::ImpersonationsController do
+  let(:impersonator) { create(:admin) }
+  let(:user) { create(:user) }
+
+  describe "DELETE destroy" do
+    context "when not signed in" do
+      it "redirects to the sign in page" do
+        delete :destroy
+
+        expect(response).to redirect_to(new_user_session_path)
+      end
+    end
+
+    context "when signed in" do
+      before do
+        sign_in(user)
+      end
+
+      context "when not impersonating" do
+        it "responds with status 404" do
+          delete :destroy
+
+          expect(response.status).to eq(404)
+        end
+
+        it "doesn't sign us in" do
+          delete :destroy
+
+          expect(warden.user).to eq(user)
+        end
+      end
+
+      context "when impersonating" do
+        before do
+          session[:impersonator_id] = impersonator.id
+        end
+
+        context "when the impersonator is not admin (anymore)" do
+          before do
+            impersonator.admin = false
+            impersonator.save
+          end
+
+          it "responds with status 404" do
+            delete :destroy
+
+            expect(response.status).to eq(404)
+          end
+
+          it "doesn't sign us in as the impersonator" do
+            delete :destroy
+
+            expect(warden.user).to eq(user)
+          end
+        end
+
+        context "when the impersonator is admin" do
+          context "when the impersonator is blocked" do
+            before do
+              impersonator.block!
+            end
+
+            it "responds with status 404" do
+              delete :destroy
+
+              expect(response.status).to eq(404)
+            end
+
+            it "doesn't sign us in as the impersonator" do
+              delete :destroy
+
+              expect(warden.user).to eq(user)
+            end
+          end
+
+          context "when the impersonator is not blocked" do
+            it "redirects to the impersonated user's page" do
+              delete :destroy
+
+              expect(response).to redirect_to(admin_user_path(user))
+            end
+
+            it "signs us in as the impersonator" do
+              delete :destroy
+
+              expect(warden.user).to eq(impersonator)
+            end
+          end
+        end
+      end
+    end
+  end
+end
diff --git a/spec/controllers/admin/users_controller_spec.rb b/spec/controllers/admin/users_controller_spec.rb
index 9ef8ba1b09763fac21972a2480d790600c86b46e..ce2a62ae1fd3319d7b03ac62d7c579a1eca07264 100644
--- a/spec/controllers/admin/users_controller_spec.rb
+++ b/spec/controllers/admin/users_controller_spec.rb
@@ -2,9 +2,10 @@
 
 describe Admin::UsersController do
   let(:user) { create(:user) }
+  let(:admin) { create(:admin) }
 
   before do
-    sign_in(create(:admin))
+    sign_in(admin)
   end
 
   describe 'DELETE #user with projects' do
@@ -112,4 +113,50 @@ def go
       patch :disable_two_factor, id: user.to_param
     end
   end
+
+  describe "POST impersonate" do
+    context "when the user is blocked" do
+      before do
+        user.block!
+      end
+
+      it "shows a notice" do
+        post :impersonate, id: user.username
+
+        expect(flash[:alert]).to eq("You cannot impersonate a blocked user")
+      end
+
+      it "doesn't sign us in as the user" do
+        post :impersonate, id: user.username
+
+        expect(warden.user).to eq(admin)
+      end
+    end
+
+    context "when the user is not blocked" do
+      it "stores the impersonator in the session" do
+        post :impersonate, id: user.username
+
+        expect(session[:impersonator_id]).to eq(admin.id)
+      end
+
+      it "signs us in as the user" do
+        post :impersonate, id: user.username
+
+        expect(warden.user).to eq(user)
+      end
+
+      it "redirects to root" do
+        post :impersonate, id: user.username
+
+        expect(response).to redirect_to(root_path)
+      end
+
+      it "shows a notice" do
+        post :impersonate, id: user.username
+
+        expect(flash[:alert]).to eq("You are now impersonating #{user.username}")
+      end
+    end
+  end
 end
diff --git a/spec/features/merge_requests/create_new_mr_spec.rb b/spec/features/merge_requests/create_new_mr_spec.rb
index 00b60bd0e7564551a175c7bc64f908de34ecef67..e296078bad80fb8ed4e85cffe3fe727264a39c2e 100644
--- a/spec/features/merge_requests/create_new_mr_spec.rb
+++ b/spec/features/merge_requests/create_new_mr_spec.rb
@@ -30,4 +30,14 @@
 
     expect(page).to have_content 'git checkout -b orphaned-branch origin/orphaned-branch'
   end
+
+  context 'when target project cannot be viewed by the current user' do
+    it 'does not leak the private project name & namespace' do
+      private_project = create(:project, :private)
+
+      visit new_namespace_project_merge_request_path(project.namespace, project, merge_request: { target_project_id: private_project.id })
+
+      expect(page).not_to have_content private_project.to_reference
+    end
+  end
 end
diff --git a/spec/helpers/issues_helper_spec.rb b/spec/helpers/issues_helper_spec.rb
index 543593cf389cfb2fe71b63a34f278f329cbb1091..bffe2c18b6f90d86d801d3455ae13a8a466ab195 100644
--- a/spec/helpers/issues_helper_spec.rb
+++ b/spec/helpers/issues_helper_spec.rb
@@ -30,6 +30,18 @@
       expect(url_for_project_issues).to eq ""
     end
 
+    it 'returns an empty string if project_url is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.project_url') { 'javascript:alert("foo");' }
+
+      expect(url_for_project_issues(project)).to eq ''
+    end
+
+    it 'returns an empty string if project_path is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.project_path') { 'javascript:alert("foo");' }
+
+      expect(url_for_project_issues(project, only_path: true)).to eq ''
+    end
+
     describe "when external tracker was enabled and then config removed" do
       before do
         @project = ext_project
@@ -68,6 +80,18 @@
       expect(url_for_issue(issue.iid)).to eq ""
     end
 
+    it 'returns an empty string if issue_url is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.issue_url') { 'javascript:alert("foo");' }
+
+      expect(url_for_issue(issue.iid, project)).to eq ''
+    end
+
+    it 'returns an empty string if issue_path is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.issue_path') { 'javascript:alert("foo");' }
+
+      expect(url_for_issue(issue.iid, project, only_path: true)).to eq ''
+    end
+
     describe "when external tracker was enabled and then config removed" do
       before do
         @project = ext_project
@@ -105,6 +129,18 @@
       expect(url_for_new_issue).to eq ""
     end
 
+    it 'returns an empty string if issue_url is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.new_issue_url') { 'javascript:alert("foo");' }
+
+      expect(url_for_new_issue(project)).to eq ''
+    end
+
+    it 'returns an empty string if issue_path is invalid' do
+      expect(project).to receive_message_chain('issues_tracker.new_issue_path') { 'javascript:alert("foo");' }
+
+      expect(url_for_new_issue(project, only_path: true)).to eq ''
+    end
+
     describe "when external tracker was enabled and then config removed" do
       before do
         @project = ext_project
diff --git a/spec/models/project_services/bamboo_service_spec.rb b/spec/models/project_services/bamboo_service_spec.rb
index 31b2c90122d94e772f166505ad802b4ddc6e0fec..e771f35811ec004e3e6e4af8da371addf625cf99 100644
--- a/spec/models/project_services/bamboo_service_spec.rb
+++ b/spec/models/project_services/bamboo_service_spec.rb
@@ -27,86 +27,51 @@
   end
 
   describe 'Validations' do
-    describe '#bamboo_url' do
-      it 'does not validate the presence of bamboo_url if service is not active' do
-        bamboo_service = service
-        bamboo_service.active = false
-
-        expect(bamboo_service).not_to validate_presence_of(:bamboo_url)
-      end
-
-      it 'validates the presence of bamboo_url if service is active' do
-        bamboo_service = service
-        bamboo_service.active = true
-
-        expect(bamboo_service).to validate_presence_of(:bamboo_url)
-      end
-    end
+    subject { service }
 
-    describe '#build_key' do
-      it 'does not validate the presence of build_key if service is not active' do
-        bamboo_service = service
-        bamboo_service.active = false
+    context 'when service is active' do
+      before { subject.active = true }
 
-        expect(bamboo_service).not_to validate_presence_of(:build_key)
-      end
+      it { is_expected.to validate_presence_of(:build_key) }
+      it { is_expected.to validate_presence_of(:bamboo_url) }
+      it_behaves_like 'issue tracker service URL attribute', :bamboo_url
 
-      it 'validates the presence of build_key if service is active' do
-        bamboo_service = service
-        bamboo_service.active = true
+      describe '#username' do
+        it 'does not validate the presence of username if password is nil' do
+          subject.password = nil
 
-        expect(bamboo_service).to validate_presence_of(:build_key)
-      end
-    end
+          expect(subject).not_to validate_presence_of(:username)
+        end
 
-    describe '#username' do
-      it 'does not validate the presence of username if service is not active' do
-        bamboo_service = service
-        bamboo_service.active = false
+        it 'validates the presence of username if password is present' do
+          subject.password = 'secret'
 
-        expect(bamboo_service).not_to validate_presence_of(:username)
+          expect(subject).to validate_presence_of(:username)
+        end
       end
 
-      it 'does not validate the presence of username if username is nil' do
-        bamboo_service = service
-        bamboo_service.active = true
-        bamboo_service.password = nil
+      describe '#password' do
+        it 'does not validate the presence of password if username is nil' do
+          subject.username = nil
 
-        expect(bamboo_service).not_to validate_presence_of(:username)
-      end
+          expect(subject).not_to validate_presence_of(:password)
+        end
 
-      it 'validates the presence of username if service is active and username is present' do
-        bamboo_service = service
-        bamboo_service.active = true
-        bamboo_service.password = 'secret'
+        it 'validates the presence of password if username is present' do
+          subject.username = 'john'
 
-        expect(bamboo_service).to validate_presence_of(:username)
+          expect(subject).to validate_presence_of(:password)
+        end
       end
     end
 
-    describe '#password' do
-      it 'does not validate the presence of password if service is not active' do
-        bamboo_service = service
-        bamboo_service.active = false
-
-        expect(bamboo_service).not_to validate_presence_of(:password)
-      end
-
-      it 'does not validate the presence of password if username is nil' do
-        bamboo_service = service
-        bamboo_service.active = true
-        bamboo_service.username = nil
-
-        expect(bamboo_service).not_to validate_presence_of(:password)
-      end
-
-      it 'validates the presence of password if service is active and username is present' do
-        bamboo_service = service
-        bamboo_service.active = true
-        bamboo_service.username = 'john'
+    context 'when service is inactive' do
+      before { subject.active = false }
 
-        expect(bamboo_service).to validate_presence_of(:password)
-      end
+      it { is_expected.not_to validate_presence_of(:build_key) }
+      it { is_expected.not_to validate_presence_of(:bamboo_url) }
+      it { is_expected.not_to validate_presence_of(:username) }
+      it { is_expected.not_to validate_presence_of(:password) }
     end
   end
 
diff --git a/spec/models/project_services/buildkite_service_spec.rb b/spec/models/project_services/buildkite_service_spec.rb
index 88cd624877a8e18f1e7de718ab9705bd59fd8e5f..60364df20154469aabebe1d5a036330c5a3c9647 100644
--- a/spec/models/project_services/buildkite_service_spec.rb
+++ b/spec/models/project_services/buildkite_service_spec.rb
@@ -26,6 +26,23 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:project_url) }
+      it { is_expected.to validate_presence_of(:token) }
+      it_behaves_like 'issue tracker service URL attribute', :project_url
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:project_url) }
+      it { is_expected.not_to validate_presence_of(:token) }
+    end
+  end
+
   describe 'commits methods' do
     before do
       @project = Project.new
diff --git a/spec/models/project_services/builds_email_service_spec.rb b/spec/models/project_services/builds_email_service_spec.rb
index 7c23c2efccd8e2f97c57c25e750c98db22bad252..236df8f047df335a682329594893cde6110c1311 100644
--- a/spec/models/project_services/builds_email_service_spec.rb
+++ b/spec/models/project_services/builds_email_service_spec.rb
@@ -1,76 +1,71 @@
 require 'spec_helper'
 
 describe BuildsEmailService do
-  let(:build) { create(:ci_build) }
-  let(:data) { Gitlab::BuildDataBuilder.build(build) }
-  let!(:project) { create(:project, :public, ci_id: 1) }
-  let(:service) { described_class.new(project: project, active: true) }
+  let(:data) { Gitlab::BuildDataBuilder.build(create(:ci_build)) }
+
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:recipients) }
+
+      context 'when pusher is added' do
+        before { subject.add_pusher = true }
+
+        it { is_expected.not_to validate_presence_of(:recipients) }
+      end
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:recipients) }
+    end
+  end
 
   describe '#execute' do
     it 'sends email' do
-      service.recipients = 'test@gitlab.com'
+      subject.recipients = 'test@gitlab.com'
       data[:build_status] = 'failed'
+
       expect(BuildEmailWorker).to receive(:perform_async)
-      service.execute(data)
+
+      subject.execute(data)
     end
 
     it 'does not send email with succeeded build and notify_only_broken_builds on' do
-      expect(service).to receive(:notify_only_broken_builds).and_return(true)
+      expect(subject).to receive(:notify_only_broken_builds).and_return(true)
       data[:build_status] = 'success'
+
       expect(BuildEmailWorker).not_to receive(:perform_async)
-      service.execute(data)
+
+      subject.execute(data)
     end
 
     it 'does not send email with failed build and build_allow_failure is true' do
       data[:build_status] = 'failed'
       data[:build_allow_failure] = true
+
       expect(BuildEmailWorker).not_to receive(:perform_async)
-      service.execute(data)
+
+      subject.execute(data)
     end
 
     it 'does not send email with unknown build status' do
       data[:build_status] = 'foo'
-      expect(BuildEmailWorker).not_to receive(:perform_async)
-      service.execute(data)
-    end
 
-    it 'does not send email when recipients list is empty' do
-      service.recipients = ' ,, '
-      data[:build_status] = 'failed'
       expect(BuildEmailWorker).not_to receive(:perform_async)
-      service.execute(data)
-    end
-  end
-
-  describe 'validations' do
-
-    context 'when pusher is not added' do
-      before { service.add_pusher = false }
-
-      it 'does not allow empty recipient input' do
-        service.recipients = ''
-        expect(service.valid?).to be false
-      end
-
-      it 'does allow non-empty recipient input' do
-        service.recipients = 'test@example.com'
-        expect(service.valid?).to be true
-      end
 
+      subject.execute(data)
     end
 
-    context 'when pusher is added' do
-      before { service.add_pusher = true }
+    it 'does not send email when recipients list is empty' do
+      subject.recipients = ' ,, '
+      data[:build_status] = 'failed'
 
-      it 'does allow empty recipient input' do
-        service.recipients = ''
-        expect(service.valid?).to be true
-      end
+      expect(BuildEmailWorker).not_to receive(:perform_async)
 
-      it 'does allow non-empty recipient input' do
-        service.recipients = 'test@example.com'
-        expect(service.valid?).to be true
-      end
+      subject.execute(data)
     end
   end
 end
diff --git a/spec/models/project_services/campfire_service_spec.rb b/spec/models/project_services/campfire_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..3e6da42803b800822d640ae8c7f163fb1a751d5b
--- /dev/null
+++ b/spec/models/project_services/campfire_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CampfireService, models: true do
+  describe 'Associations' do
+    it { is_expected.to belong_to :project }
+    it { is_expected.to have_one :service_hook }
+  end
+
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:token) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+    end
+  end
+end
diff --git a/spec/models/project_services/custom_issue_tracker_service_spec.rb b/spec/models/project_services/custom_issue_tracker_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..ff976f8ec5932ff613ae4f51fc9e395b566c9155
--- /dev/null
+++ b/spec/models/project_services/custom_issue_tracker_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe CustomIssueTrackerService, models: true do
+  describe 'Associations' do
+    it { is_expected.to belong_to :project }
+    it { is_expected.to have_one :service_hook }
+  end
+
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:project_url) }
+      it { is_expected.to validate_presence_of(:issues_url) }
+      it { is_expected.to validate_presence_of(:new_issue_url) }
+      it_behaves_like 'issue tracker service URL attribute', :project_url
+      it_behaves_like 'issue tracker service URL attribute', :issues_url
+      it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:project_url) }
+      it { is_expected.not_to validate_presence_of(:issues_url) }
+      it { is_expected.not_to validate_presence_of(:new_issue_url) }
+    end
+  end
+end
diff --git a/spec/models/project_services/drone_ci_service_spec.rb b/spec/models/project_services/drone_ci_service_spec.rb
index a2cf68a9e387c037dda62ce3ab5b10850f5d55d3..3a8e67438fc308f98b4a03b5f5aac9757a48907a 100644
--- a/spec/models/project_services/drone_ci_service_spec.rb
+++ b/spec/models/project_services/drone_ci_service_spec.rb
@@ -28,25 +28,18 @@
 
   describe 'validations' do
     context 'active' do
-      before { allow(subject).to receive(:activated?).and_return(true) }
+      before { subject.active = true }
 
       it { is_expected.to validate_presence_of(:token) }
       it { is_expected.to validate_presence_of(:drone_url) }
-      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
-      it { is_expected.to allow_value('http://ci.example.com').for(:drone_url) }
-      it { is_expected.not_to allow_value('this is not url').for(:drone_url) }
-      it { is_expected.not_to allow_value('http//noturl').for(:drone_url) }
-      it { is_expected.not_to allow_value('ftp://ci.example.com').for(:drone_url) }
+      it_behaves_like 'issue tracker service URL attribute', :drone_url
     end
 
     context 'inactive' do
-      before { allow(subject).to receive(:activated?).and_return(false) }
+      before { subject.active = false }
 
       it { is_expected.not_to validate_presence_of(:token) }
       it { is_expected.not_to validate_presence_of(:drone_url) }
-      it { is_expected.to allow_value('ewf9843kdnfdfs89234n').for(:token) }
-      it { is_expected.to allow_value('http://drone.example.com').for(:drone_url) }
-      it { is_expected.to allow_value('ftp://drone.example.com').for(:drone_url) }
     end
   end
 
diff --git a/spec/models/project_services/emails_on_push_service_spec.rb b/spec/models/project_services/emails_on_push_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e6f78898c822c198d10e6634f7b62e101e08e50e
--- /dev/null
+++ b/spec/models/project_services/emails_on_push_service_spec.rb
@@ -0,0 +1,17 @@
+require 'spec_helper'
+
+describe EmailsOnPushService do
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:recipients) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:recipients) }
+    end
+  end
+end
diff --git a/spec/models/external_wiki_service_spec.rb b/spec/models/project_services/external_wiki_service_spec.rb
similarity index 78%
rename from spec/models/external_wiki_service_spec.rb
rename to spec/models/project_services/external_wiki_service_spec.rb
index d37978720bf9cd945fe10a5bcad1533d544649bc..5fe5ea7d2dfc518e677f4a39e5ad363e6d7da050 100644
--- a/spec/models/external_wiki_service_spec.rb
+++ b/spec/models/project_services/external_wiki_service_spec.rb
@@ -28,13 +28,18 @@
     it { should have_one :service_hook }
   end
 
-  describe "Validations" do
-    context "active" do
-      before do
-        subject.active = true
-      end
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:external_wiki_url) }
+      it_behaves_like 'issue tracker service URL attribute', :external_wiki_url
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
 
-      it { should validate_presence_of :external_wiki_url }
+      it { is_expected.not_to validate_presence_of(:external_wiki_url) }
     end
   end
 
diff --git a/spec/models/project_services/flowdock_service_spec.rb b/spec/models/project_services/flowdock_service_spec.rb
index ff7fbcaa00443d634bbe83344254e0eb31f924a0..b7e627e6518cb9e6584e5ed3dd022cd1f9189c32 100644
--- a/spec/models/project_services/flowdock_service_spec.rb
+++ b/spec/models/project_services/flowdock_service_spec.rb
@@ -26,6 +26,20 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:token) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+    end
+  end
+
   describe "Execute" do
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
diff --git a/spec/models/project_services/gemnasium_service_spec.rb b/spec/models/project_services/gemnasium_service_spec.rb
index ecb3ccb16732b4a5c4d7ae8f70983edf1c96f09c..a08f1ac229f1ed8c090bfbad65197b1bac684f3f 100644
--- a/spec/models/project_services/gemnasium_service_spec.rb
+++ b/spec/models/project_services/gemnasium_service_spec.rb
@@ -26,6 +26,22 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:token) }
+      it { is_expected.to validate_presence_of(:api_key) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+      it { is_expected.not_to validate_presence_of(:api_key) }
+    end
+  end
+
   describe "Execute" do
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
diff --git a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
index 3518dbd172863d435f4a34489a7fbfb63c225b74..7a1f106d6e3a3a7b4d2065c790a2b998220d93a3 100644
--- a/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
+++ b/spec/models/project_services/gitlab_issue_tracker_service_spec.rb
@@ -26,6 +26,20 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      subject { described_class.new(project: create(:project), active: true) }
+
+      it { is_expected.to validate_presence_of(:issues_url) }
+      it_behaves_like 'issue tracker service URL attribute', :issues_url
+    end
+
+    context 'when service is inactive' do
+      subject { described_class.new(project: create(:project), active: false) }
+
+      it { is_expected.not_to validate_presence_of(:issues_url) }
+    end
+  end
 
   describe 'project and issue urls' do
     let(:project) { create(:project) }
diff --git a/spec/models/project_services/hipchat_service_spec.rb b/spec/models/project_services/hipchat_service_spec.rb
index d878162a220f5072263968ff3d7ac20beb3a9204..6fb5cad50119f181d00953ccd33ccf7d1209b8b1 100644
--- a/spec/models/project_services/hipchat_service_spec.rb
+++ b/spec/models/project_services/hipchat_service_spec.rb
@@ -26,6 +26,20 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:token) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+    end
+  end
+
   describe "Execute" do
     let(:hipchat) { HipchatService.new }
     let(:user)    { create(:user, username: 'username') }
diff --git a/spec/models/project_services/irker_service_spec.rb b/spec/models/project_services/irker_service_spec.rb
index b783b1a576ec8dc931f4aa356c11b338d755ab22..4ee022a51710ec490efbc85eb824de543b1e9df1 100644
--- a/spec/models/project_services/irker_service_spec.rb
+++ b/spec/models/project_services/irker_service_spec.rb
@@ -29,14 +29,16 @@
   end
 
   describe 'Validations' do
-    before do
-      subject.active = true
-      subject.properties['recipients'] = _recipients
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:recipients) }
     end
 
-    context 'active' do
-      let(:_recipients) { nil }
-      it { should validate_presence_of :recipients }
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:recipients) }
     end
   end
 
diff --git a/spec/models/project_services/jira_service_spec.rb b/spec/models/project_services/jira_service_spec.rb
index 2f8193170aedf3009a9377879a2b4da0c7c11c08..5309cfb99fff5154d442df92a2edf79666d61ba3 100644
--- a/spec/models/project_services/jira_service_spec.rb
+++ b/spec/models/project_services/jira_service_spec.rb
@@ -26,6 +26,30 @@
     it { is_expected.to have_one :service_hook }
   end
 
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:api_url) }
+      it { is_expected.to validate_presence_of(:project_url) }
+      it { is_expected.to validate_presence_of(:issues_url) }
+      it { is_expected.to validate_presence_of(:new_issue_url) }
+      it_behaves_like 'issue tracker service URL attribute', :api_url
+      it_behaves_like 'issue tracker service URL attribute', :project_url
+      it_behaves_like 'issue tracker service URL attribute', :issues_url
+      it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:api_url) }
+      it { is_expected.not_to validate_presence_of(:project_url) }
+      it { is_expected.not_to validate_presence_of(:issues_url) }
+      it { is_expected.not_to validate_presence_of(:new_issue_url) }
+    end
+  end
+
   describe "Execute" do
     let(:user)    { create(:user) }
     let(:project) { create(:project) }
@@ -72,7 +96,7 @@
 
     context "when a password was previously set" do
       before do
-        @jira_service = JiraService.create(
+        @jira_service = JiraService.create!(
           project: create(:project),
           properties: {
             api_url: 'http://jira.example.com/rest/api/2',
diff --git a/spec/models/project_services/pivotaltracker_service_spec.rb b/spec/models/project_services/pivotaltracker_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..f37edd4d970967086a38870a2aff1c5fb809cc10
--- /dev/null
+++ b/spec/models/project_services/pivotaltracker_service_spec.rb
@@ -0,0 +1,42 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe PivotaltrackerService, models: true do
+  describe 'Associations' do
+    it { is_expected.to belong_to :project }
+    it { is_expected.to have_one :service_hook }
+  end
+
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:token) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:token) }
+    end
+  end
+end
diff --git a/spec/models/project_services/pushover_service_spec.rb b/spec/models/project_services/pushover_service_spec.rb
index 96039f9491bc49619de645fe3ccc8835296b0283..555d9757b47a7bf55ab7979f7fbafdecdb6b5f3b 100644
--- a/spec/models/project_services/pushover_service_spec.rb
+++ b/spec/models/project_services/pushover_service_spec.rb
@@ -27,14 +27,20 @@
   end
 
   describe 'Validations' do
-    context 'active' do
-      before do
-        subject.active = true
-      end
+    context 'when service is active' do
+      before { subject.active = true }
 
-      it { is_expected.to validate_presence_of :api_key }
-      it { is_expected.to validate_presence_of :user_key }
-      it { is_expected.to validate_presence_of :priority }
+      it { is_expected.to validate_presence_of(:api_key) }
+      it { is_expected.to validate_presence_of(:user_key) }
+      it { is_expected.to validate_presence_of(:priority) }
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:api_key) }
+      it { is_expected.not_to validate_presence_of(:user_key) }
+      it { is_expected.not_to validate_presence_of(:priority) }
     end
   end
 
diff --git a/spec/models/project_services/redmine_service_spec.rb b/spec/models/project_services/redmine_service_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..7d14f6e82806f6466dad3a06c1da7e2865f09358
--- /dev/null
+++ b/spec/models/project_services/redmine_service_spec.rb
@@ -0,0 +1,49 @@
+# == Schema Information
+#
+# Table name: services
+#
+#  id                    :integer          not null, primary key
+#  type                  :string(255)
+#  title                 :string(255)
+#  project_id            :integer
+#  created_at            :datetime
+#  updated_at            :datetime
+#  active                :boolean          default(FALSE), not null
+#  properties            :text
+#  template              :boolean          default(FALSE)
+#  push_events           :boolean          default(TRUE)
+#  issues_events         :boolean          default(TRUE)
+#  merge_requests_events :boolean          default(TRUE)
+#  tag_push_events       :boolean          default(TRUE)
+#  note_events           :boolean          default(TRUE), not null
+#
+
+require 'spec_helper'
+
+describe RedmineService, models: true do
+  describe 'Associations' do
+    it { is_expected.to belong_to :project }
+    it { is_expected.to have_one :service_hook }
+  end
+
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
+
+      it { is_expected.to validate_presence_of(:project_url) }
+      it { is_expected.to validate_presence_of(:issues_url) }
+      it { is_expected.to validate_presence_of(:new_issue_url) }
+      it_behaves_like 'issue tracker service URL attribute', :project_url
+      it_behaves_like 'issue tracker service URL attribute', :issues_url
+      it_behaves_like 'issue tracker service URL attribute', :new_issue_url
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:project_url) }
+      it { is_expected.not_to validate_presence_of(:issues_url) }
+      it { is_expected.not_to validate_presence_of(:new_issue_url) }
+    end
+  end
+end
diff --git a/spec/models/project_services/slack_service_spec.rb b/spec/models/project_services/slack_service_spec.rb
index 478d59be08ba867fd96c7fd11e1c6f1ce5ffb99d..a97b7560137fdaec9860916a2926690bf2b71341 100644
--- a/spec/models/project_services/slack_service_spec.rb
+++ b/spec/models/project_services/slack_service_spec.rb
@@ -26,13 +26,18 @@
     it { is_expected.to have_one :service_hook }
   end
 
-  describe "Validations" do
-    context "active" do
-      before do
-        subject.active = true
-      end
+  describe 'Validations' do
+    context 'when service is active' do
+      before { subject.active = true }
 
-      it { is_expected.to validate_presence_of :webhook }
+      it { is_expected.to validate_presence_of(:webhook) }
+      it_behaves_like 'issue tracker service URL attribute', :webhook
+    end
+
+    context 'when service is inactive' do
+      before { subject.active = false }
+
+      it { is_expected.not_to validate_presence_of(:webhook) }
     end
   end
 
diff --git a/spec/models/project_services/teamcity_service_spec.rb b/spec/models/project_services/teamcity_service_spec.rb
index bc7423cee6986a39f9cd1d5103799b0ecf3484fe..ad24b895170cb2531cb55937b0456c7e4779d5cd 100644
--- a/spec/models/project_services/teamcity_service_spec.rb
+++ b/spec/models/project_services/teamcity_service_spec.rb
@@ -27,86 +27,51 @@
   end
 
   describe 'Validations' do
-    describe '#teamcity_url' do
-      it 'does not validate the presence of teamcity_url if service is not active' do
-        teamcity_service = service
-        teamcity_service.active = false
-
-        expect(teamcity_service).not_to validate_presence_of(:teamcity_url)
-      end
-
-      it 'validates the presence of teamcity_url if service is active' do
-        teamcity_service = service
-        teamcity_service.active = true
-
-        expect(teamcity_service).to validate_presence_of(:teamcity_url)
-      end
-    end
+    subject { service }
 
-    describe '#build_type' do
-      it 'does not validate the presence of build_type if service is not active' do
-        teamcity_service = service
-        teamcity_service.active = false
+    context 'when service is active' do
+      before { subject.active = true }
 
-        expect(teamcity_service).not_to validate_presence_of(:build_type)
-      end
+      it { is_expected.to validate_presence_of(:build_type) }
+      it { is_expected.to validate_presence_of(:teamcity_url) }
+      it_behaves_like 'issue tracker service URL attribute', :teamcity_url
 
-      it 'validates the presence of build_type if service is active' do
-        teamcity_service = service
-        teamcity_service.active = true
+      describe '#username' do
+        it 'does not validate the presence of username if password is nil' do
+          subject.password = nil
 
-        expect(teamcity_service).to validate_presence_of(:build_type)
-      end
-    end
+          expect(subject).not_to validate_presence_of(:username)
+        end
 
-    describe '#username' do
-      it 'does not validate the presence of username if service is not active' do
-        teamcity_service = service
-        teamcity_service.active = false
+        it 'validates the presence of username if password is present' do
+          subject.password = 'secret'
 
-        expect(teamcity_service).not_to validate_presence_of(:username)
+          expect(subject).to validate_presence_of(:username)
+        end
       end
 
-      it 'does not validate the presence of username if username is nil' do
-        teamcity_service = service
-        teamcity_service.active = true
-        teamcity_service.password = nil
+      describe '#password' do
+        it 'does not validate the presence of password if username is nil' do
+          subject.username = nil
 
-        expect(teamcity_service).not_to validate_presence_of(:username)
-      end
+          expect(subject).not_to validate_presence_of(:password)
+        end
 
-      it 'validates the presence of username if service is active and username is present' do
-        teamcity_service = service
-        teamcity_service.active = true
-        teamcity_service.password = 'secret'
+        it 'validates the presence of password if username is present' do
+          subject.username = 'john'
 
-        expect(teamcity_service).to validate_presence_of(:username)
+          expect(subject).to validate_presence_of(:password)
+        end
       end
     end
 
-    describe '#password' do
-      it 'does not validate the presence of password if service is not active' do
-        teamcity_service = service
-        teamcity_service.active = false
-
-        expect(teamcity_service).not_to validate_presence_of(:password)
-      end
-
-      it 'does not validate the presence of password if username is nil' do
-        teamcity_service = service
-        teamcity_service.active = true
-        teamcity_service.username = nil
-
-        expect(teamcity_service).not_to validate_presence_of(:password)
-      end
-
-      it 'validates the presence of password if service is active and username is present' do
-        teamcity_service = service
-        teamcity_service.active = true
-        teamcity_service.username = 'john'
+    context 'when service is inactive' do
+      before { subject.active = false }
 
-        expect(teamcity_service).to validate_presence_of(:password)
-      end
+      it { is_expected.not_to validate_presence_of(:build_type) }
+      it { is_expected.not_to validate_presence_of(:teamcity_url) }
+      it { is_expected.not_to validate_presence_of(:username) }
+      it { is_expected.not_to validate_presence_of(:password) }
     end
   end
 
diff --git a/spec/requests/api/milestones_spec.rb b/spec/requests/api/milestones_spec.rb
index 344f0fe0b7fcbebae65249a9288656b83d9a7eee..241995041bb113828eac54c99efb9dd4e512f578 100644
--- a/spec/requests/api/milestones_spec.rb
+++ b/spec/requests/api/milestones_spec.rb
@@ -127,7 +127,7 @@
 
   describe 'GET /projects/:id/milestones/:milestone_id/issues' do
     before do
-      milestone.issues << create(:issue)
+      milestone.issues << create(:issue, project: project)
     end
     it 'should return project issues for a particular milestone' do
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues", user)
@@ -140,5 +140,34 @@
       get api("/projects/#{project.id}/milestones/#{milestone.id}/issues")
       expect(response.status).to eq(401)
     end
+
+    describe 'confidential issues' do
+      let(:public_project) { create(:project, :public) }
+      let(:milestone) { create(:milestone, project: public_project) }
+      let(:issue) { create(:issue, project: public_project) }
+      let(:confidential_issue) { create(:issue, confidential: true, project: public_project) }
+      before do
+        public_project.team << [user, :developer]
+        milestone.issues << issue << confidential_issue
+      end
+
+      it 'returns confidential issues to team members' do
+        get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", user)
+
+        expect(response.status).to eq(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(2)
+        expect(json_response.map { |issue| issue['id'] }).to include(issue.id, confidential_issue.id)
+      end
+
+      it 'does not return confidential issues to regular users' do
+        get api("/projects/#{public_project.id}/milestones/#{milestone.id}/issues", create(:user))
+
+        expect(response.status).to eq(200)
+        expect(json_response).to be_an Array
+        expect(json_response.size).to eq(1)
+        expect(json_response.map { |issue| issue['id'] }).to include(issue.id)
+      end
+    end
   end
 end
diff --git a/spec/requests/api/notes_spec.rb b/spec/requests/api/notes_spec.rb
index ec9eda0a2edd29966c9766eae2ce3d4a95c295a1..49091fc0f49d416bcfa294a4ea22056141ec076f 100644
--- a/spec/requests/api/notes_spec.rb
+++ b/spec/requests/api/notes_spec.rb
@@ -3,7 +3,7 @@
 describe API::API, api: true  do
   include ApiHelpers
   let(:user) { create(:user) }
-  let!(:project) { create(:project, namespace: user.namespace ) }
+  let!(:project) { create(:project, namespace: user.namespace) }
   let!(:issue) { create(:issue, project: project, author: user) }
   let!(:merge_request) { create(:merge_request, source_project: project, target_project: project, author: user) }
   let!(:snippet) { create(:project_snippet, project: project, author: user) }
@@ -45,7 +45,7 @@
       end
 
       it "should return a 404 error when issue id not found" do
-        get api("/projects/#{project.id}/issues/123/notes", user)
+        get api("/projects/#{project.id}/issues/12345/notes", user)
         expect(response.status).to eq(404)
       end
 
@@ -106,7 +106,7 @@
       end
 
       it "should return a 404 error if issue note not found" do
-        get api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+        get api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
         expect(response.status).to eq(404)
       end
 
@@ -134,7 +134,7 @@
       end
 
       it "should return a 404 error if snippet note not found" do
-        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/123", user)
+        get api("/projects/#{project.id}/snippets/#{snippet.id}/notes/12345", user)
         expect(response.status).to eq(404)
       end
     end
@@ -191,6 +191,27 @@
         expect(response.status).to eq(401)
       end
     end
+
+    context 'when user does not have access to create noteable' do
+      let(:private_issue) { create(:issue, project: create(:project, :private)) }
+
+      ##
+      # We are posting to project user has access to, but we use issue id
+      # from a different project, see #15577
+      #
+      before do
+        post api("/projects/#{project.id}/issues/#{private_issue.id}/notes", user),
+             body: 'Hi!'
+      end
+
+      it 'responds with 500' do
+        expect(response.status).to eq 500
+      end
+
+      it 'does not create new note' do
+        expect(private_issue.notes.reload).to be_empty
+      end
+    end
   end
 
   describe "POST /projects/:id/noteable/:noteable_id/notes to test observer on create" do
@@ -211,7 +232,7 @@
       end
 
       it 'should return a 404 error when note id not found' do
-        put api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user),
+        put api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user),
                 body: 'Hello!'
         expect(response.status).to eq(404)
       end
@@ -233,7 +254,7 @@
 
       it 'should return a 404 error when note id not found' do
         put api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                  "notes/123", user), body: "Hello!"
+                  "notes/12345", user), body: "Hello!"
         expect(response.status).to eq(404)
       end
     end
@@ -248,7 +269,7 @@
 
       it 'should return a 404 error when note id not found' do
         put api("/projects/#{project.id}/merge_requests/#{merge_request.id}/"\
-                  "notes/123", user), body: "Hello!"
+                  "notes/12345", user), body: "Hello!"
         expect(response.status).to eq(404)
       end
     end
@@ -268,7 +289,7 @@
       end
 
       it 'returns a 404 error when note id not found' do
-        delete api("/projects/#{project.id}/issues/#{issue.id}/notes/123", user)
+        delete api("/projects/#{project.id}/issues/#{issue.id}/notes/12345", user)
 
         expect(response.status).to eq(404)
       end
@@ -288,7 +309,7 @@
 
       it 'returns a 404 error when note id not found' do
         delete api("/projects/#{project.id}/snippets/#{snippet.id}/"\
-                   "notes/123", user)
+                   "notes/12345", user)
 
         expect(response.status).to eq(404)
       end
@@ -308,7 +329,7 @@
 
       it 'returns a 404 error when note id not found' do
         delete api("/projects/#{project.id}/merge_requests/"\
-                   "#{merge_request.id}/notes/123", user)
+                   "#{merge_request.id}/notes/12345", user)
 
         expect(response.status).to eq(404)
       end
diff --git a/spec/requests/api/project_snippets_spec.rb b/spec/requests/api/project_snippets_spec.rb
index 3722ddf5a33f1d867cba5551999b169ebb0d045f..9706d060cfaea22c06f7b9d5cba92a0aa1562421 100644
--- a/spec/requests/api/project_snippets_spec.rb
+++ b/spec/requests/api/project_snippets_spec.rb
@@ -15,4 +15,91 @@
       expect(json_response['expires_at']).to be_nil
     end
   end
+
+  describe 'GET /projects/:project_id/snippets/' do
+    it 'all snippets available to team member' do
+      project = create(:project, :public)
+      user = create(:user)
+      project.team << [user, :developer]
+      public_snippet = create(:project_snippet, :public, project: project)
+      internal_snippet = create(:project_snippet, :internal, project: project)
+      private_snippet = create(:project_snippet, :private, project: project)
+
+      get api("/projects/#{project.id}/snippets/", user)
+
+      expect(response.status).to eq(200)
+      expect(json_response.size).to eq(3)
+      expect(json_response.map{ |snippet| snippet['id']} ).to include(public_snippet.id, internal_snippet.id, private_snippet.id)
+    end
+
+    it 'hides private snippets from regular user' do
+      project = create(:project, :public)
+      user = create(:user)
+      create(:project_snippet, :private, project: project)
+
+      get api("/projects/#{project.id}/snippets/", user)
+      expect(response.status).to eq(200)
+      expect(json_response.size).to eq(0)
+    end
+  end
+
+  describe 'POST /projects/:project_id/snippets/' do
+    it 'creates a new snippet' do
+      admin = create(:admin)
+      project = create(:project)
+      params = {
+        title: 'Test Title',
+        file_name: 'test.rb',
+        code: 'puts "hello world"',
+        visibility_level: Gitlab::VisibilityLevel::PUBLIC
+      }
+
+      post api("/projects/#{project.id}/snippets/", admin), params
+
+      expect(response.status).to eq(201)
+      snippet = ProjectSnippet.find(json_response['id'])
+      expect(snippet.content).to eq(params[:code])
+      expect(snippet.title).to eq(params[:title])
+      expect(snippet.file_name).to eq(params[:file_name])
+      expect(snippet.visibility_level).to eq(params[:visibility_level])
+    end
+  end
+
+  describe 'PUT /projects/:project_id/snippets/:id/' do
+    it 'updates snippet' do
+      admin = create(:admin)
+      snippet = create(:project_snippet, author: admin)
+      new_content = 'New content'
+
+      put api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin), code: new_content
+
+      expect(response.status).to eq(200)
+      snippet.reload
+      expect(snippet.content).to eq(new_content)
+    end
+  end
+
+  describe 'DELETE /projects/:project_id/snippets/:id/' do
+    it 'deletes snippet' do
+      admin = create(:admin)
+      snippet = create(:project_snippet, author: admin)
+
+      delete api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/", admin)
+
+      expect(response.status).to eq(200)
+    end
+  end
+
+  describe 'GET /projects/:project_id/snippets/:id/raw' do
+    it 'returns raw text' do
+      admin = create(:admin)
+      snippet = create(:project_snippet, author: admin)
+
+      get api("/projects/#{snippet.project.id}/snippets/#{snippet.id}/raw", admin)
+
+      expect(response.status).to eq(200)
+      expect(response.content_type).to eq 'text/plain'
+      expect(response.body).to eq(snippet.content)
+    end
+  end
 end
diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb
index fccd08bd6dac024f62463525b277d72591ec73f2..66193eac051e1592ec0e00d689688cecccbbbdfb 100644
--- a/spec/requests/api/projects_spec.rb
+++ b/spec/requests/api/projects_spec.rb
@@ -11,7 +11,7 @@
   let(:project) { create(:project, creator_id: user.id, namespace: user.namespace) }
   let(:project2) { create(:project, path: 'project2', creator_id: user.id, namespace: user.namespace) }
   let(:project3) { create(:project, path: 'project3', creator_id: user.id, namespace: user.namespace) }
-  let(:snippet) { create(:project_snippet, author: user, project: project, title: 'example') }
+  let(:snippet) { create(:project_snippet, :public, author: user, project: project, title: 'example') }
   let(:project_member) { create(:project_member, :master, user: user, project: project) }
   let(:project_member2) { create(:project_member, :developer, user: user3, project: project) }
   let(:user4) { create(:user) }
diff --git a/spec/support/issue_tracker_service_shared_example.rb b/spec/support/issue_tracker_service_shared_example.rb
new file mode 100644
index 0000000000000000000000000000000000000000..b6d7436c3609efa3d740c237ed4fa0704250ccd8
--- /dev/null
+++ b/spec/support/issue_tracker_service_shared_example.rb
@@ -0,0 +1,7 @@
+RSpec.shared_examples 'issue tracker service URL attribute' do |url_attr|
+  it { is_expected.to allow_value('https://example.com').for(url_attr) }
+
+  it { is_expected.not_to allow_value('example.com').for(url_attr) }
+  it { is_expected.not_to allow_value('ftp://example.com').for(url_attr) }
+  it { is_expected.not_to allow_value('herp-and-derp').for(url_attr) }
+end