From 17c6bec79d876ce932edc0edc5d17622adb2f724 Mon Sep 17 00:00:00 2001
From: Kamil Trzcinski <ayufan@ayufan.eu>
Date: Fri, 3 Jun 2016 13:57:40 +0200
Subject: [PATCH] WIP

---
 CHANGELOG                              |  1 +
 lib/gitlab/backend/grack_auth.rb       |  2 +-
 lib/gitlab/lfs/response.rb             |  7 ++---
 lib/gitlab/lfs/router.rb               |  5 ++--
 spec/lib/gitlab/lfs/lfs_router_spec.rb | 36 ++++++++++++++++++--------
 5 files changed, 34 insertions(+), 17 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index d1cde40c1c7..b6c1959bc4c 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -22,6 +22,7 @@ v 8.9.0 (unreleased)
   - Remove 'main language' feature
   - Pipelines can be canceled only when there are running builds
   - Use downcased path to container repository as this is expected path by Docker
+  - Allow to use CI token to fetch LFS objects
   - Projects pending deletion will render a 404 page
   - Measure queue duration between gitlab-workhorse and Rails
   - Make authentication service for Container Registry to be compatible with < Docker 1.11
diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb
index cdcaae8094c..baa81d92dd9 100644
--- a/lib/gitlab/backend/grack_auth.rb
+++ b/lib/gitlab/backend/grack_auth.rb
@@ -33,7 +33,7 @@ module Grack
 
       auth!
 
-      lfs_response = Gitlab::Lfs::Router.new(project, @user, @request).try_call
+      lfs_response = Gitlab::Lfs::Router.new(project, @user, @ci, @request).try_call
       return lfs_response unless lfs_response.nil?
 
       if project && authorized_request?
diff --git a/lib/gitlab/lfs/response.rb b/lib/gitlab/lfs/response.rb
index 9d9617761b3..e3ed2f6791d 100644
--- a/lib/gitlab/lfs/response.rb
+++ b/lib/gitlab/lfs/response.rb
@@ -2,10 +2,11 @@ module Gitlab
   module Lfs
     class Response
 
-      def initialize(project, user, request)
+      def initialize(project, user, ci, request)
         @origin_project = project
         @project = storage_project(project)
         @user = user
+        @ci = ci
         @env = request.env
         @request = request
       end
@@ -189,7 +190,7 @@ module Gitlab
         return render_not_enabled unless Gitlab.config.lfs.enabled
 
         unless @project.public?
-          return render_unauthorized unless @user
+          return render_unauthorized unless @user || @ci
           return render_forbidden unless user_can_fetch?
         end
 
@@ -210,7 +211,7 @@ module Gitlab
 
       def user_can_fetch?
         # Check user access against the project they used to initiate the pull
-        @user.can?(:download_code, @origin_project)
+        @ci || @user.can?(:download_code, @origin_project)
       end
 
       def user_can_push?
diff --git a/lib/gitlab/lfs/router.rb b/lib/gitlab/lfs/router.rb
index 78d02891102..f0c58890547 100644
--- a/lib/gitlab/lfs/router.rb
+++ b/lib/gitlab/lfs/router.rb
@@ -1,9 +1,10 @@
 module Gitlab
   module Lfs
     class Router
-      def initialize(project, user, request)
+      def initialize(project, user, ci, request)
         @project = project
         @user = user
+        @ci = ci
         @env = request.env
         @request = request
       end
@@ -80,7 +81,7 @@ module Gitlab
       def lfs
         return unless @project
 
-        Gitlab::Lfs::Response.new(@project, @user, @request)
+        Gitlab::Lfs::Response.new(@project, @user, @ci, @request)
       end
 
       def sanitize_tmp_filename(name)
diff --git a/spec/lib/gitlab/lfs/lfs_router_spec.rb b/spec/lib/gitlab/lfs/lfs_router_spec.rb
index 88814bc474d..8c5a27bf368 100644
--- a/spec/lib/gitlab/lfs/lfs_router_spec.rb
+++ b/spec/lib/gitlab/lfs/lfs_router_spec.rb
@@ -17,12 +17,15 @@ describe Gitlab::Lfs::Router, lib: true do
     }
   end
 
-  let(:lfs_router_auth) { new_lfs_router(project, user) }
-  let(:lfs_router_noauth) { new_lfs_router(project, nil) }
-  let(:lfs_router_public_auth) { new_lfs_router(public_project, user) }
-  let(:lfs_router_public_noauth) { new_lfs_router(public_project, nil) }
-  let(:lfs_router_forked_noauth) { new_lfs_router(forked_project, nil) }
-  let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user_two) }
+  let(:lfs_router_auth) { new_lfs_router(project, user: user) }
+  let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) }
+  let(:lfs_router_noauth) { new_lfs_router(project) }
+  let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
+  let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
+  let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
+  let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
+  let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
+  let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
 
   let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
   let(:sample_size) { 499013 }
@@ -104,6 +107,17 @@ describe Gitlab::Lfs::Router, lib: true do
             expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
           end
         end
+
+        context 'when CI is authorized' do
+          it "responds with status 200" do
+            expect(lfs_router_ci_auth.try_call.first).to eq(200)
+          end
+
+          it "responds with the file location" do
+            expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
+            expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
+          end
+        end
       end
 
       context 'without required headers' do
@@ -525,7 +539,7 @@ describe Gitlab::Lfs::Router, lib: true do
         end
 
         describe 'when user is unauthenticated' do
-          let(:lfs_router_noauth) { new_lfs_router(project, nil) }
+          let(:lfs_router_noauth) { new_lfs_router(project) }
 
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
@@ -584,7 +598,7 @@ describe Gitlab::Lfs::Router, lib: true do
         end
 
         describe 'when user is unauthenticated' do
-          let(:lfs_router_noauth) { new_lfs_router(project, nil) }
+          let(:lfs_router_noauth) { new_lfs_router(project) }
 
           context 'and request is sent by gitlab-workhorse to authorize the request' do
             before do
@@ -716,7 +730,7 @@ describe Gitlab::Lfs::Router, lib: true do
 
       describe 'and second project not related to fork or a source project' do
         let(:second_project) { create(:project) }
-        let(:lfs_router_second_project) { new_lfs_router(second_project, user) }
+        let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) }
 
         before do
           public_project.lfs_objects << lfs_object
@@ -745,8 +759,8 @@ describe Gitlab::Lfs::Router, lib: true do
     ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
   end
 
-  def new_lfs_router(project, user)
-    Gitlab::Lfs::Router.new(project, user, request)
+  def new_lfs_router(project, user: nil, ci: false)
+    Gitlab::Lfs::Router.new(project, user, ci, request)
   end
 
   def header_for_upload_authorize(project)
-- 
GitLab