diff --git a/app/controllers/jwt_controller.rb b/app/controllers/jwt_controller.rb
index ca02df28b9108b1e7cae83547fe32dc065012bfa..1b075cc5e2d4a42f4d458e7fd91f21ea3922d9ff 100644
--- a/app/controllers/jwt_controller.rb
+++ b/app/controllers/jwt_controller.rb
@@ -11,7 +11,7 @@ class JwtController < ApplicationController
     service = SERVICES[params[:service]]
     return head :not_found unless service
 
-    result = service.new(@project, @user, auth_params).execute(access_type: @access_type)
+    result = service.new(@project, @user, auth_params).execute(capabilities: @capabilities)
 
     render json: result, status: result[:http_status]
   end
@@ -20,12 +20,16 @@ class JwtController < ApplicationController
 
   def authenticate_project_or_user
     authenticate_with_http_basic do |login, password|
-      # if it's possible we first try to authenticate project with login and password
-      @project, @user, @access_type = authenticate_build(login, password)
-      return if @project
+      @auth_result = Gitlab::Auth.find_for_git_client(login, password, ip: request.ip)
 
-      @user, @access_type = authenticate_user(login, password)
-      return if @user
+      @user = auth_result.user
+      @project = auth_result.project
+      @type = auth_result.type
+      @capabilities = auth_result.capabilities || []
+
+      if @user || @project
+        return # Allow access
+      end
 
       render_403
     end
@@ -34,18 +38,4 @@ class JwtController < ApplicationController
   def auth_params
     params.permit(:service, :scope, :account, :client_id)
   end
-
-  def authenticate_build(login, password)
-    return unless login == 'gitlab-ci-token'
-    return unless password
-
-    build = Ci::Build.running.find_by(token: password)
-    return build.project, build.user, :restricted if build
-  end
-
-  def authenticate_user(login, password)
-    user = Gitlab::Auth.find_with_user_password(login, password)
-    Gitlab::Auth.rate_limit!(request.ip, success: user.present?, login: login)
-    return user, :full
-  end
 end
diff --git a/app/controllers/projects/git_http_client_controller.rb b/app/controllers/projects/git_http_client_controller.rb
index 0f72dc8437c5a730beaa3e7dd2b11e02be41d251..6870102c296a6ec6aec6cb674751f26b60a6336d 100644
--- a/app/controllers/projects/git_http_client_controller.rb
+++ b/app/controllers/projects/git_http_client_controller.rb
@@ -4,7 +4,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
   include ActionController::HttpAuthentication::Basic
   include KerberosSpnegoHelper
 
-  attr_reader :user, :access_type
+  attr_reader :user, :capabilities
 
   # Git clients will not know what authenticity token to send along
   skip_before_action :verify_authenticity_token
@@ -34,7 +34,7 @@ class Projects::GitHttpClientController < Projects::ApplicationController
         @user = auth_result.user
       end
 
-      @access_type = auth_result.access_type
+      @capabilities = auth_result.capabilities || []
 
       if ci? || user
         return # Allow access
@@ -120,12 +120,8 @@ class Projects::GitHttpClientController < Projects::ApplicationController
     @ci.present?
   end
 
-  def full?
-    @access_type == :full
-  end
-
-  def restricted?
-    @access_type == :restricted
+  def has_capability?(capability)
+    @capabilities.include?(capability)
   end
 
   def verify_workhorse_api!
diff --git a/app/controllers/projects/git_http_controller.rb b/app/controllers/projects/git_http_controller.rb
index d59a47417f45c7fdd1fe90219751189069a8f28b..89afaaed510c1e0e1b2c2eabaf066d30777153d9 100644
--- a/app/controllers/projects/git_http_controller.rb
+++ b/app/controllers/projects/git_http_controller.rb
@@ -86,7 +86,7 @@ class Projects::GitHttpController < Projects::GitHttpClientController
   end
 
   def access
