PageRenderTime 47ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/gitlab/backend/grack_auth.rb

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