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

/lib/gitlab/backend/grack_auth.rb

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