/lib/gitlab/backend/grack_auth.rb
Ruby | 160 lines | 115 code | 29 blank | 16 comment | 26 complexity | 0c165fddabdbf0b2d068cf2b99301f3b 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)
- # 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'] = ""
- if project
- auth!
- else
- render_not_found
- end
- end
- private
- def auth!
- if @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)
- return @app.call(env)
- end
- @user = authenticate_user(login, password)
- if @user
- Gitlab::ShellEnv.set_env(@user)
- @env['REMOTE_USER'] = @auth.username
- end
- end
- if authorized_request?
- @app.call(env)
- else
- unauthorized
- end
- end
- def gitlab_ci_request?(login, password)
- if login == "gitlab-ci-token" && 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?
- 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
- @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$/, '')
- Project.find_with_namespace(path_with_namespace)
- end
- end
- def render_not_found
- [404, { "Content-Type" => "text/plain" }, ["Not Found"]]
- end
- end
- end