190 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			190 lines
		
	
	
		
			5.3 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# frozen_string_literal: true
 | 
						|
 | 
						|
module Gitlab
 | 
						|
  module APIAuthentication
 | 
						|
    class TokenResolver
 | 
						|
      include ActiveModel::Validations
 | 
						|
 | 
						|
      attr_reader :token_type
 | 
						|
 | 
						|
      validates :token_type, inclusion: {
 | 
						|
        in: %i[
 | 
						|
          personal_access_token_with_username
 | 
						|
          job_token_with_username
 | 
						|
          deploy_token_with_username
 | 
						|
          personal_access_token
 | 
						|
          job_token
 | 
						|
          deploy_token
 | 
						|
          personal_access_token_from_jwt
 | 
						|
          deploy_token_from_jwt
 | 
						|
          job_token_from_jwt
 | 
						|
        ]
 | 
						|
      }
 | 
						|
 | 
						|
      UsernameAndPassword = ::Gitlab::APIAuthentication::TokenLocator::UsernameAndPassword
 | 
						|
 | 
						|
      def initialize(token_type)
 | 
						|
        @token_type = token_type
 | 
						|
        validate!
 | 
						|
      end
 | 
						|
 | 
						|
      # Existing behavior is known to be inconsistent across authentication
 | 
						|
      # methods with regards to whether to silently ignore present but invalid
 | 
						|
      # credentials or to raise an error/respond with 401.
 | 
						|
      #
 | 
						|
      # If a token can be located from the provided credentials, but the token
 | 
						|
      # or credentials are in some way invalid, this implementation opts to
 | 
						|
      # raise an error.
 | 
						|
      #
 | 
						|
      # For example, if the raw credentials include a username and password, and
 | 
						|
      # a token is resolved from the password, but the username does not match
 | 
						|
      # the token, an error will be raised.
 | 
						|
      #
 | 
						|
      # See https://gitlab.com/gitlab-org/gitlab/-/issues/246569
 | 
						|
 | 
						|
      def resolve(raw)
 | 
						|
        case @token_type
 | 
						|
        when :personal_access_token
 | 
						|
          resolve_personal_access_token raw
 | 
						|
 | 
						|
        when :job_token
 | 
						|
          resolve_job_token raw
 | 
						|
 | 
						|
        when :deploy_token
 | 
						|
          resolve_deploy_token raw
 | 
						|
 | 
						|
        when :personal_access_token_with_username
 | 
						|
          resolve_personal_access_token_with_username raw
 | 
						|
 | 
						|
        when :job_token_with_username
 | 
						|
          resolve_job_token_with_username raw
 | 
						|
 | 
						|
        when :deploy_token_with_username
 | 
						|
          resolve_deploy_token_with_username raw
 | 
						|
 | 
						|
        when :personal_access_token_from_jwt
 | 
						|
          resolve_personal_access_token_from_jwt raw
 | 
						|
 | 
						|
        when :deploy_token_from_jwt
 | 
						|
          resolve_deploy_token_from_jwt raw
 | 
						|
 | 
						|
        when :job_token_from_jwt
 | 
						|
          resolve_job_token_from_jwt raw
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      private
 | 
						|
 | 
						|
      def resolve_personal_access_token_with_username(raw)
 | 
						|
        raise ::Gitlab::Auth::UnauthorizedError unless raw.username
 | 
						|
 | 
						|
        with_personal_access_token(raw) do |pat|
 | 
						|
          break unless pat
 | 
						|
 | 
						|
          # Ensure that the username matches the token. This check is a subtle
 | 
						|
          # departure from the existing behavior of #find_personal_access_token_from_http_basic_auth.
 | 
						|
          # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_435907856
 | 
						|
          raise ::Gitlab::Auth::UnauthorizedError unless pat.user.username == raw.username
 | 
						|
 | 
						|
          pat
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_job_token_with_username(raw)
 | 
						|
        # Only look for a job if the username is correct
 | 
						|
        return if ::Gitlab::Auth::CI_JOB_USER != raw.username
 | 
						|
 | 
						|
        with_job_token(raw) do |job|
 | 
						|
          job
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_deploy_token_with_username(raw)
 | 
						|
        with_deploy_token(raw) do |token|
 | 
						|
          break unless token
 | 
						|
 | 
						|
          # Ensure that the username matches the token. This check is a subtle
 | 
						|
          # departure from the existing behavior of #deploy_token_from_request.
 | 
						|
          # https://gitlab.com/gitlab-org/gitlab/-/merge_requests/38627#note_474826205
 | 
						|
          raise ::Gitlab::Auth::UnauthorizedError unless token.username == raw.username
 | 
						|
 | 
						|
          token
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_personal_access_token(raw)
 | 
						|
        with_personal_access_token(raw) do |pat|
 | 
						|
          pat
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_job_token(raw)
 | 
						|
        with_job_token(raw) do |job|
 | 
						|
          job
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_deploy_token(raw)
 | 
						|
        with_deploy_token(raw) do |token|
 | 
						|
          token
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_personal_access_token_from_jwt(raw)
 | 
						|
        with_jwt_token(raw) do |jwt_token|
 | 
						|
          break unless jwt_token['token'].is_a?(Integer)
 | 
						|
 | 
						|
          pat = ::PersonalAccessToken.find(jwt_token['token'])
 | 
						|
          break unless pat
 | 
						|
 | 
						|
          pat
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_deploy_token_from_jwt(raw)
 | 
						|
        with_jwt_token(raw) do |jwt_token|
 | 
						|
          break unless jwt_token['token'].is_a?(String)
 | 
						|
 | 
						|
          resolve_deploy_token(UsernameAndPassword.new(nil, jwt_token['token']))
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def resolve_job_token_from_jwt(raw)
 | 
						|
        with_jwt_token(raw) do |jwt_token|
 | 
						|
          break unless jwt_token['token'].is_a?(String)
 | 
						|
 | 
						|
          resolve_job_token(UsernameAndPassword.new(nil, jwt_token['token']))
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def with_personal_access_token(raw, &block)
 | 
						|
        pat = ::PersonalAccessToken.find_by_token(raw.password)
 | 
						|
        return unless pat
 | 
						|
 | 
						|
        yield(pat)
 | 
						|
      end
 | 
						|
 | 
						|
      def with_deploy_token(raw, &block)
 | 
						|
        token = ::DeployToken.active.find_by_token(raw.password)
 | 
						|
        return unless token
 | 
						|
 | 
						|
        yield(token)
 | 
						|
      end
 | 
						|
 | 
						|
      def with_job_token(raw, &block)
 | 
						|
        job = ::Ci::AuthJobFinder.new(token: raw.password).execute
 | 
						|
        raise ::Gitlab::Auth::UnauthorizedError unless job
 | 
						|
 | 
						|
        yield(job)
 | 
						|
      end
 | 
						|
 | 
						|
      def with_jwt_token(raw, &block)
 | 
						|
        jwt_token = ::Gitlab::JWTToken.decode(raw.password)
 | 
						|
        raise ::Gitlab::Auth::UnauthorizedError unless jwt_token
 | 
						|
 | 
						|
        yield(jwt_token)
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |