diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-07 00:09:12 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-02-07 00:09:12 +0000 |
commit | 6168721025dd8e98caeb2bf6844273e6690eaf69 (patch) | |
tree | 8c4fb20d793669e488a739bc9951dab8b363eed4 /app | |
parent | a89cb5cbdd832d4d9e80517973aceda6bc0a3856 (diff) | |
download | gitlab-ce-6168721025dd8e98caeb2bf6844273e6690eaf69.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'app')
19 files changed, 313 insertions, 27 deletions
diff --git a/app/assets/javascripts/blob/components/blob_header_filepath.vue b/app/assets/javascripts/blob/components/blob_header_filepath.vue new file mode 100644 index 00000000000..6c6a22e2b36 --- /dev/null +++ b/app/assets/javascripts/blob/components/blob_header_filepath.vue @@ -0,0 +1,47 @@ +<script> +import FileIcon from '~/vue_shared/components/file_icon.vue'; +import ClipboardButton from '~/vue_shared/components/clipboard_button.vue'; +import { numberToHumanSize } from '~/lib/utils/number_utils'; + +export default { + components: { + FileIcon, + ClipboardButton, + }, + props: { + blob: { + type: Object, + required: true, + }, + }, + computed: { + blobSize() { + return numberToHumanSize(this.blob.size); + }, + gfmCopyText() { + return `\`${this.blob.path}\``; + }, + }, +}; +</script> +<template> + <div class="file-header-content d-flex align-items-center lh-100"> + <slot name="filepathPrepend"></slot> + + <file-icon :file-name="blob.path" :size="18" aria-hidden="true" css-classes="mr-2" /> + <strong + v-if="blob.name" + class="file-title-name qa-file-title-name mr-1 js-blob-header-filepath" + >{{ blob.name }}</strong + > + + <small class="mr-2">{{ blobSize }}</small> + + <clipboard-button + :text="blob.path" + :gfm="gfmCopyText" + :title="__('Copy file path')" + css-class="btn-clipboard btn-transparent lh-100 position-static" + /> + </div> +</template> diff --git a/app/assets/javascripts/monitoring/constants.js b/app/assets/javascripts/monitoring/constants.js index 789b3131d11..b468254b0cf 100644 --- a/app/assets/javascripts/monitoring/constants.js +++ b/app/assets/javascripts/monitoring/constants.js @@ -110,8 +110,8 @@ export const timeRanges = [ duration: { seconds: 60 * 60 * 24 * 7 * 1 }, }, { - label: __('2 weeks'), - duration: { seconds: 60 * 60 * 24 * 7 * 2 }, + label: __('1 month'), + duration: { seconds: 60 * 60 * 24 * 30 }, }, ]; diff --git a/app/assets/stylesheets/framework/snippets.scss b/app/assets/stylesheets/framework/snippets.scss index fbe241df32f..dbcb5086d70 100644 --- a/app/assets/stylesheets/framework/snippets.scss +++ b/app/assets/stylesheets/framework/snippets.scss @@ -32,10 +32,6 @@ .snippet-file-content { border-radius: 3px; - - .file-title-flex-parent .btn-clipboard { - line-height: 28px; - } } .snippet-header { diff --git a/app/assets/stylesheets/pages/commits.scss b/app/assets/stylesheets/pages/commits.scss index be0311f584f..781b6c09458 100644 --- a/app/assets/stylesheets/pages/commits.scss +++ b/app/assets/stylesheets/pages/commits.scss @@ -321,6 +321,16 @@ } } +.gpg-popover-certificate-details { + ul { + padding-left: $gl-padding; + } + + li.unstyled { + list-style-type: none; + } +} + .gpg-popover-status { display: flex; align-items: center; diff --git a/app/models/commit.rb b/app/models/commit.rb index f2a6a8b6cbb..46222bbc4cd 100644 --- a/app/models/commit.rb +++ b/app/models/commit.rb @@ -25,7 +25,7 @@ class Commit attr_accessor :redacted_description_html attr_accessor :redacted_title_html attr_accessor :redacted_full_title_html - attr_reader :gpg_commit, :container + attr_reader :container delegate :repository, to: :container delegate :project, to: :repository, allow_nil: true @@ -123,7 +123,6 @@ class Commit @raw = raw_commit @container = container - @gpg_commit = Gitlab::Gpg::Commit.new(self) if container end delegate \ @@ -320,13 +319,34 @@ class Commit ) end - def signature - return @signature if defined?(@signature) + def has_signature? + signature_type && signature_type != :NONE + end + + def raw_signature_type + strong_memoize(:raw_signature_type) do + next unless @raw.instance_of?(Gitlab::Git::Commit) + + @raw.raw_commit.signature_type if defined? @raw.raw_commit.signature_type + end + end - @signature = gpg_commit.signature + def signature_type + @signature_type ||= raw_signature_type || :NONE end - delegate :has_signature?, to: :gpg_commit + def signature + strong_memoize(:signature) do + case signature_type + when :PGP + Gitlab::Gpg::Commit.new(self).signature + when :X509 + Gitlab::X509::Commit.new(self).signature + else + nil + end + end + end def revert_branch_name "revert-#{short_id}" diff --git a/app/models/concerns/x509_serial_number_attribute.rb b/app/models/concerns/x509_serial_number_attribute.rb new file mode 100644 index 00000000000..d2a5c736604 --- /dev/null +++ b/app/models/concerns/x509_serial_number_attribute.rb @@ -0,0 +1,45 @@ +# frozen_string_literal: true + +module X509SerialNumberAttribute + extend ActiveSupport::Concern + + class_methods do + def x509_serial_number_attribute(name) + return if ENV['STATIC_VERIFICATION'] + + validate_binary_column_exists!(name) unless Rails.env.production? + + attribute(name, Gitlab::Database::X509SerialNumberAttribute.new) + end + + # This only gets executed in non-production environments as an additional check to ensure + # the column is the correct type. In production it should behave like any other attribute. + # See https://gitlab.com/gitlab-org/gitlab/merge_requests/5502 for more discussion + def validate_binary_column_exists!(name) + return unless database_exists? + + unless table_exists? + warn "WARNING: x509_serial_number_attribute #{name.inspect} is invalid since the table doesn't exist - you may need to run database migrations" + return + end + + column = columns.find { |c| c.name == name.to_s } + + unless column + warn "WARNING: x509_serial_number_attribute #{name.inspect} is invalid since the column doesn't exist - you may need to run database migrations" + return + end + + unless column.type == :binary + raise ArgumentError.new("x509_serial_number_attribute #{name.inspect} is invalid since the column type is not :binary") + end + rescue => error + Gitlab::AppLogger.error "X509SerialNumberAttribute initialization: #{error.message}" + raise + end + + def database_exists? + Gitlab::Database.exists? + end + end +end diff --git a/app/models/user.rb b/app/models/user.rb index aa7e825d516..11bfa485ae9 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -190,6 +190,12 @@ class User < ApplicationRecord validate :owns_commit_email, if: :commit_email_changed? validate :signup_domain_valid?, on: :create, if: ->(user) { !user.created_by_id } + validates :theme_id, allow_nil: true, inclusion: { in: Gitlab::Themes.valid_ids, + message: _("%{placeholder} is not a valid theme") % { placeholder: '%{value}' } } + + validates :color_scheme_id, allow_nil: true, inclusion: { in: Gitlab::ColorSchemes.valid_ids, + message: _("%{placeholder} is not a valid color scheme") % { placeholder: '%{value}' } } + before_validation :sanitize_attrs before_validation :set_notification_email, if: :new_record? before_validation :set_public_email, if: :public_email_changed? diff --git a/app/models/x509_certificate.rb b/app/models/x509_certificate.rb new file mode 100644 index 00000000000..43927e65db1 --- /dev/null +++ b/app/models/x509_certificate.rb @@ -0,0 +1,32 @@ +# frozen_string_literal: true + +class X509Certificate < ApplicationRecord + include X509SerialNumberAttribute + + x509_serial_number_attribute :serial_number + + enum certificate_status: { + good: 0, + revoked: 1 + } + + belongs_to :x509_issuer, class_name: 'X509Issuer', foreign_key: 'x509_issuer_id', optional: false + + has_many :x509_commit_signatures, inverse_of: 'x509_certificate' + + # rfc 5280 - 4.2.1.2 Subject Key Identifier + validates :subject_key_identifier, presence: true, format: { with: /\A(\h{2}:){19}\h{2}\z/ } + # rfc 5280 - 4.1.2.6 Subject + validates :subject, presence: true + # rfc 5280 - 4.1.2.6 Subject (subjectAltName contains the email address) + validates :email, presence: true, format: { with: URI::MailTo::EMAIL_REGEXP } + # rfc 5280 - 4.1.2.2 Serial number + validates :serial_number, presence: true, numericality: { only_integer: true } + + validates :x509_issuer_id, presence: true + + def self.safe_create!(attributes) + create_with(attributes) + .safe_find_or_create_by!(subject_key_identifier: attributes[:subject_key_identifier]) + end +end diff --git a/app/models/x509_commit_signature.rb b/app/models/x509_commit_signature.rb new file mode 100644 index 00000000000..ed7c638cecc --- /dev/null +++ b/app/models/x509_commit_signature.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +class X509CommitSignature < ApplicationRecord + include ShaAttribute + + sha_attribute :commit_sha + + enum verification_status: { + unverified: 0, + verified: 1 + } + + belongs_to :project, class_name: 'Project', foreign_key: 'project_id', optional: false + belongs_to :x509_certificate, class_name: 'X509Certificate', foreign_key: 'x509_certificate_id', optional: false + + validates :commit_sha, presence: true + validates :project_id, presence: true + validates :x509_certificate_id, presence: true + + scope :by_commit_sha, ->(shas) { where(commit_sha: shas) } + + def self.safe_create!(attributes) + create_with(attributes) + .safe_find_or_create_by!(commit_sha: attributes[:commit_sha]) + end + + # Find commits that are lacking a signature in the database at present + def self.unsigned_commit_shas(commit_shas) + return [] if commit_shas.empty? + + signed = by_commit_sha(commit_shas).pluck(:commit_sha) + commit_shas - signed + end + + def commit + project.commit(commit_sha) + end + + def x509_commit + return unless commit + + Gitlab::X509::Commit.new(commit) + end +end diff --git a/app/models/x509_issuer.rb b/app/models/x509_issuer.rb new file mode 100644 index 00000000000..514b38808ef --- /dev/null +++ b/app/models/x509_issuer.rb @@ -0,0 +1,18 @@ +# frozen_string_literal: true + +class X509Issuer < ApplicationRecord + has_many :x509_certificates, inverse_of: 'x509_issuer' + + # rfc 5280 - 4.2.1.1 Authority Key Identifier + validates :subject_key_identifier, presence: true, format: { with: /\A(\h{2}:){19}\h{2}\z/ } + # rfc 5280 - 4.1.2.4 Issuer + validates :subject, presence: true + # rfc 5280 - 4.2.1.14 CRL Distribution Points + # cRLDistributionPoints extension using URI:http + validates :crl_url, presence: true, public_url: true + + def self.safe_create!(attributes) + create_with(attributes) + .safe_find_or_create_by!(subject_key_identifier: attributes[:subject_key_identifier]) + end +end diff --git a/app/services/git/branch_hooks_service.rb b/app/services/git/branch_hooks_service.rb index 69f1f9eb31f..e1cc1f8c834 100644 --- a/app/services/git/branch_hooks_service.rb +++ b/app/services/git/branch_hooks_service.rb @@ -6,7 +6,7 @@ module Git execute_branch_hooks super.tap do - enqueue_update_gpg_signatures + enqueue_update_signatures end end @@ -103,14 +103,22 @@ module Git end end - def enqueue_update_gpg_signatures - unsigned = GpgSignature.unsigned_commit_shas(limited_commits.map(&:sha)) + def unsigned_x509_shas(commits) + X509CommitSignature.unsigned_commit_shas(commits.map(&:sha)) + end + + def unsigned_gpg_shas(commits) + GpgSignature.unsigned_commit_shas(commits.map(&:sha)) + end + + def enqueue_update_signatures + unsigned = unsigned_x509_shas(commits) & unsigned_gpg_shas(commits) return if unsigned.empty? signable = Gitlab::Git::Commit.shas_with_signatures(project.repository, unsigned) return if signable.empty? - CreateGpgSignatureWorker.perform_async(signable, project.id) + CreateCommitSignatureWorker.perform_async(signable, project.id) end # It's not sufficient to just check for a blank SHA as it's possible for the diff --git a/app/views/projects/commit/_signature.html.haml b/app/views/projects/commit/_signature.html.haml index 145bc629380..aa7c90bad66 100644 --- a/app/views/projects/commit/_signature.html.haml +++ b/app/views/projects/commit/_signature.html.haml @@ -1,2 +1,3 @@ - if signature - = render partial: "projects/commit/#{signature.verification_status}_signature_badge", locals: { signature: signature } + - uri = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}" + = render partial: "#{uri}#{signature.verification_status}_signature_badge", locals: { signature: signature } diff --git a/app/views/projects/commit/_signature_badge.html.haml b/app/views/projects/commit/_signature_badge.html.haml index cbd998c60ef..776ce48d4bc 100644 --- a/app/views/projects/commit/_signature_badge.html.haml +++ b/app/views/projects/commit/_signature_badge.html.haml @@ -17,12 +17,18 @@ - content = capture do - if show_user .clearfix - = render partial: 'projects/commit/signature_badge_user', locals: { signature: signature } + - uri_signature_badge_user = "projects/commit/#{"x509/" if signature.instance_of?(X509CommitSignature)}signature_badge_user" + = render partial: "#{uri_signature_badge_user}", locals: { signature: signature } - = _('GPG Key ID:') - %span.monospace= signature.gpg_key_primary_keyid + - if signature.instance_of?(X509CommitSignature) + = render partial: "projects/commit/x509/certificate_details", locals: { signature: signature } - = link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') + = link_to(_('Learn more about x509 signed commits'), help_page_path('user/project/repository/x509_signed_commits/index.md'), class: 'gpg-popover-help-link') + - else + = _('GPG Key ID:') + %span.monospace= signature.gpg_key_primary_keyid + + = link_to(_('Learn more about signing commits'), help_page_path('user/project/repository/gpg_signed_commits/index.md'), class: 'gpg-popover-help-link') %button{ tabindex: 0, class: css_classes, data: { toggle: 'popover', html: 'true', placement: 'top', title: title, content: content } } = label diff --git a/app/views/projects/commit/x509/_certificate_details.html.haml b/app/views/projects/commit/x509/_certificate_details.html.haml new file mode 100644 index 00000000000..2357c6d803b --- /dev/null +++ b/app/views/projects/commit/x509/_certificate_details.html.haml @@ -0,0 +1,17 @@ +.gpg-popover-certificate-details + %strong= _('Certificate Subject') + %ul + - signature.x509_certificate.subject.split(",").each do |i| + - if i.start_with?("CN", "O") + %li= i + %li= _('Subject Key Identifier:') + %li.unstyled= signature.x509_certificate.subject_key_identifier.gsub(":", " ") + +.gpg-popover-certificate-details + %strong= _('Certificate Issuer') + %ul + - signature.x509_certificate.x509_issuer.subject.split(",").each do |i| + - if i.start_with?("CN", "OU", "O") + %li= i + %li= _('Subject Key Identifier:') + %li.unstyled= signature.x509_certificate.x509_issuer.subject_key_identifier.gsub(":", " ") diff --git a/app/views/projects/commit/x509/_signature_badge_user.html.haml b/app/views/projects/commit/x509/_signature_badge_user.html.haml new file mode 100644 index 00000000000..b64ccba2a18 --- /dev/null +++ b/app/views/projects/commit/x509/_signature_badge_user.html.haml @@ -0,0 +1,19 @@ +- user = signature.commit.committer +- user_email = signature.x509_certificate.email + +- if user + = link_to user_path(user), class: 'gpg-popover-user-link' do + %div + = user_avatar_without_link(user: user, size: 32) + + %div + %strong= user.name + %div= user.to_reference + +- else + = mail_to user_email do + %div + = user_avatar_without_link(user_email: user_email, size: 32) + + %div + %strong= user_email diff --git a/app/views/projects/commit/x509/_unverified_signature_badge.html.haml b/app/views/projects/commit/x509/_unverified_signature_badge.html.haml new file mode 100644 index 00000000000..680cc32c7e6 --- /dev/null +++ b/app/views/projects/commit/x509/_unverified_signature_badge.html.haml @@ -0,0 +1,6 @@ +- title = capture do + = _('This commit was signed with an <strong>unverified</strong> signature.').html_safe + +- locals = { signature: signature, title: title, label: _('Unverified'), css_class: 'invalid', icon: 'status_notfound_borderless', show_user: true } + += render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/views/projects/commit/x509/_verified_signature_badge.html.haml b/app/views/projects/commit/x509/_verified_signature_badge.html.haml new file mode 100644 index 00000000000..4964b1b8ee7 --- /dev/null +++ b/app/views/projects/commit/x509/_verified_signature_badge.html.haml @@ -0,0 +1,6 @@ +- title = capture do + = _('This commit was signed with a <strong>verified</strong> signature and the committer email is verified to belong to the same user.').html_safe + +- locals = { signature: signature, title: title, label: _('Verified'), css_class: 'valid', icon: 'status_success_borderless', show_user: true } + += render partial: 'projects/commit/signature_badge', locals: locals diff --git a/app/workers/all_queues.yml b/app/workers/all_queues.yml index 0426a0b8fbb..35852742b0d 100644 --- a/app/workers/all_queues.yml +++ b/app/workers/all_queues.yml @@ -699,14 +699,14 @@ :latency_sensitive: true :resource_boundary: :unknown :weight: 2 -- :name: create_evidence - :feature_category: :release_governance +- :name: create_commit_signature + :feature_category: :source_code_management :has_external_dependencies: :latency_sensitive: :resource_boundary: :unknown :weight: 2 -- :name: create_gpg_signature - :feature_category: :source_code_management +- :name: create_evidence + :feature_category: :release_governance :has_external_dependencies: :latency_sensitive: :resource_boundary: :unknown diff --git a/app/workers/create_gpg_signature_worker.rb b/app/workers/create_commit_signature_worker.rb index 2043c3c8e77..027fea3e402 100644 --- a/app/workers/create_gpg_signature_worker.rb +++ b/app/workers/create_commit_signature_worker.rb @@ -1,6 +1,6 @@ # frozen_string_literal: true -class CreateGpgSignatureWorker +class CreateCommitSignatureWorker include ApplicationWorker feature_category :source_code_management @@ -23,7 +23,12 @@ class CreateGpgSignatureWorker # This calculates and caches the signature in the database commits.each do |commit| - Gitlab::Gpg::Commit.new(commit).signature + case commit.signature_type + when :PGP + Gitlab::Gpg::Commit.new(commit).signature + when :X509 + Gitlab::X509::Commit.new(commit).signature + end rescue => e Rails.logger.error("Failed to create signature for commit #{commit.id}. Error: #{e.message}") # rubocop:disable Gitlab/RailsLogger end |