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