-    @access ||= Gitlab::GitAccess.new(user, project, 'http', access_type: access_type)
+    @access ||= Gitlab::GitAccess.new(user, project, 'http', capabilities: capabilities)
   end
 
   def access_check
diff --git a/app/helpers/lfs_helper.rb b/app/helpers/lfs_helper.rb
index 625dfddcf8dde73d93a9d7af09b29f70fe3e8f1b..bee03ffb446ff953079ec4cdcf5f6ff3d9c89bcf 100644
--- a/app/helpers/lfs_helper.rb
+++ b/app/helpers/lfs_helper.rb
@@ -29,11 +29,11 @@ module LfsHelper
   end
 
   def privileged_user_can_download_code?
-    full? && user && user.can?(:download_code, project)
+    has_capability?(:download_code) && user && user.can?(:download_code, project)
   end
 
   def restricted_user_can_download_code?
-    restricted? && user && user.can?(:restricted_download_code, project)
+    has_capability?(:restricted_download_code) && user && user.can?(:restricted_download_code, project)
   end
 
   def lfs_upload_access?
@@ -43,7 +43,7 @@ module LfsHelper
   end
 
   def privileged_user_can_push_code?
-    full? && user && user.can?(:push_code, project)
+    has_capability?(:push_code) && user && user.can?(:push_code, project)
   end
 
   def render_lfs_forbidden
diff --git a/app/services/auth/container_registry_authentication_service.rb b/app/services/auth/container_registry_authentication_service.rb
index 270d5a11d9ee2b796edcc3a97c8c7673766c8e31..cba0e2297a8f87ea0269a2481bb774905dbda819 100644
--- a/app/services/auth/container_registry_authentication_service.rb
+++ b/app/services/auth/container_registry_authentication_service.rb
@@ -4,8 +4,8 @@ module Auth
 
     AUDIENCE = 'container_registry'
 
-    def execute(access_type: access_type)
-      @access_type = access_type
+    def execute(capabilities: capabilities)
+      @capabilities = capabilities
 
       return error('not found', 404) unless registry.enabled
 
@@ -91,33 +91,28 @@ module Auth
     private
 
     def restricted_user_can_pull?(requested_project)
-      return false unless restricted?
-
       # Restricted can:
       # 1. pull from it's own project (for ex. a build)
       # 2. read images from dependent projects if he is a team member
-      requested_project == project || can?(current_user, :restricted_read_container_image, requested_project)
+      requested_project == project ||
+        has_ability?(:restricted_read_container_image, requested_project)
     end
 
     def privileged_user_can_pull?(requested_project)
-      full? && can?(current_user, :read_container_image, requested_project)
+      has_ability?(:read_container_image, requested_project)
     end
 
     def restricted_user_can_push?(requested_project)
       # Restricted can push only to project to from which he originates
-      restricted? && requested_project == project
+      requested_project == project
     end
 
     def privileged_user_can_push?(requested_project)
-      full? && can?(current_user, :create_container_image, requested_project)
-    end
-
-    def full?
-      @access_type == :full
+      has_ability?(:create_container_image, requested_project)
     end
 
-    def restricted?
-      @access_type == :restricted
+    def has_ability?(ability, requested_project)
+      @capabilities.include?(ability) && can?(current_user, ability, requested_project)
     end
   end
 end
diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb
index e7bf8ee616622761956bcfa3ce751d6853a99d31..001917211a10e4bac6cc577f7f3d32ddaa332276 100644
--- a/lib/gitlab/auth.rb
+++ b/lib/gitlab/auth.rb
@@ -1,6 +1,6 @@
 module Gitlab
   module Auth
-    Result = Struct.new(:user, :type, :access_type)
+    Result = Struct.new(:user, :type, :project, :capabilities)
 
     class << self
       def find_for_git_client(login, password, project:, ip:)
