BigW Consortium Gitlab

auth.rb 6.52 KB
Newer Older
1
module Gitlab
2
  module Auth
3
    MissingPersonalTokenError = Class.new(StandardError)
4

5 6 7
    # Scopes used for GitLab API access
    API_SCOPES = [:api, :read_user].freeze

8
    # Scopes used for OpenID Connect
9 10
    OPENID_SCOPES = [:openid].freeze

11
    # Default scopes for OAuth applications that don't define their own
12
    DEFAULT_SCOPES = [:api].freeze
13 14

    # Other available scopes
15
    OPTIONAL_SCOPES = (API_SCOPES + OPENID_SCOPES - DEFAULT_SCOPES).freeze
16

17
    class << self
18
      def find_for_git_client(login, password, project:, ip:)
19 20
        raise "Must provide an IP for rate limiting" if ip.nil?

21 22 23
        # `user_with_password_for_git` should be the last check
        # because it's the most expensive, especially when LDAP
        # is enabled.
24
        result =
25
          service_request_check(login, password, project) ||
26
          build_access_token_check(login, password) ||
27
          lfs_token_check(login, password) ||
28 29
          oauth_access_token_check(login, password) ||
          user_with_password_for_git(login, password) ||
30
          personal_access_token_check(password) ||
31
          Gitlab::Auth::Result.new
32

33
        rate_limit!(ip, success: result.success?, login: login)
34
        Gitlab::Auth::UniqueIpsLimiter.limit_user!(result.actor)
35

36
        result
37 38
      end

39
      def find_with_user_password(login, password)
40 41
        Gitlab::Auth::UniqueIpsLimiter.limit_user! do
          user = User.by_login(login)
42

43 44 45 46 47
          # 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?
48

49 50
            Gitlab::LDAP::Authentication.login(login, password)
          else
51
            user if user.active? && user.valid_password?(password)
52
          end
53 54 55
        end
      end

Jacob Vosmaer committed
56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76
      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

77 78
      private

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

82
        return unless project && matched_login.present?
83 84 85

        underscored_service = matched_login['service'].underscore

86
        if Service.available_services_names.include?(underscored_service)
87 88
          # We treat underscored_service as a trusted input because it is included
          # in the Service.available_services_names whitelist.
Jacob Vosmaer committed
89
          service = project.public_send("#{underscored_service}_service")
90

91
          if service && service.activated? && service.valid_token?(password)
92
            Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
93 94 95 96 97 98
          end
        end
      end

      def user_with_password_for_git(login, password)
        user = find_with_user_password(login, password)
99 100
        return unless user

101
        raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled?
102

103
        Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
104 105
      end

106 107 108
      def oauth_access_token_check(login, password)
        if login == "oauth2" && password.present?
          token = Doorkeeper::AccessToken.by_token(password)
109
          if valid_oauth_token?(token)
110
            user = User.find_by(id: token.resource_owner_id)
111
            Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
112
          end
113 114
        end
      end
115

116 117
      def personal_access_token_check(password)
        return unless password.present?
118

119
        token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
120

121
        if token && valid_api_token?(token)
122
          Gitlab::Auth::Result.new(token.user, nil, :personal_token, full_authentication_abilities)
123 124
        end
      end
125

126
      def valid_oauth_token?(token)
127
        token && token.accessible? && valid_api_token?(token)
128 129
      end

130
      def valid_api_token?(token)
131
        AccessTokenValidationService.new(token).include_any_scope?(['api'])
132 133
      end

134 135 136 137 138 139 140 141 142 143
      def lfs_token_check(login, password)
        deploy_key_matches = login.match(/\Alfs\+deploy-key-(\d+)\z/)

        actor =
          if deploy_key_matches
            DeployKey.find(deploy_key_matches[1])
          else
            User.by_login(login)
          end

144
        return unless actor
145

146
        token_handler = Gitlab::LfsToken.new(actor)
147

148 149 150 151 152 153 154
        authentication_abilities =
          if token_handler.user?
            full_authentication_abilities
          else
            read_authentication_abilities
          end

155 156 157
        if Devise.secure_compare(token_handler.token, password)
          Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
        end
158 159
      end

160 161 162 163
      def build_access_token_check(login, password)
        return unless login == 'gitlab-ci-token'
        return unless password

164
        build = ::Ci::Build.running.find_by_token(password)
165
        return unless build
Kamil Trzcinski committed
166
        return unless build.project.builds_enabled?
167 168 169

        if build.user
          # If user is assigned to build, use restricted credentials of user
170
          Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
171 172
        else
          # Otherwise use generic CI credentials (backward compatibility)
173
          Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
174 175
        end
      end
176

177 178
      public

179
      def build_authentication_abilities
180 181
        [
          :read_project,
182 183 184
          :build_download_code,
          :build_read_container_image,
          :build_create_container_image
185 186 187
        ]
      end

188
      def read_authentication_abilities
189 190
        [
          :read_project,
191
          :download_code,
192 193 194 195
          :read_container_image
        ]
      end

196 197
      def full_authentication_abilities
        read_authentication_abilities + [
198
          :push_code,
199
          :create_container_image
200 201
        ]
      end
202
    end
203 204
  end
end