PageRenderTime 26ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/spec/requests/git_http_spec.rb

https://gitlab.com/svansteenis/gitlab-ee
Ruby | 395 lines | 347 code | 47 blank | 1 comment | 2 complexity | 02176500e3ae09f9b0e9b6ea10af4b1d MD5 | raw file
  1. require "spec_helper"
  2. describe 'Git HTTP requests', lib: true do
  3. let(:user) { create(:user) }
  4. let(:project) { create(:project, path: 'project.git-project') }
  5. it "gives WWW-Authenticate hints" do
  6. clone_get('doesnt/exist.git')
  7. expect(response.header['WWW-Authenticate']).to start_with('Basic ')
  8. end
  9. context "when the project doesn't exist" do
  10. context "when no authentication is provided" do
  11. it "responds with status 401 (no project existence information leak)" do
  12. download('doesnt/exist.git') do |response|
  13. expect(response.status).to eq(401)
  14. end
  15. end
  16. end
  17. context "when username and password are provided" do
  18. context "when authentication fails" do
  19. it "responds with status 401" do
  20. download('doesnt/exist.git', user: user.username, password: "nope") do |response|
  21. expect(response.status).to eq(401)
  22. end
  23. end
  24. end
  25. context "when authentication succeeds" do
  26. it "responds with status 404" do
  27. download('/doesnt/exist.git', user: user.username, password: user.password) do |response|
  28. expect(response.status).to eq(404)
  29. end
  30. end
  31. end
  32. end
  33. end
  34. context "when the Wiki for a project exists" do
  35. it "responds with the right project" do
  36. wiki = ProjectWiki.new(project)
  37. project.update_attribute(:visibility_level, Project::PUBLIC)
  38. download("/#{wiki.repository.path_with_namespace}.git") do |response|
  39. json_body = ActiveSupport::JSON.decode(response.body)
  40. expect(response.status).to eq(200)
  41. expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
  42. end
  43. end
  44. end
  45. context "when the project exists" do
  46. let(:path) { "#{project.path_with_namespace}.git" }
  47. context "when the project is public" do
  48. before do
  49. project.update_attribute(:visibility_level, Project::PUBLIC)
  50. end
  51. it "downloads get status 200" do
  52. download(path, {}) do |response|
  53. expect(response.status).to eq(200)
  54. end
  55. end
  56. it "uploads get status 401" do
  57. upload(path, {}) do |response|
  58. expect(response.status).to eq(401)
  59. end
  60. end
  61. context "with correct credentials" do
  62. let(:env) { { user: user.username, password: user.password } }
  63. it "uploads get status 200 (because Git hooks do the real check)" do
  64. upload(path, env) do |response|
  65. expect(response.status).to eq(200)
  66. end
  67. end
  68. context 'but git-receive-pack is disabled' do
  69. it "responds with status 404" do
  70. allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
  71. upload(path, env) do |response|
  72. expect(response.status).to eq(404)
  73. end
  74. end
  75. end
  76. end
  77. context 'but git-upload-pack is disabled' do
  78. it "responds with status 404" do
  79. allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
  80. download(path, {}) do |response|
  81. expect(response.status).to eq(404)
  82. end
  83. end
  84. end
  85. end
  86. context "when the project is private" do
  87. before do
  88. project.update_attribute(:visibility_level, Project::PRIVATE)
  89. end
  90. context "when no authentication is provided" do
  91. it "responds with status 401 to downloads" do
  92. download(path, {}) do |response|
  93. expect(response.status).to eq(401)
  94. end
  95. end
  96. it "responds with status 401 to uploads" do
  97. upload(path, {}) do |response|
  98. expect(response.status).to eq(401)
  99. end
  100. end
  101. end
  102. context "when username and password are provided" do
  103. let(:env) { { user: user.username, password: 'nope' } }
  104. context "when authentication fails" do
  105. it "responds with status 401" do
  106. download(path, env) do |response|
  107. expect(response.status).to eq(401)
  108. end
  109. end
  110. context "when the user is IP banned" do
  111. it "responds with status 401" do
  112. expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
  113. allow_any_instance_of(Rack::Request).to receive(:ip).and_return('1.2.3.4')
  114. clone_get(path, env)
  115. expect(response.status).to eq(401)
  116. end
  117. end
  118. end
  119. context "when authentication succeeds" do
  120. let(:env) { { user: user.username, password: user.password } }
  121. context "when the user has access to the project" do
  122. before do
  123. project.team << [user, :master]
  124. end
  125. context "when the user is blocked" do
  126. it "responds with status 404" do
  127. user.block
  128. project.team << [user, :master]
  129. download(path, env) do |response|
  130. expect(response.status).to eq(404)
  131. end
  132. end
  133. end
  134. context "when the user isn't blocked" do
  135. it "downloads get status 200" do
  136. expect(Rack::Attack::Allow2Ban).to receive(:reset)
  137. clone_get(path, env)
  138. expect(response.status).to eq(200)
  139. end
  140. it "uploads get status 200" do
  141. upload(path, env) do |response|
  142. expect(response.status).to eq(200)
  143. end
  144. end
  145. end
  146. context "when an oauth token is provided" do
  147. before do
  148. application = Doorkeeper::Application.create!(name: "MyApp", redirect_uri: "https://app.com", owner: user)
  149. @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id)
  150. end
  151. it "downloads get status 200" do
  152. clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
  153. expect(response.status).to eq(200)
  154. end
  155. it "uploads get status 401 (no project existence information leak)" do
  156. push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
  157. expect(response.status).to eq(401)
  158. end
  159. end
  160. context "when blank password attempts follow a valid login" do
  161. def attempt_login(include_password)
  162. password = include_password ? user.password : ""
  163. clone_get path, user: user.username, password: password
  164. response.status
  165. end
  166. it "repeated attempts followed by successful attempt" do
  167. options = Gitlab.config.rack_attack.git_basic_auth
  168. maxretry = options[:maxretry] - 1
  169. ip = '1.2.3.4'
  170. allow_any_instance_of(Rack::Request).to receive(:ip).and_return(ip)
  171. Rack::Attack::Allow2Ban.reset(ip, options)
  172. maxretry.times.each do
  173. expect(attempt_login(false)).to eq(401)
  174. end
  175. expect(attempt_login(true)).to eq(200)
  176. expect(Rack::Attack::Allow2Ban.banned?(ip)).to be_falsey
  177. maxretry.times.each do
  178. expect(attempt_login(false)).to eq(401)
  179. end
  180. Rack::Attack::Allow2Ban.reset(ip, options)
  181. end
  182. end
  183. end
  184. context "when the user doesn't have access to the project" do
  185. it "downloads get status 404" do
  186. download(path, user: user.username, password: user.password) do |response|
  187. expect(response.status).to eq(404)
  188. end
  189. end
  190. it "uploads get status 200 (because Git hooks do the real check)" do
  191. upload(path, user: user.username, password: user.password) do |response|
  192. expect(response.status).to eq(200)
  193. end
  194. end
  195. end
  196. end
  197. end
  198. context "when a gitlab ci token is provided" do
  199. let(:token) { 123 }
  200. let(:project) { FactoryGirl.create :empty_project }
  201. before do
  202. project.update_attributes(runners_token: token, builds_enabled: true)
  203. end
  204. it "downloads get status 200" do
  205. clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
  206. expect(response.status).to eq(200)
  207. end
  208. it "uploads get status 401 (no project existence information leak)" do
  209. push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: token
  210. expect(response.status).to eq(401)
  211. end
  212. end
  213. end
  214. end
  215. context "when the project path doesn't end in .git" do
  216. context "GET info/refs" do
  217. let(:path) { "/#{project.path_with_namespace}/info/refs" }
  218. context "when no params are added" do
  219. before { get path }
  220. it "redirects to the .git suffix version" do
  221. expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs")
  222. end
  223. end
  224. context "when the upload-pack service is requested" do
  225. let(:params) { { service: 'git-upload-pack' } }
  226. before { get path, params }
  227. it "redirects to the .git suffix version" do
  228. expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
  229. end
  230. end
  231. context "when the receive-pack service is requested" do
  232. let(:params) { { service: 'git-receive-pack' } }
  233. before { get path, params }
  234. it "redirects to the .git suffix version" do
  235. expect(response).to redirect_to("/#{project.path_with_namespace}.git/info/refs?service=#{params[:service]}")
  236. end
  237. end
  238. context "when the params are anything else" do
  239. let(:params) { { service: 'git-implode-pack' } }
  240. before { get path, params }
  241. it "redirects to the sign-in page" do
  242. expect(response).to redirect_to(new_user_session_path)
  243. end
  244. end
  245. end
  246. context "POST git-upload-pack" do
  247. it "fails to find a route" do
  248. expect { clone_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
  249. end
  250. end
  251. context "POST git-receive-pack" do
  252. it "failes to find a route" do
  253. expect { push_post(project.path_with_namespace) }.to raise_error(ActionController::RoutingError)
  254. end
  255. end
  256. end
  257. context "retrieving an info/refs file" do
  258. before { project.update_attribute(:visibility_level, Project::PUBLIC) }
  259. context "when the file exists" do
  260. before do
  261. # Provide a dummy file in its place
  262. allow_any_instance_of(Repository).to receive(:blob_at).and_call_original
  263. allow_any_instance_of(Repository).to receive(:blob_at).with('5937ac0a7beb003549fc5fd26fc247adbce4a52e', 'info/refs') do
  264. Gitlab::Git::Blob.find(project.repository, 'master', '.gitignore')
  265. end
  266. get "/#{project.path_with_namespace}/blob/master/info/refs"
  267. end
  268. it "returns the file" do
  269. expect(response.status).to eq(200)
  270. end
  271. end
  272. context "when the file does not exist" do
  273. before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
  274. it "returns not found" do
  275. expect(response.status).to eq(404)
  276. end
  277. end
  278. end
  279. def clone_get(project, options={})
  280. get "/#{project}/info/refs", { service: 'git-upload-pack' }, auth_env(*options.values_at(:user, :password))
  281. end
  282. def clone_post(project, options={})
  283. post "/#{project}/git-upload-pack", {}, auth_env(*options.values_at(:user, :password))
  284. end
  285. def push_get(project, options={})
  286. get "/#{project}/info/refs", { service: 'git-receive-pack' }, auth_env(*options.values_at(:user, :password))
  287. end
  288. def push_post(project, options={})
  289. post "/#{project}/git-receive-pack", {}, auth_env(*options.values_at(:user, :password))
  290. end
  291. def download(project, user: nil, password: nil)
  292. args = [project, { user: user, password: password }]
  293. clone_get(*args)
  294. yield response
  295. clone_post(*args)
  296. yield response
  297. end
  298. def upload(project, user: nil, password: nil)
  299. args = [project, { user: user, password: password }]
  300. push_get(*args)
  301. yield response
  302. push_post(*args)
  303. yield response
  304. end
  305. def auth_env(user, password)
  306. if user && password
  307. { 'HTTP_AUTHORIZATION' => ActionController::HttpAuthentication::Basic.encode_credentials(user, password) }
  308. else
  309. {}
  310. end
  311. end
  312. end