@@ -9,7 +9,7 @@ module Gitlab
         result = Result.new
 
         if valid_ci_request?(login, password, project)
-          result.type = :ci
+          result = Result.new(nil, project, :ci, restricted_capabilities)
         else
           result = populate_result(login, password)
         end
@@ -81,7 +81,7 @@ module Gitlab
           personal_access_token_check(login, password)
 
         if result
-          result.type = nil unless result.user && result.type != :ci
+          result.type = nil unless result.capabilities
 
           if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap
             result.type = :missing_personal_token
@@ -93,7 +93,7 @@ module Gitlab
 
       def user_with_password_for_git(login, password)
         user = find_with_user_password(login, password)
-        Result.new(user, :gitlab_or_ldap, :full) if user
+        Result.new(user, :gitlab_or_ldap, nil, full_capabilities) if user
       end
 
       def oauth_access_token_check(login, password)
@@ -101,7 +101,7 @@ module Gitlab
           token = Doorkeeper::AccessToken.by_token(password)
           if token && token.accessible?
             user = User.find_by(id: token.resource_owner_id)
-            Result.new(user, :oauth, :full)
+            Result.new(user, nil, :oauth, full_capabilities)
           end
         end
       end
@@ -110,7 +110,7 @@ module Gitlab
         if login && password
           user = User.find_by_personal_access_token(password)
           validation = User.by_login(login)
-          Result.new(user, :personal_token, :full) if user == validation
+          Result.new(user, nil, :personal_token, full_capabilities) if user == validation
         end
       end
 
@@ -123,12 +123,31 @@ module Gitlab
 
         if build.user
           # If user is assigned to build, use restricted credentials of user
-          Result.new(build.user, :build, :restricted)
+          Result.new(build.user, build.project, :build, restricted_capabilities)
         else
           # Otherwise use generic CI credentials (backward compatibility)
-          Result.new(nil, :ci, :restricted)
+          Result.new(nil, build.project, :ci, restricted_capabilities)
         end
       end
+
+      private
+
+      def restricted_capabilities
+        [
+          :read_project,
+          :restricted_download_code,
+          :restricted_read_container_image
+        ]
+      end
+
+      def full_capabilities
+        restricted_capabilities + [
+          :download_code,
+          :push_code,
+          :read_container_image,
+          :update_container_image
+        ]
+      end
     end
   end
 end
diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb
index 5bd0134ed45cdaafb7fc0c174f583d4bd88f4d21..10ef4a1e3cf06524b71f77a113fd5094506aca67 100644
--- a/lib/gitlab/git_access.rb
+++ b/lib/gitlab/git_access.rb
@@ -5,13 +5,13 @@ module Gitlab
     DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }
     PUSH_COMMANDS = %w{ git-receive-pack }
 
-    attr_reader :actor, :project, :protocol, :user_access, :access_type
+    attr_reader :actor, :project, :protocol, :user_access, :capabilities
 
-    def initialize(actor, project, protocol, access_type: access_type)
+    def initialize(actor, project, protocol, capabilities: capabilities)
       @actor    = actor
       @project  = project
       @protocol = protocol
-      @access_type = access_type
+      @capabilities = capabilities
       @user_access = UserAccess.new(user, project: project)
     end
 
@@ -69,15 +69,15 @@ module Gitlab
     end
 
     def privileged_user_can_download_code?
-      access_type == :full && user_access.can_do_action?(:download_code)
+      capabilities.include?(:download_code) && user_access.can_do_action?(:download_code)
     end
 
     def restricted_user_can_download_code?
-      access_type == :restricted && user_access.can_do_action?(:restricted_download_code)
+      capabilities.include?(:restricted_download_code) && user_access.can_do_action?(:restricted_download_code)
     end
 
     def user_push_access_check(changes)
-      unless access_type == :full
+      unless capabilities.include?(:push_code)
         return build_status_object(false, "You are not allowed to upload code for this project.")
       end