/lib/gitlab/backend/grack_auth.rb
Ruby | 165 lines | 117 code | 32 blank | 16 comment | 30 complexity | c608b831e1d9bea0f92717803e6f2b48 MD5 | raw file
Possible License(s): CC-BY-3.0
- require_relative 'shell_env'
- module Grack
- class Auth < Rack::Auth::Basic
- attr_accessor :user, :project, :env
- def call(env)
- @env = env
- @request = Rack::Request.new(env)
- @auth = Request.new(env)
- @gitlab_ci = false
- # Need this patch due to the rails mount
- # Need this if under RELATIVE_URL_ROOT
- unless Gitlab.config.gitlab.relative_url_root.empty?
- # If website is mounted using relative_url_root need to remove it first
- @env['PATH_INFO'] = @request.path.sub(Gitlab.config.gitlab.relative_url_root,'')
- else
- @env['PATH_INFO'] = @request.path
- end
- @env['SCRIPT_NAME'] = ""
- auth!
- if project && authorized_request?
- @app.call(env)
- elsif @user.nil? && !@gitlab_ci
- unauthorized
- else
- render_not_found
- end
- end
- private
- def auth!
- return unless @auth.provided?
- return bad_request unless @auth.basic?
- # Authentication with username and password
- login, password = @auth.credentials
- # Allow authentication for GitLab CI service
- # if valid token passed
- if gitlab_ci_request?(login, password)
- @gitlab_ci = true
- return
- end
- @user = authenticate_user(login, password)
- if @user
- Gitlab::ShellEnv.set_env(@user)
- @env['REMOTE_USER'] = @auth.username
- end
- end
- def gitlab_ci_request?(login, password)
- if login == "gitlab-ci-token" && project && project.gitlab_ci?
- token = project.gitlab_ci_service.token
- if token.present? && token == password && git_cmd == 'git-upload-pack'
- return true
- end
- end
- false
- end
- def oauth_access_token_check(login, password)
- if login == "oauth2" && git_cmd == 'git-upload-pack' && password.present?
- token = Doorkeeper::AccessToken.by_token(password)
- token && token.accessible? && User.find_by(id: token.resource_owner_id)
- end
- end
- def authenticate_user(login, password)
- user = Gitlab::Auth.new.find(login, password)
- unless user
- user = oauth_access_token_check(login, password)
- end
- return user if user.present?
- # At this point, we know the credentials were wrong. We let Rack::Attack
- # know there was a failed authentication attempt from this IP. This
- # information is stored in the Rails cache (Redis) and will be used by
- # the Rack::Attack middleware to decide whether to block requests from
- # this IP.
- config = Gitlab.config.rack_attack.git_basic_auth
- Rack::Attack::Allow2Ban.filter(@request.ip, config) do
- # Unless the IP is whitelisted, return true so that Allow2Ban
- # increments the counter (stored in Rails.cache) for the IP
- if config.ip_whitelist.include?(@request.ip)
- false
- else
- true
- end
- end
- nil # No user was found
- end
- def authorized_request?
- return true if @gitlab_ci
- case git_cmd
- when *Gitlab::GitAccess::DOWNLOAD_COMMANDS
- if user
- Gitlab::GitAccess.new.download_access_check(user, project).allowed?
- elsif project.public?
- # Allow clone/fetch for public projects
- true
- else
- false
- end
- when *Gitlab::GitAccess::PUSH_COMMANDS
- if user
- # Skip user authorization on upload request.
- # It will be done by the pre-receive hook in the repository.
- true
- else
- false
- end
- else
- false
- end
- end
- def git_cmd
- if @request.get?
- @request.params['service']
- elsif @request.post?
- File.basename(@request.path)
- else
- nil
- end
- end
- def project
- return @project if defined?(@project)
- @project = project_by_path(@request.path_info)
- end
- def project_by_path(path)
- if m = /^([\w\.\/-]+)\.git/.match(path).to_a
- path_with_namespace = m.last
- path_with_namespace.gsub!(/\.wiki$/, '')
- path_with_namespace[0] = '' if path_with_namespace.start_with?('/')
- Project.find_with_namespace(path_with_namespace)
- end
- end
- def render_not_found
- [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
- end
- end
- end