auth.rb 4.27 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1
module Gitlab
2
  module Auth
3 4
    Result = Struct.new(:user, :type)

5
    class << self
6
      def find_for_git_client(login, password, project:, ip:)
7 8
        raise "Must provide an IP for rate limiting" if ip.nil?

9
        result = Result.new
10 11

        if valid_ci_request?(login, password, project)
12
          result.type = :ci
13
        else
14
          result = populate_result(login, password)
15 16
        end

17 18
        success = result.user.present? || [:ci, :missing_personal_token].include?(result.type)
        rate_limit!(ip, success: success, login: login)
19
        result
20 21
      end

22
      def find_with_user_password(login, password)
23 24 25 26 27 28 29 30 31 32 33 34 35 36
        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

Jacob Vosmaer's avatar
Jacob Vosmaer committed
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
      def rate_limit!(ip, success:, login:)
        rate_limiter = Gitlab::Auth::IpRateLimiter.new(ip)
        return unless rate_limiter.enabled?

        if success
          # Repeated login 'failures' are normal behavior for some Git clients so
          # it is important to reset the ban counter once the client has proven
          # they are not a 'bad guy'.
          rate_limiter.reset!
        else
          # Register a login failure so that Rack::Attack can block the next
          # request from this IP if needed.
          rate_limiter.register_fail!

          if rate_limiter.banned?
            Rails.logger.info "IP #{ip} failed to login " \
              "as #{login} but has been temporarily banned from Git auth"
          end
        end
      end

58 59 60 61 62 63 64 65 66 67 68 69 70 71
      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.
Jacob Vosmaer's avatar
Jacob Vosmaer committed
72
          service = project.public_send("#{underscored_service}_service")
73 74 75 76 77

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

78 79 80 81
      def populate_result(login, password)
        result =
          user_with_password_for_git(login, password) ||
          oauth_access_token_check(login, password) ||
Patricio Cano's avatar
Patricio Cano committed
82
          lfs_token_check(login, password) ||
83 84 85 86 87
          personal_access_token_check(login, password)

        if result
          result.type = nil unless result.user

Patricio Cano's avatar
Patricio Cano committed
88
          if result.user && result.type == :gitlab_or_ldap && result.user.two_factor_enabled?
89 90 91 92 93 94 95 96 97 98 99 100
            result.type = :missing_personal_token
          end
        end

        result || Result.new
      end

      def user_with_password_for_git(login, password)
        user = find_with_user_password(login, password)
        Result.new(user, :gitlab_or_ldap) if user
      end

101 102 103
      def oauth_access_token_check(login, password)
        if login == "oauth2" && password.present?
          token = Doorkeeper::AccessToken.by_token(password)
104 105
          if token && token.accessible?
            user = User.find_by(id: token.resource_owner_id)
106
            Result.new(user, :oauth)
107
          end
108 109
        end
      end
110 111 112 113

      def personal_access_token_check(login, password)
        if login && password
          user = User.find_by_personal_access_token(password)
114
          validation = User.by_login(login)
115
          Result.new(user, :personal_token) if user == validation
116 117
        end
      end
Patricio Cano's avatar
Patricio Cano committed
118 119

      def lfs_token_check(login, password)
120
        actor =
Patricio Cano's avatar
Patricio Cano committed
121 122
          if login.start_with?('lfs-deploy-key')
            DeployKey.find(login.sub('lfs-deploy-key-', ''))
123 124 125 126 127 128 129
          else
            User.by_login(login)
          end

        token_handler = Gitlab::LfsToken.new(actor)

        Result.new(actor, token_handler.type) if actor && token_handler.value == password
Patricio Cano's avatar
Patricio Cano committed
130
      end
131
    end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
132 133
  end
end