auth.rb 3.6 KB
Newer Older
D
Dmitriy Zaporozhets 已提交
1 2
module Gitlab
  class Auth
3 4 5 6
    class << self
      def find(login, password, project:, ip:)
        raise "Must provide an IP for rate limiting" if ip.nil?

J
Jacob Vosmaer 已提交
7 8
        user = nil
        type = nil
9 10 11

        if valid_ci_request?(login, password, project)
          type = :ci
12
        elsif user = find_in_gitlab_or_ldap(login, password)
13 14 15 16 17 18 19 20 21
          type = :master_or_ldap
        elsif user = oauth_access_token_check(login, password)
          type = :oauth
        end

        rate_limit!(ip, success: !!user || (type == :ci), login: login)
        [user, type]
      end

22
      def find_in_gitlab_or_ldap(login, password)
23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98
        user = User.by_login(login)

        # If no user is found, or it's an LDAP server, try LDAP.
        #   LDAP users are only authenticated via LDAP
        if user.nil? || user.ldap_user?
          # Second chance - try LDAP authentication
          return nil unless Gitlab::LDAP::Config.enabled?

          Gitlab::LDAP::Authentication.login(login, password)
        else
          user if user.valid_password?(password)
        end
      end

      private

      def valid_ci_request?(login, password, project)
        matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login)

        return false unless project && matched_login.present?

        underscored_service = matched_login['service'].underscore

        if underscored_service == 'gitlab_ci'
          project && project.valid_build_token?(password)
        elsif Service.available_services_names.include?(underscored_service)
          # We treat underscored_service as a trusted input because it is included
          # in the Service.available_services_names whitelist.
          service_method = "#{underscored_service}_service"
          service = project.send(service_method)

          service && service.activated? && service.valid_token?(password)
        end
      end

      def oauth_access_token_check(login, password)
        if login == "oauth2" && password.present?
          token = Doorkeeper::AccessToken.by_token(password)
          token && token.accessible? && User.find_by(id: token.resource_owner_id)
        end
      end

      def rate_limit!(ip, success:, login:)
        # If the user authenticated successfully, we reset the auth failure count
        # from Rack::Attack for that IP. A client may attempt to authenticate
        # with a username and blank password first, and only after it receives
        # a 401 error does it present a password. Resetting the count prevents
        # false positives from occurring.
        #
        # Otherwise, 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
        return unless config.enabled

        if success
          # A successful login will reset the auth failure count from this IP
          Rack::Attack::Allow2Ban.reset(ip, config)
        else
          banned = Rack::Attack::Allow2Ban.filter(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?(ip)
              false
            else
              true
            end
          end

          if banned
            Rails.logger.info "IP #{ip} failed to login " \
              "as #{login} but has been temporarily banned from Git auth"
          end
        end
99 100
      end
    end
D
Dmitriy Zaporozhets 已提交
101 102
  end
end