PageRenderTime 46ms CodeModel.GetById 19ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/gitlab/backend/grack_auth.rb

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