PageRenderTime 352ms CodeModel.GetById 99ms RepoModel.GetById 150ms app.codeStats 1ms

/lib/gitlab/backend/grack_auth.rb

https://gitlab.com/maelvalais/gitlab-ce
Ruby | 190 lines | 133 code | 34 blank | 23 comment | 34 complexity | 586467ce7f2837d46b13fd1cf55f7fe4 MD5 | raw file
Possible License(s): CC-BY-3.0
  1. require_relative 'shell_env'
  2. module Grack
  3. class Auth < Rack::Auth::Basic
  4. attr_accessor :user, :project, :env
  5. def call(env)
  6. @env = env
  7. @request = Rack::Request.new(env)
  8. @auth = Request.new(env)
  9. @gitlab_ci = false
  10. # Need this patch due to the rails mount
  11. # Need this if under RELATIVE_URL_ROOT
  12. unless Gitlab.config.gitlab.relative_url_root.empty?
  13. # If website is mounted using relative_url_root need to remove it first
  14. @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
  15. else
  16. @env['PATH_INFO'] = @request.path
  17. end
  18. @env['SCRIPT_NAME'] = ""
  19. auth!
  20. if project && authorized_request?
  21. if ENV['GITLAB_GRACK_AUTH_ONLY'] == '1'
  22. # Tell gitlab-git-http-server the request is OK, and what the GL_ID is
  23. render_grack_auth_ok
  24. else
  25. @app.call(env)
  26. end
  27. elsif @user.nil? && !@gitlab_ci
  28. unauthorized
  29. else
  30. render_not_found
  31. end
  32. end
  33. private
  34. def auth!
  35. return unless @auth.provided?
  36. return bad_request unless @auth.basic?
  37. # Authentication with username and password
  38. login, password = @auth.credentials
  39. # Allow authentication for GitLab CI service
  40. # if valid token passed
  41. if gitlab_ci_request?(login, password)
  42. @gitlab_ci = true
  43. return
  44. end
  45. @user = authenticate_user(login, password)
  46. if @user
  47. Gitlab::ShellEnv.set_env(@user)
  48. @env['REMOTE_USER'] = @auth.username
  49. end
  50. end
  51. def gitlab_ci_request?(login, password)
  52. if login == "gitlab-ci-token" && project && project.gitlab_ci?
  53. token = project.gitlab_ci_service.token
  54. if token.present? && token == password && git_cmd == 'git-upload-pack'
  55. return true
  56. end
  57. end
  58. false
  59. end
  60. def oauth_access_token_check(login, password)
  61. if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
  62. token = Doorkeeper::AccessToken.by_token(password)
  63. token && token.accessible? && User.find_by(id: token.resource_owner_id)
  64. end
  65. end
  66. def authenticate_user(login, password)
  67. user = Gitlab::Auth.new.find(login, password)
  68. unless user
  69. user = oauth_access_token_check(login, password)
  70. end
  71. # If the user authenticated successfully, we reset the auth failure count
  72. # from Rack::Attack for that IP. A client may attempt to authenticate
  73. # with a username and blank password first, and only after it receives
  74. # a 401 error does it present a password. Resetting the count prevents
  75. # false positives from occurring.
  76. #
  77. # Otherwise, we let Rack::Attack know there was a failed authentication
  78. # attempt from this IP. This information is stored in the Rails cache
  79. # (Redis) and will be used by the Rack::Attack middleware to decide
  80. # whether to block requests from this IP.
  81. config = Gitlab.config.rack_attack.git_basic_auth
  82. if config.enabled
  83. if user
  84. # A successful login will reset the auth failure count from this IP
  85. Rack::Attack::Allow2Ban.reset(@request.ip, config)
  86. else
  87. banned = Rack::Attack::Allow2Ban.filter(@request.ip, config) do
  88. # Unless the IP is whitelisted, return true so that Allow2Ban
  89. # increments the counter (stored in Rails.cache) for the IP
  90. if config.ip_whitelist.include?(@request.ip)
  91. false
  92. else
  93. true
  94. end
  95. end
  96. if banned
  97. Rails.logger.info "IP #{@request.ip} failed to login " \
  98. "as #{login} but has been temporarily banned from Git auth"
  99. end
  100. end
  101. end
  102. user
  103. end
  104. def authorized_request?
  105. return true if @gitlab_ci
  106. case git_cmd
  107. when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
  108. if user
  109. Gitlab::GitAccess.new(user, project).download_access_check.allowed?
  110. elsif project.public?
  111. # Allow clone/fetch for public projects
  112. true
  113. else
  114. false
  115. end
  116. when *Gitlab::GitAccess::PUSH_COMMANDS
  117. if user
  118. # Skip user authorization on upload request.
  119. # It will be done by the pre-receive hook in the repository.
  120. true
  121. else
  122. false
  123. end
  124. else
  125. false
  126. end
  127. end
  128. def git_cmd
  129. if @request.get?
  130. @request.params['service']
  131. elsif @request.post?
  132. File.basename(@request.path)
  133. else
  134. nil
  135. end
  136. end
  137. def project
  138. return @project if defined?(@project)
  139. @project = project_by_path(@request.path_info)
  140. end
  141. def project_by_path(path)
  142. if m = /^([\w\.\/-]+)\.git/.match(path).to_a
  143. path_with_namespace = m.last
  144. path_with_namespace.gsub!(/\.wiki$/, '')
  145. path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
  146. Project.find_with_namespace(path_with_namespace)
  147. end
  148. end
  149. def render_grack_auth_ok
  150. [200, { "Content-Type" => "application/json" }, [JSON.dump({ 'GL_ID' => Gitlab::ShellEnv.gl_id(@user) })]]
  151. end
  152. def render_not_found
  153. [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
  154. end
  155. end
  156. end