diff options
author | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2022-02-26 00:59:19 +0300 |
---|---|---|
committer | GitLab Release Tools Bot <delivery-team+release-tools@gitlab.com> | 2022-02-26 00:59:19 +0300 |
commit | ee98d5353d02654dc39c0d3534c04699e5aaab43 (patch) | |
tree | 4e997cb7fa3b0bf71689e11b95245424a431d96c /app | |
parent | 479d579719c36f1b8706165c20f4525bc32bb451 (diff) | |
parent | c7be43f6dd37211709111be3796af9e1f00d3713 (diff) |
Merge remote-tracking branch 'dev/14-8-stable' into 14-8-stable
Diffstat (limited to 'app')
-rw-r--r-- | app/assets/javascripts/mirrors/mirror_repos.js | 10 | ||||
-rw-r--r-- | app/assets/javascripts/snippets/components/show.vue | 13 | ||||
-rw-r--r-- | app/assets/javascripts/snippets/mixins/snippets.js | 1 | ||||
-rw-r--r-- | app/graphql/queries/snippet/snippet.query.graphql | 1 | ||||
-rw-r--r-- | app/graphql/queries/snippet/snippet_blob_content.query.graphql | 1 | ||||
-rw-r--r-- | app/graphql/resolvers/snippets/blobs_resolver.rb | 24 | ||||
-rw-r--r-- | app/graphql/resolvers/users_resolver.rb | 9 | ||||
-rw-r--r-- | app/graphql/types/snippets/blob_connection_type.rb | 16 | ||||
-rw-r--r-- | app/graphql/types/snippets/blob_type.rb | 2 | ||||
-rw-r--r-- | app/models/concerns/token_authenticatable_strategies/encrypted.rb | 46 | ||||
-rw-r--r-- | app/models/group.rb | 20 | ||||
-rw-r--r-- | app/models/note.rb | 37 | ||||
-rw-r--r-- | app/models/project.rb | 18 | ||||
-rw-r--r-- | app/models/snippet.rb | 10 | ||||
-rw-r--r-- | app/services/members/create_service.rb | 13 | ||||
-rw-r--r-- | app/views/projects/mirrors/_authentication_method.html.haml | 2 |
16 files changed, 183 insertions, 40 deletions
diff --git a/app/assets/javascripts/mirrors/mirror_repos.js b/app/assets/javascripts/mirrors/mirror_repos.js index e59da18fb77..5bf08be1ead 100644 --- a/app/assets/javascripts/mirrors/mirror_repos.js +++ b/app/assets/javascripts/mirrors/mirror_repos.js @@ -6,6 +6,8 @@ import { __ } from '~/locale'; import { hide } from '~/tooltips'; import SSHMirror from './ssh_mirror'; +const PASSWORD_FIELD_SELECTOR = '.js-mirror-password-field'; + export default class MirrorRepos { constructor(container) { this.$container = $(container); @@ -27,7 +29,6 @@ export default class MirrorRepos { this.$passwordGroup = $('.js-password-group', this.$container); this.$password = $('.js-password', this.$passwordGroup); this.$authMethod = $('.js-auth-method', this.$form); - this.$keepDivergentRefsInput.on('change', () => this.updateKeepDivergentRefs()); this.$authMethod.on('change', () => this.togglePassword()); this.$password.on('input.updateUrl', () => this.debouncedUpdateUrl()); @@ -35,6 +36,13 @@ export default class MirrorRepos { this.initMirrorSSH(); this.updateProtectedBranches(); this.updateKeepDivergentRefs(); + MirrorRepos.resetPasswordField(); + } + + static resetPasswordField() { + if (document.querySelector(PASSWORD_FIELD_SELECTOR)) { + document.querySelector(PASSWORD_FIELD_SELECTOR).value = ''; + } } initMirrorSSH() { diff --git a/app/assets/javascripts/snippets/components/show.vue b/app/assets/javascripts/snippets/components/show.vue index 35d88d5ec8e..ee8b00c1f5d 100644 --- a/app/assets/javascripts/snippets/components/show.vue +++ b/app/assets/javascripts/snippets/components/show.vue @@ -1,5 +1,5 @@ <script> -import { GlLoadingIcon } from '@gitlab/ui'; +import { GlAlert, GlLoadingIcon } from '@gitlab/ui'; import eventHub from '~/blob/components/eventhub'; import { SNIPPET_MARK_VIEW_APP_START, @@ -23,6 +23,7 @@ export default { EmbedDropdown, SnippetHeader, SnippetTitle, + GlAlert, GlLoadingIcon, SnippetBlob, CloneDropdownButton, @@ -35,6 +36,9 @@ export default { canBeCloned() { return Boolean(this.snippet.sshUrlToRepo || this.snippet.httpUrlToRepo); }, + hasUnretrievableBlobs() { + return this.snippet.hasUnretrievableBlobs; + }, }, beforeCreate() { performanceMarkAndMeasure({ mark: SNIPPET_MARK_VIEW_APP_START }); @@ -66,6 +70,13 @@ export default { data-qa-selector="clone_button" /> </div> + <gl-alert v-if="hasUnretrievableBlobs" variant="danger" class="gl-mb-3" :dismissible="false"> + {{ + __( + 'WARNING: This snippet contains hidden files which might be used to mask malicious behavior. Exercise caution if cloning and executing code from this snippet.', + ) + }} + </gl-alert> <snippet-blob v-for="blob in blobs" :key="blob.path" diff --git a/app/assets/javascripts/snippets/mixins/snippets.js b/app/assets/javascripts/snippets/mixins/snippets.js index b72befef56b..0b3cca4e53a 100644 --- a/app/assets/javascripts/snippets/mixins/snippets.js +++ b/app/assets/javascripts/snippets/mixins/snippets.js @@ -17,6 +17,7 @@ export const getSnippetMixin = { // Set `snippet.blobs` since some child components are coupled to this. if (!isEmpty(res)) { + res.hasUnretrievableBlobs = res.blobs?.hasUnretrievableBlobs || false; // It's possible for us to not get any blobs in a response. // In this case, we should default to current blobs. res.blobs = res.blobs ? res.blobs.nodes : blobsDefault; diff --git a/app/graphql/queries/snippet/snippet.query.graphql b/app/graphql/queries/snippet/snippet.query.graphql index 24b268ec853..5c0c7ebaa1b 100644 --- a/app/graphql/queries/snippet/snippet.query.graphql +++ b/app/graphql/queries/snippet/snippet.query.graphql @@ -15,6 +15,7 @@ query GetSnippetQuery($ids: [SnippetID!]) { sshUrlToRepo blobs { __typename + hasUnretrievableBlobs nodes { __typename binary diff --git a/app/graphql/queries/snippet/snippet_blob_content.query.graphql b/app/graphql/queries/snippet/snippet_blob_content.query.graphql index 005f42ff726..4459a5e4316 100644 --- a/app/graphql/queries/snippet/snippet_blob_content.query.graphql +++ b/app/graphql/queries/snippet/snippet_blob_content.query.graphql @@ -12,6 +12,7 @@ query SnippetBlobContent($ids: [ID!], $rich: Boolean!, $paths: [String!]) { richData @include(if: $rich) plainData @skip(if: $rich) } + hasUnretrievableBlobs } } } diff --git a/app/graphql/resolvers/snippets/blobs_resolver.rb b/app/graphql/resolvers/snippets/blobs_resolver.rb index cbbc65d7263..29716ce1394 100644 --- a/app/graphql/resolvers/snippets/blobs_resolver.rb +++ b/app/graphql/resolvers/snippets/blobs_resolver.rb @@ -19,18 +19,18 @@ module Resolvers def resolve(paths: []) return [snippet.blob] if snippet.empty_repo? - if paths.empty? - snippet.blobs - else - snippet.repository.blobs_at(transformed_blob_paths(paths)) - end - end - - private - - def transformed_blob_paths(paths) - ref = snippet.default_branch - paths.map { |path| [ref, path] } + paths = snippet.all_files if paths.empty? + blobs = snippet.blobs(paths) + + # TODO: Some blobs, e.g. those with non-utf8 filenames, are returned as nil from the + # repository. We need to provide a flag to notify the user of this until we come up with a + # way to retrieve and display these blobs. We will be exploring a more holistic solution for + # this general problem of making all blobs retrievable as part + # of https://gitlab.com/gitlab-org/gitlab/-/issues/323082, at which point this attribute may + # be removed. + context[:unretrievable_blobs?] = blobs.size < paths.size + + blobs end end end diff --git a/app/graphql/resolvers/users_resolver.rb b/app/graphql/resolvers/users_resolver.rb index c6de3dba41a..1424c14083d 100644 --- a/app/graphql/resolvers/users_resolver.rb +++ b/app/graphql/resolvers/users_resolver.rb @@ -29,7 +29,7 @@ module Resolvers description: 'Return only admin users.' def resolve(ids: nil, usernames: nil, sort: nil, search: nil, admins: nil) - authorize! + authorize!(usernames) ::UsersFinder.new(context[:current_user], finder_params(ids, usernames, sort, search, admins)).execute end @@ -46,8 +46,11 @@ module Resolvers super end - def authorize! - Ability.allowed?(context[:current_user], :read_users_list) || raise_resource_not_available_error! + def authorize!(usernames) + authorized = Ability.allowed?(context[:current_user], :read_users_list) + authorized &&= usernames.present? if context[:current_user].blank? + + raise_resource_not_available_error! unless authorized end private diff --git a/app/graphql/types/snippets/blob_connection_type.rb b/app/graphql/types/snippets/blob_connection_type.rb new file mode 100644 index 00000000000..15d26af7374 --- /dev/null +++ b/app/graphql/types/snippets/blob_connection_type.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +module Types + module Snippets + # rubocop: disable Graphql/AuthorizeTypes + class BlobConnectionType < GraphQL::Types::Relay::BaseConnection + field :has_unretrievable_blobs, GraphQL::Types::Boolean, null: false, + description: 'Indicates if the snippet has unretrievable blobs.', + resolver_method: :unretrievable_blobs? + + def unretrievable_blobs? + !!context[:unretrievable_blobs?] + end + end + end +end diff --git a/app/graphql/types/snippets/blob_type.rb b/app/graphql/types/snippets/blob_type.rb index 2b9b76a6194..80702c71f63 100644 --- a/app/graphql/types/snippets/blob_type.rb +++ b/app/graphql/types/snippets/blob_type.rb @@ -8,6 +8,8 @@ module Types description 'Represents the snippet blob' present_using SnippetBlobPresenter + connection_type_class(Types::Snippets::BlobConnectionType) + field :rich_data, GraphQL::Types::String, description: 'Blob highlighted data.', null: true diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb index 50a2613bb10..e957d09fbc6 100644 --- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb +++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb @@ -5,16 +5,18 @@ module TokenAuthenticatableStrategies def find_token_authenticatable(token, unscoped = false) return if token.blank? - if required? - find_by_encrypted_token(token, unscoped) - elsif optional? - find_by_encrypted_token(token, unscoped) || - find_by_plaintext_token(token, unscoped) - elsif migrating? - find_by_plaintext_token(token, unscoped) - else - raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } - end + instance = if required? + find_by_encrypted_token(token, unscoped) + elsif optional? + find_by_encrypted_token(token, unscoped) || + find_by_plaintext_token(token, unscoped) + elsif migrating? + find_by_plaintext_token(token, unscoped) + else + raise ArgumentError, _("Unknown encryption strategy: %{encrypted_strategy}!") % { encrypted_strategy: encrypted_strategy } + end + + instance if instance && matches_prefix?(instance, token) end def ensure_token(instance) @@ -41,9 +43,7 @@ module TokenAuthenticatableStrategies def get_token(instance) return insecure_strategy.get_token(instance) if migrating? - encrypted_token = instance.read_attribute(encrypted_field) - token = EncryptionHelper.decrypt_token(encrypted_token) - token || (insecure_strategy.get_token(instance) if optional?) + get_encrypted_token(instance) end def set_token(instance, token) @@ -69,6 +69,12 @@ module TokenAuthenticatableStrategies protected + def get_encrypted_token(instance) + encrypted_token = instance.read_attribute(encrypted_field) + token = EncryptionHelper.decrypt_token(encrypted_token) + token || (insecure_strategy.get_token(instance) if optional?) + end + def encrypted_strategy value = options[:encrypted] value = value.call if value.is_a?(Proc) @@ -95,14 +101,22 @@ module TokenAuthenticatableStrategies .new(klass, token_field, options) end + def matches_prefix?(instance, token) + prefix = options[:prefix] + prefix = prefix.call(instance) if prefix.is_a?(Proc) + prefix = '' unless prefix.is_a?(String) + + token.start_with?(prefix) + end + def token_set?(instance) - raw_token = instance.read_attribute(encrypted_field) + token = get_encrypted_token(instance) unless required? - raw_token ||= insecure_strategy.get_token(instance) + token ||= insecure_strategy.get_token(instance) end - raw_token.present? + token.present? && matches_prefix?(instance, token) end def encrypted_field diff --git a/app/models/group.rb b/app/models/group.rb index 53da70f47e5..a395861fbb6 100644 --- a/app/models/group.rb +++ b/app/models/group.rb @@ -20,6 +20,13 @@ class Group < Namespace include ChronicDurationAttribute include RunnerTokenExpirationInterval + extend ::Gitlab::Utils::Override + + # Prefix for runners_token which can be used to invalidate existing tokens. + # The value chosen here is GR (for Gitlab Runner) combined with the rotation + # date (20220225) decimal to hex encoded. + RUNNERS_TOKEN_PREFIX = 'GR1348941' + def self.sti_name 'Group' end @@ -115,7 +122,9 @@ class Group < Namespace message: Gitlab::Regex.group_name_regex_message }, if: :name_changed? - add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } + add_authentication_token_field :runners_token, + encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, + prefix: ->(instance) { instance.runners_token_prefix } after_create :post_create_hook after_destroy :post_destroy_hook @@ -669,6 +678,15 @@ class Group < Namespace ensure_runners_token! end + def runners_token_prefix + Feature.enabled?(:groups_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' + end + + override :format_runners_token + def format_runners_token(token) + "#{runners_token_prefix}#{token}" + end + def project_creation_level super || ::Gitlab::CurrentSettings.default_project_creation end diff --git a/app/models/note.rb b/app/models/note.rb index 3f3fa968393..a84da066968 100644 --- a/app/models/note.rb +++ b/app/models/note.rb @@ -50,7 +50,7 @@ class Note < ApplicationRecord attr_accessor :user_visible_reference_count # Attribute used to store the attributes that have been changed by quick actions. - attr_accessor :commands_changes + attr_writer :commands_changes # Attribute used to determine whether keep_around_commits will be skipped for diff notes. attr_accessor :skip_keep_around_commits @@ -616,6 +616,41 @@ class Note < ApplicationRecord change_position.line_range["end"] || change_position.line_range["start"] end + def commands_changes + @commands_changes&.slice( + :due_date, + :label_ids, + :remove_label_ids, + :add_label_ids, + :canonical_issue_id, + :clone_with_notes, + :confidential, + :create_merge_request, + :add_contacts, + :remove_contacts, + :assignee_ids, + :milestone_id, + :time_estimate, + :spend_time, + :discussion_locked, + :merge, + :rebase, + :wip_event, + :target_branch, + :reviewer_ids, + :health_status, + :promote_to_epic, + :weight, + :emoji_award, + :todo_event, + :subscription_event, + :state_event, + :title, + :tag_message, + :tag_name + ) + end + private def system_note_viewable_by?(user) diff --git a/app/models/project.rb b/app/models/project.rb index 512c6ac1acb..de7dd42866f 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -89,6 +89,11 @@ class Project < ApplicationRecord DEFAULT_SQUASH_COMMIT_TEMPLATE = '%{title}' + # Prefix for runners_token which can be used to invalidate existing tokens. + # The value chosen here is GR (for Gitlab Runner) combined with the rotation + # date (20220225) decimal to hex encoded. + RUNNERS_TOKEN_PREFIX = 'GR1348941' + cache_markdown_field :description, pipeline: :description default_value_for :packages_enabled, true @@ -109,7 +114,9 @@ class Project < ApplicationRecord default_value_for :autoclose_referenced_issues, true default_value_for(:ci_config_path) { Gitlab::CurrentSettings.default_ci_config_path } - add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required } + add_authentication_token_field :runners_token, + encrypted: -> { Feature.enabled?(:projects_tokens_optional_encryption, default_enabled: true) ? :optional : :required }, + prefix: ->(instance) { instance.runners_token_prefix } before_validation :mark_remote_mirrors_for_removal, if: -> { RemoteMirror.table_exists? } @@ -1873,6 +1880,15 @@ class Project < ApplicationRecord ensure_runners_token! end + def runners_token_prefix + Feature.enabled?(:projects_runners_token_prefix, self, default_enabled: :yaml) ? RUNNERS_TOKEN_PREFIX : '' + end + + override :format_runners_token + def format_runners_token(token) + "#{runners_token_prefix}#{token}" + end + def pages_deployed? pages_metadatum&.deployed? end diff --git a/app/models/snippet.rb b/app/models/snippet.rb index 6a8123b3c08..b04fca64c87 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -237,15 +237,19 @@ class Snippet < ApplicationRecord end end + def all_files + list_files(default_branch) + end + def blob @blob ||= Blob.decorate(SnippetBlob.new(self), self) end - def blobs + def blobs(paths = []) return [] unless repository_exists? - files = list_files(default_branch) - items = files.map { |file| [default_branch, file] } + paths = all_files if paths.empty? + items = paths.map { |path| [default_branch, path] } repository.blobs_at(items).compact end diff --git a/app/services/members/create_service.rb b/app/services/members/create_service.rb index dc29bb2c6da..758fa2e67f1 100644 --- a/app/services/members/create_service.rb +++ b/app/services/members/create_service.rb @@ -19,6 +19,8 @@ module Members end def execute + raise Gitlab::Access::AccessDeniedError unless can?(current_user, create_member_permission(source), source) + validate_invite_source! validate_invitable! @@ -156,6 +158,17 @@ module Members }) ) end + + def create_member_permission(source) + case source + when Group + :admin_group_member + when Project + :admin_project_member + else + raise "Unknown source type: #{source.class}!" + end + end end end diff --git a/app/views/projects/mirrors/_authentication_method.html.haml b/app/views/projects/mirrors/_authentication_method.html.haml index e9e3645d7f2..28b433b2514 100644 --- a/app/views/projects/mirrors/_authentication_method.html.haml +++ b/app/views/projects/mirrors/_authentication_method.html.haml @@ -13,4 +13,4 @@ .form-group .well-password-auth.collapse.js-well-password-auth = f.label :password, _("Password"), class: "label-bold" - = f.password_field :password, class: 'form-control gl-form-input qa-password', autocomplete: 'new-password' + = f.password_field :password, class: 'form-control gl-form-input qa-password js-mirror-password-field', autocomplete: 'off' |