PageRenderTime 47ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/lib/gitlab/backend/grack_auth.rb

https://gitlab.com/ewiltshi/gitlab-ce
Ruby | 165 lines | 117 code | 32 blank | 16 comment | 30 complexity | c608b831e1d9bea0f92717803e6f2b48 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. return user if user.present?
  67. # At this point, we know the credentials were wrong. We let Rack::Attack
  68. # know there was a failed authentication attempt from this IP. This
  69. # information is stored in the Rails cache (Redis) and will be used by
  70. # the Rack::Attack middleware to decide whether to block requests from
  71. # this IP.
  72. config = Gitlab.config.rack_attack.git_basic_auth
  73. Rack::Attack::Allow2Ban.filter(@request.ip, config) do
  74. # Unless the IP is whitelisted, return true so that Allow2Ban
  75. # increments the counter (stored in Rails.cache) for the IP
  76. if config.ip_whitelist.include?(@request.ip)
  77. false
  78. else
  79. true
  80. end
  81. end
  82. nil # No user was found
  83. end
  84. def authorized_request?
  85. return true if @gitlab_ci
  86. case git_cmd
  87. when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
  88. if user
  89. Gitlab::GitAccess.new.download_access_check(user, project).allowed?
  90. elsif project.public?
  91. # Allow clone/fetch for public projects
  92. true
  93. else
  94. false
  95. end
  96. when *Gitlab::GitAccess::PUSH_COMMANDS
  97. if user
  98. # Skip user authorization on upload request.
  99. # It will be done by the pre-receive hook in the repository.
  100. true
  101. else
  102. false
  103. end
  104. else
  105. false
  106. end
  107. end
  108. def git_cmd
  109. if @request.get?
  110. @request.params['service']
  111. elsif @request.post?
  112. File.basename(@request.path)
  113. else
  114. nil
  115. end
  116. end
  117. def project
  118. return @project if defined?(@project)
  119. @project = project_by_path(@request.path_info)
  120. end
  121. def project_by_path(path)
  122. if m = /^([\w\.\/-]+)\.git/.match(path).to_a
  123. path_with_namespace = m.last
  124. path_with_namespace.gsub!(/\.wiki$/, '')
  125. path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
  126. Project.find_with_namespace(path_with_namespace)
  127. end
  128. end
  129. def render_not_found
  130. [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
  131. end
  132. end
  133. end