BigW Consortium Gitlab

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

5
    REGISTRY_SCOPES = [:read_registry].freeze
6

7
    # Scopes used for GitLab API access
Douwe Maan committed
8
    API_SCOPES = [:api, :read_user, :sudo].freeze
9

10
    # Scopes used for OpenID Connect
11 12
    OPENID_SCOPES = [:openid].freeze

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

16
    class << self
17 18
      include Gitlab::CurrentSettings

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

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

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

37
        return result if result.success? || authenticate_using_internal_or_ldap_password?
38 39 40

        # If sign-in is disabled and LDAP is not configured, recommend a
        # personal access token on failed auth attempts
41
        raise Gitlab::Auth::MissingPersonalAccessTokenError
42 43
      end

44
      def find_with_user_password(login, password)
45 46 47
        # Avoid resource intensive login checks if password is not provided
        return unless password.present?

48 49 50 51
        # Nothing to do here if internal auth is disabled and LDAP is
        # not configured
        return unless authenticate_using_internal_or_ldap_password?

52 53
        Gitlab::Auth::UniqueIpsLimiter.limit_user! do
          user = User.by_login(login)
54

55 56 57 58 59
          # 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
            Gitlab::LDAP::Authentication.login(login, password)
60
          elsif current_application_settings.password_authentication_enabled_for_git?
61
            user if user.active? && user.valid_password?(password)
62
          end
63 64 65
        end
      end

Jacob Vosmaer committed
66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
      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

87 88
      private

89 90 91 92
      def authenticate_using_internal_or_ldap_password?
        current_application_settings.password_authentication_enabled_for_git? || Gitlab::LDAP::Config.enabled?
      end

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

96
        return unless project && matched_login.present?
97 98 99

        underscored_service = matched_login['service'].underscore

100
        if Service.available_services_names.include?(underscored_service)
101 102
          # We treat underscored_service as a trusted input because it is included
          # in the Service.available_services_names whitelist.
103
          service = project.public_send("#{underscored_service}_service") # rubocop:disable GitlabSecurity/PublicSend
104

105
          if service && service.activated? && service.valid_token?(password)
106
            Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities)
107 108 109 110 111 112
          end
        end
      end

      def user_with_password_for_git(login, password)
        user = find_with_user_password(login, password)
113 114
        return unless user

115
        raise Gitlab::Auth::MissingPersonalAccessTokenError if user.two_factor_enabled?
116

Z.J. van de Weg committed
117
        Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities)
118 119
      end

120 121 122
      def oauth_access_token_check(login, password)
        if login == "oauth2" && password.present?
          token = Doorkeeper::AccessToken.by_token(password)
123

124
          if valid_oauth_token?(token)
125
            user = User.find_by(id: token.resource_owner_id)
Z.J. van de Weg committed
126
            Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
127
          end
128 129
        end
      end
130

131 132
      def personal_access_token_check(password)
        return unless password.present?
133

134
        token = PersonalAccessTokensFinder.new(state: 'active').find_by(token: password)
135

136
        if token && valid_scoped_token?(token, available_scopes)
137
          Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
138 139
        end
      end
140

141
      def valid_oauth_token?(token)
142
        token && token.accessible? && valid_scoped_token?(token, [:api])
143 144
      end

Z.J. van de Weg committed
145
      def valid_scoped_token?(token, scopes)
146 147 148
        AccessTokenValidationService.new(token).include_any_scope?(scopes)
      end

149 150 151 152 153 154 155 156 157
      def abilities_for_scopes(scopes)
        abilities_by_scope = {
          api: full_authentication_abilities,
          read_registry: [:read_container_image]
        }

        scopes.flat_map do |scope|
          abilities_by_scope.fetch(scope.to_sym, [])
        end.uniq
158 159
      end

160
      def lfs_token_check(login, password, project)
161 162 163 164 165 166 167 168 169
        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

170
        return unless actor
171

172
        token_handler = Gitlab::LfsToken.new(actor)
173

174 175
        authentication_abilities =
          if token_handler.user?
Z.J. van de Weg committed
176
            full_authentication_abilities
177 178
          elsif token_handler.deploy_key_pushable?(project)
            read_write_authentication_abilities
179
          else
Z.J. van de Weg committed
180
            read_authentication_abilities
181 182
          end

183 184 185
        if Devise.secure_compare(token_handler.token, password)
          Gitlab::Auth::Result.new(actor, nil, token_handler.type, authentication_abilities)
        end
186 187
      end

188 189 190 191
      def build_access_token_check(login, password)
        return unless login == 'gitlab-ci-token'
        return unless password

192
        build = ::Ci::Build.running.find_by_token(password)
193
        return unless build
Kamil Trzcinski committed
194
        return unless build.project.builds_enabled?
195 196 197

        if build.user
          # If user is assigned to build, use restricted credentials of user
198
          Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
199 200
        else
          # Otherwise use generic CI credentials (backward compatibility)
201
          Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities)
202 203
        end
      end
204

205 206
      public

207
      def build_authentication_abilities
208 209
        [
          :read_project,
210 211 212
          :build_download_code,
          :build_read_container_image,
          :build_create_container_image
213 214 215
        ]
      end

Z.J. van de Weg committed
216
      def read_authentication_abilities
217 218
        [
          :read_project,
219
          :download_code,
220 221 222 223
          :read_container_image
        ]
      end

224
      def read_write_authentication_abilities
Z.J. van de Weg committed
225
        read_authentication_abilities + [
226
          :push_code,
227 228 229 230 231 232
          :create_container_image
        ]
      end

      def full_authentication_abilities
        read_write_authentication_abilities + [
233
          :admin_container_image
234 235
        ]
      end
236

Douwe Maan committed
237 238 239 240
      def available_scopes(current_user = nil)
        scopes = API_SCOPES + registry_scopes
        scopes.delete(:sudo) if current_user && !current_user.admin?
        scopes
241 242 243 244 245 246 247 248 249 250 251 252
      end

      # Other available scopes
      def optional_scopes
        available_scopes + OPENID_SCOPES - DEFAULT_SCOPES
      end

      def registry_scopes
        return [] unless Gitlab.config.registry.enabled

        REGISTRY_SCOPES
      end
253
    end
254 255
  end
end