diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/api/internal.rb | 12 | ||||
-rw-r--r-- | lib/ci/api/entities.rb | 9 | ||||
-rw-r--r-- | lib/ci/api/helpers.rb | 14 | ||||
-rw-r--r-- | lib/ci/gitlab_ci_yaml_processor.rb | 3 | ||||
-rw-r--r-- | lib/ci/mask_secret.rb | 9 | ||||
-rw-r--r-- | lib/expand_variables.rb | 17 | ||||
-rw-r--r-- | lib/gitlab/auth.rb | 98 | ||||
-rw-r--r-- | lib/gitlab/auth/result.rb | 13 | ||||
-rw-r--r-- | lib/gitlab/ci/config/node/environment.rb | 68 | ||||
-rw-r--r-- | lib/gitlab/ci/config/node/job.rb | 41 | ||||
-rw-r--r-- | lib/gitlab/contributions_calendar.rb | 17 | ||||
-rw-r--r-- | lib/gitlab/git_access.rb | 19 | ||||
-rw-r--r-- | lib/gitlab/import_export.rb | 3 | ||||
-rw-r--r-- | lib/gitlab/import_export/import_export.yml | 4 | ||||
-rw-r--r-- | lib/gitlab/import_export/relation_factory.rb | 28 | ||||
-rw-r--r-- | lib/gitlab/import_export/version_checker.rb | 4 | ||||
-rw-r--r-- | lib/gitlab/regex.rb | 4 |
17 files changed, 279 insertions, 84 deletions
diff --git a/lib/api/internal.rb b/lib/api/internal.rb index 6e6efece7c4..1114fd21784 100644 --- a/lib/api/internal.rb +++ b/lib/api/internal.rb @@ -35,6 +35,14 @@ module API Project.find_with_namespace(project_path) end end + + def ssh_authentication_abilities + [ + :read_project, + :download_code, + :push_code + ] + end end post "/allowed" do @@ -51,9 +59,9 @@ module API access = if wiki? - Gitlab::GitAccessWiki.new(actor, project, protocol) + Gitlab::GitAccessWiki.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) else - Gitlab::GitAccess.new(actor, project, protocol) + Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) end access_status = access.check(params[:action], params[:changes]) diff --git a/lib/ci/api/entities.rb b/lib/ci/api/entities.rb index 3f5bdaba3f5..66c05773b68 100644 --- a/lib/ci/api/entities.rb +++ b/lib/ci/api/entities.rb @@ -15,6 +15,15 @@ module Ci expose :filename, :size end + class BuildOptions < Grape::Entity + expose :image + expose :services + expose :artifacts + expose :cache + expose :dependencies + expose :after_script + end + class Build < Grape::Entity expose :id, :ref, :tag, :sha, :status expose :name, :token, :stage diff --git a/lib/ci/api/helpers.rb b/lib/ci/api/helpers.rb index ba80c89df78..23353c62885 100644 --- a/lib/ci/api/helpers.rb +++ b/lib/ci/api/helpers.rb @@ -14,12 +14,20 @@ module Ci end def authenticate_build_token!(build) - token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s - forbidden! unless token && build.valid_token?(token) + forbidden! unless build_token_valid?(build) end def runner_registration_token_valid? - params[:token] == current_application_settings.runners_registration_token + ActiveSupport::SecurityUtils.variable_size_secure_compare( + params[:token], + current_application_settings.runners_registration_token) + end + + def build_token_valid?(build) + token = (params[BUILD_TOKEN_PARAM] || env[BUILD_TOKEN_HEADER]).to_s + + # We require to also check `runners_token` to maintain compatibility with old version of runners + token && (build.valid_token?(token) || build.project.valid_runners_token?(token)) end def update_runner_last_contact(save: true) diff --git a/lib/ci/gitlab_ci_yaml_processor.rb b/lib/ci/gitlab_ci_yaml_processor.rb index caa815f720f..0369e80312a 100644 --- a/lib/ci/gitlab_ci_yaml_processor.rb +++ b/lib/ci/gitlab_ci_yaml_processor.rb @@ -60,7 +60,7 @@ module Ci name: job[:name].to_s, allow_failure: job[:allow_failure] || false, when: job[:when] || 'on_success', - environment: job[:environment], + environment: job[:environment_name], yaml_variables: yaml_variables(name), options: { image: job[:image], @@ -69,6 +69,7 @@ module Ci cache: job[:cache], dependencies: job[:dependencies], after_script: job[:after_script], + environment: job[:environment], }.compact } end diff --git a/lib/ci/mask_secret.rb b/lib/ci/mask_secret.rb new file mode 100644 index 00000000000..3da04edde70 --- /dev/null +++ b/lib/ci/mask_secret.rb @@ -0,0 +1,9 @@ +module Ci::MaskSecret + class << self + def mask(value, token) + return value unless value.present? && token.present? + + value.gsub(token, 'x' * token.length) + end + end +end diff --git a/lib/expand_variables.rb b/lib/expand_variables.rb new file mode 100644 index 00000000000..7b1533d0d32 --- /dev/null +++ b/lib/expand_variables.rb @@ -0,0 +1,17 @@ +module ExpandVariables + class << self + def expand(value, variables) + # Convert hash array to variables + if variables.is_a?(Array) + variables = variables.reduce({}) do |hash, variable| + hash[variable[:key]] = variable[:value] + hash + end + end + + value.gsub(/\$([a-zA-Z_][a-zA-Z0-9_]*)|\${\g<1>}|%\g<1>%/) do + variables[$1 || $2] + end + end + end +end diff --git a/lib/gitlab/auth.rb b/lib/gitlab/auth.rb index 91f0270818a..0a0f1c3b17b 100644 --- a/lib/gitlab/auth.rb +++ b/lib/gitlab/auth.rb @@ -1,21 +1,21 @@ module Gitlab module Auth - Result = Struct.new(:user, :type) + class MissingPersonalTokenError < StandardError; end class << self def find_for_git_client(login, password, project:, ip:) raise "Must provide an IP for rate limiting" if ip.nil? - result = Result.new + result = + service_request_check(login, password, project) || + build_access_token_check(login, password) || + user_with_password_for_git(login, password) || + oauth_access_token_check(login, password) || + personal_access_token_check(login, password) || + Gitlab::Auth::Result.new - if valid_ci_request?(login, password, project) - result.type = :ci - else - result = populate_result(login, password) - end + rate_limit!(ip, success: result.success?, login: login) - success = result.user.present? || [:ci, :missing_personal_token].include?(result.type) - rate_limit!(ip, success: success, login: login) result end @@ -57,44 +57,31 @@ module Gitlab private - def valid_ci_request?(login, password, project) + def service_request_check(login, password, project) matched_login = /(?<service>^[a-zA-Z]*-ci)-token$/.match(login) - return false unless project && matched_login.present? + return unless project && matched_login.present? underscored_service = matched_login['service'].underscore - if underscored_service == 'gitlab_ci' - project && project.valid_build_token?(password) - elsif Service.available_services_names.include?(underscored_service) + if Service.available_services_names.include?(underscored_service) # We treat underscored_service as a trusted input because it is included # in the Service.available_services_names whitelist. service = project.public_send("#{underscored_service}_service") - service && service.activated? && service.valid_token?(password) - end - end - - def populate_result(login, password) - result = - user_with_password_for_git(login, password) || - oauth_access_token_check(login, password) || - personal_access_token_check(login, password) - - if result - result.type = nil unless result.user - - if result.user && result.user.two_factor_enabled? && result.type == :gitlab_or_ldap - result.type = :missing_personal_token + if service && service.activated? && service.valid_token?(password) + Gitlab::Auth::Result.new(nil, project, :ci, build_authentication_abilities) end end - - result || Result.new end def user_with_password_for_git(login, password) user = find_with_user_password(login, password) - Result.new(user, :gitlab_or_ldap) if user + return unless user + + raise Gitlab::Auth::MissingPersonalTokenError if user.two_factor_enabled? + + Gitlab::Auth::Result.new(user, nil, :gitlab_or_ldap, full_authentication_abilities) end def oauth_access_token_check(login, password) @@ -102,7 +89,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) + Gitlab::Auth::Result.new(user, nil, :oauth, read_authentication_abilities) end end end @@ -111,9 +98,52 @@ module Gitlab if login && password user = User.find_by_personal_access_token(password) validation = User.by_login(login) - Result.new(user, :personal_token) if user == validation + Gitlab::Auth::Result.new(user, nil, :personal_token, full_authentication_abilities) if user.present? && user == validation + end + end + + def build_access_token_check(login, password) + return unless login == 'gitlab-ci-token' + return unless password + + build = ::Ci::Build.running.find_by_token(password) + return unless build + return unless build.project.builds_enabled? + + if build.user + # If user is assigned to build, use restricted credentials of user + Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities) + else + # Otherwise use generic CI credentials (backward compatibility) + Gitlab::Auth::Result.new(nil, build.project, :ci, build_authentication_abilities) end end + + public + + def build_authentication_abilities + [ + :read_project, + :build_download_code, + :build_read_container_image, + :build_create_container_image + ] + end + + def read_authentication_abilities + [ + :read_project, + :download_code, + :read_container_image + ] + end + + def full_authentication_abilities + read_authentication_abilities + [ + :push_code, + :create_container_image + ] + end end end end diff --git a/lib/gitlab/auth/result.rb b/lib/gitlab/auth/result.rb new file mode 100644 index 00000000000..bf625649cbf --- /dev/null +++ b/lib/gitlab/auth/result.rb @@ -0,0 +1,13 @@ +module Gitlab + module Auth + Result = Struct.new(:actor, :project, :type, :authentication_abilities) do + def ci? + type == :ci + end + + def success? + actor.present? || type == :ci + end + end + end +end diff --git a/lib/gitlab/ci/config/node/environment.rb b/lib/gitlab/ci/config/node/environment.rb new file mode 100644 index 00000000000..d388ab6b879 --- /dev/null +++ b/lib/gitlab/ci/config/node/environment.rb @@ -0,0 +1,68 @@ +module Gitlab + module Ci + class Config + module Node + ## + # Entry that represents an environment. + # + class Environment < Entry + include Validatable + + ALLOWED_KEYS = %i[name url] + + validations do + validate do + unless hash? || string? + errors.add(:config, 'should be a hash or a string') + end + end + + validates :name, presence: true + validates :name, + type: { + with: String, + message: Gitlab::Regex.environment_name_regex_message } + + validates :name, + format: { + with: Gitlab::Regex.environment_name_regex, + message: Gitlab::Regex.environment_name_regex_message } + + with_options if: :hash? do + validates :config, allowed_keys: ALLOWED_KEYS + + validates :url, + length: { maximum: 255 }, + addressable_url: true, + allow_nil: true + end + end + + def hash? + @config.is_a?(Hash) + end + + def string? + @config.is_a?(String) + end + + def name + value[:name] + end + + def url + value[:url] + end + + def value + case @config + when String then { name: @config } + when Hash then @config + else {} + end + end + end + end + end + end +end diff --git a/lib/gitlab/ci/config/node/job.rb b/lib/gitlab/ci/config/node/job.rb index 0cbdf7619c0..603334d6793 100644 --- a/lib/gitlab/ci/config/node/job.rb +++ b/lib/gitlab/ci/config/node/job.rb @@ -13,7 +13,7 @@ module Gitlab type stage when artifacts cache dependencies before_script after_script variables environment] - attributes :tags, :allow_failure, :when, :environment, :dependencies + attributes :tags, :allow_failure, :when, :dependencies validations do validates :config, allowed_keys: ALLOWED_KEYS @@ -29,58 +29,53 @@ module Gitlab inclusion: { in: %w[on_success on_failure always manual], message: 'should be on_success, on_failure, ' \ 'always or manual' } - validates :environment, - type: { - with: String, - message: Gitlab::Regex.environment_name_regex_message } - validates :environment, - format: { - with: Gitlab::Regex.environment_name_regex, - message: Gitlab::Regex.environment_name_regex_message } validates :dependencies, array_of_strings: true end end - node :before_script, Script, + node :before_script, Node::Script, description: 'Global before script overridden in this job.' - node :script, Commands, + node :script, Node::Commands, description: 'Commands that will be executed in this job.' - node :stage, Stage, + node :stage, Node::Stage, description: 'Pipeline stage this job will be executed into.' - node :type, Stage, + node :type, Node::Stage, description: 'Deprecated: stage this job will be executed into.' - node :after_script, Script, + node :after_script, Node::Script, description: 'Commands that will be executed when finishing job.' - node :cache, Cache, + node :cache, Node::Cache, description: 'Cache definition for this job.' - node :image, Image, + node :image, Node::Image, description: 'Image that will be used to execute this job.' - node :services, Services, + node :services, Node::Services, description: 'Services that will be used to execute this job.' - node :only, Trigger, + node :only, Node::Trigger, description: 'Refs policy this job will be executed for.' - node :except, Trigger, + node :except, Node::Trigger, description: 'Refs policy this job will be executed for.' - node :variables, Variables, + node :variables, Node::Variables, description: 'Environment variables available for this job.' - node :artifacts, Artifacts, + node :artifacts, Node::Artifacts, description: 'Artifacts configuration for this job.' + node :environment, Node::Environment, + description: 'Environment configuration for this job.' + helpers :before_script, :script, :stage, :type, :after_script, :cache, :image, :services, :only, :except, :variables, - :artifacts, :commands + :artifacts, :commands, :environment def compose!(deps = nil) super do @@ -133,6 +128,8 @@ module Gitlab only: only, except: except, variables: variables_defined? ? variables : nil, + environment: environment_defined? ? environment : nil, + environment_name: environment_defined? ? environment[:name] : nil, artifacts: artifacts, after_script: after_script } end diff --git a/lib/gitlab/contributions_calendar.rb b/lib/gitlab/contributions_calendar.rb index bd681f03173..b164f5a2eea 100644 --- a/lib/gitlab/contributions_calendar.rb +++ b/lib/gitlab/contributions_calendar.rb @@ -1,16 +1,16 @@ module Gitlab class ContributionsCalendar - attr_reader :timestamps, :projects, :user + attr_reader :activity_dates, :projects, :user def initialize(projects, user) @projects = projects @user = user end - def timestamps - return @timestamps if @timestamps.present? + def activity_dates + return @activity_dates if @activity_dates.present? - @timestamps = {} + @activity_dates = {} date_from = 1.year.ago events = Event.reorder(nil).contributions.where(author_id: user.id). @@ -19,18 +19,17 @@ module Gitlab select('date(created_at) as date, count(id) as total_amount'). map(&:attributes) - dates = (1.year.ago.to_date..Date.today).to_a + activity_dates = (1.year.ago.to_date..Date.today).to_a - dates.each do |date| - date_id = date.to_time.to_i.to_s + activity_dates.each do |date| day_events = events.find { |day_events| day_events["date"] == date } if day_events - @timestamps[date_id] = day_events["total_amount"] + @activity_dates[date] = day_events["total_amount"] end end - @timestamps + @activity_dates end def events_by_date(date) diff --git a/lib/gitlab/git_access.rb b/lib/gitlab/git_access.rb index 1882eb8d050..799794c0171 100644 --- a/lib/gitlab/git_access.rb +++ b/lib/gitlab/git_access.rb @@ -5,12 +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 + attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities - def initialize(actor, project, protocol) + def initialize(actor, project, protocol, authentication_abilities:) @actor = actor @project = project @protocol = protocol + @authentication_abilities = authentication_abilities @user_access = UserAccess.new(user, project: project) end @@ -60,14 +61,26 @@ module Gitlab end def user_download_access_check - unless user_access.can_do_action?(:download_code) + unless user_can_download_code? || build_can_download_code? return build_status_object(false, "You are not allowed to download code from this project.") end build_status_object(true) end + def user_can_download_code? + authentication_abilities.include?(:download_code) && user_access.can_do_action?(:download_code) + end + + def build_can_download_code? + authentication_abilities.include?(:build_download_code) && user_access.can_do_action?(:build_download_code) + end + def user_push_access_check(changes) + unless authentication_abilities.include?(:push_code) + return build_status_object(false, "You are not allowed to upload code for this project.") + end + if changes.blank? return build_status_object(true) end diff --git a/lib/gitlab/import_export.rb b/lib/gitlab/import_export.rb index bb562bdcd2c..181e288a014 100644 --- a/lib/gitlab/import_export.rb +++ b/lib/gitlab/import_export.rb @@ -2,7 +2,8 @@ module Gitlab module ImportExport extend self - VERSION = '0.1.3' + # For every version update, the version history in import_export.md has to be kept up to date. + VERSION = '0.1.4' FILENAME_LIMIT = 50 def export_path(relative_path:) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index c2e8a1ca5dd..925a952156f 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -35,7 +35,9 @@ project_tree: - :deploy_keys - :services - :hooks - - :protected_branches + - protected_branches: + - :merge_access_levels + - :push_access_levels - :labels - milestones: - :events diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index b0726268ca6..354ccd64696 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -7,7 +7,9 @@ module Gitlab variables: 'Ci::Variable', triggers: 'Ci::Trigger', builds: 'Ci::Build', - hooks: 'ProjectHook' }.freeze + hooks: 'ProjectHook', + merge_access_levels: 'ProtectedBranch::MergeAccessLevel', + push_access_levels: 'ProtectedBranch::PushAccessLevel' }.freeze USER_REFERENCES = %w[author_id assignee_id updated_by_id user_id].freeze @@ -17,6 +19,8 @@ module Gitlab EXISTING_OBJECT_CHECK = %i[milestone milestones label labels].freeze + FINDER_ATTRIBUTES = %w[title project_id].freeze + def self.create(*args) new(*args).create end @@ -149,7 +153,7 @@ module Gitlab end def parsed_relation_hash - @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } + @parsed_relation_hash ||= @relation_hash.reject { |k, _v| !relation_class.attribute_method?(k) } end def set_st_diffs @@ -161,14 +165,30 @@ module Gitlab # Otherwise always create the record, skipping the extra SELECT clause. @existing_or_new_object ||= begin if EXISTING_OBJECT_CHECK.include?(@relation_name) - existing_object = relation_class.find_or_initialize_by(parsed_relation_hash.slice('title', 'project_id')) - existing_object.assign_attributes(parsed_relation_hash) + events = parsed_relation_hash.delete('events') + + unless events.blank? + existing_object.assign_attributes(events: events) + end + existing_object else relation_class.new(parsed_relation_hash) end end end + + def existing_object + @existing_object ||= + begin + finder_hash = parsed_relation_hash.slice(*FINDER_ATTRIBUTES) + existing_object = relation_class.find_or_create_by(finder_hash) + # Done in two steps, as MySQL behaves differently than PostgreSQL using + # the +find_or_create_by+ method and does not return the ID the second time. + existing_object.update(parsed_relation_hash) + existing_object + end + end end end end diff --git a/lib/gitlab/import_export/version_checker.rb b/lib/gitlab/import_export/version_checker.rb index de3fe6d822e..fc08082fc86 100644 --- a/lib/gitlab/import_export/version_checker.rb +++ b/lib/gitlab/import_export/version_checker.rb @@ -24,8 +24,8 @@ module Gitlab end def verify_version!(version) - if Gem::Version.new(version) > Gem::Version.new(Gitlab::ImportExport.version) - raise Gitlab::ImportExport::Error.new("Import version mismatch: Required <= #{Gitlab::ImportExport.version} but was #{version}") + if Gem::Version.new(version) != Gem::Version.new(Gitlab::ImportExport.version) + raise Gitlab::ImportExport::Error.new("Import version mismatch: Required #{Gitlab::ImportExport.version} but was #{version}") else true end diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb index ffad5e17c78..bc8bbf337f3 100644 --- a/lib/gitlab/regex.rb +++ b/lib/gitlab/regex.rb @@ -96,11 +96,11 @@ module Gitlab end def environment_name_regex - @environment_name_regex ||= /\A[a-zA-Z0-9_-]+\z/.freeze + @environment_name_regex ||= /\A[a-zA-Z0-9_\\\/\${}. -]+\z/.freeze end def environment_name_regex_message - "can contain only letters, digits, '-' and '_'." + "can contain only letters, digits, '-', '_', '/', '$', '{', '}', '.' and spaces" end end end |