summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsh McKenzie <amckenzie@gitlab.com>2018-12-27 17:46:38 +1100
committerAsh McKenzie <amckenzie@gitlab.com>2019-09-11 14:53:13 +1000
commit0e31b424fb9a07ea5ba8f6d864ff726533e8ba85 (patch)
treeac192e1c9f50ed114e332f81303621a1134af49b
parentbd7e1e554b3d68f31bc1f8b23e568a9950d597e0 (diff)
downloadgitlab-ce-13235-secret-snippets.tar.gz
Add Secret support for Snippets13235-secret-snippets
Snippets can now be created as type Secret which are non-searched Snippets that can accessed publicly if the correct secret_word is known.
-rw-r--r--app/assets/javascripts/snippet/snippet_embed.js14
-rw-r--r--app/controllers/concerns/snippets_url.rb64
-rw-r--r--app/controllers/snippets_controller.rb13
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/blob_helper.rb6
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/snippets_helper.rb71
-rw-r--r--app/models/snippet.rb26
-rw-r--r--app/views/dashboard/snippets/index.html.haml2
-rw-r--r--app/views/projects/snippets/index.html.haml2
-rw-r--r--app/views/search/results/_snippet_blob.html.haml20
-rw-r--r--app/views/search/results/_snippet_title.html.haml5
-rw-r--r--app/views/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/snippets/_blob.html.haml3
-rw-r--r--app/views/shared/snippets/_embed.html.haml1
-rw-r--r--app/views/shared/snippets/_header.html.haml2
-rw-r--r--app/views/snippets/_snippets_scope_menu.html.haml7
-rw-r--r--app/views/snippets/show.html.haml2
-rw-r--r--changelogs/unreleased/13235-secret-snippets.yml5
-rw-r--r--db/migrate/20181227063544_add_secret_word_to_snippet.rb11
-rw-r--r--db/schema.rb3
-rw-r--r--lib/gitlab/import_export/import_export.yml3
-rw-r--r--lib/gitlab/snippet_search_results.rb4
-rw-r--r--locale/gitlab.pot3
-rw-r--r--spec/factories/snippets.rb4
-rw-r--r--spec/helpers/snippets_helper_spec.rb88
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb12
-rw-r--r--spec/models/snippet_spec.rb40
28 files changed, 360 insertions, 57 deletions
diff --git a/app/assets/javascripts/snippet/snippet_embed.js b/app/assets/javascripts/snippet/snippet_embed.js
index fe08d2c7ebb..e6079c08773 100644
--- a/app/assets/javascripts/snippet/snippet_embed.js
+++ b/app/assets/javascripts/snippet/snippet_embed.js
@@ -1,4 +1,5 @@
import { __ } from '~/locale';
+import { getParameterByName } from '../lib/utils/common_utils';
export default () => {
const { protocol, host, pathname } = window.location;
@@ -6,7 +7,18 @@ export default () => {
const embedBtn = document.querySelector('.js-embed-btn');
const snippetUrlArea = document.querySelector('.js-snippet-url-area');
const embedAction = document.querySelector('.js-embed-action');
- const url = `${protocol}//${host + pathname}`;
+
+ const secretParam = 'secret';
+ const secretValue = getParameterByName(secretParam);
+ const baseUrl = `${protocol}//${host + pathname}`;
+
+ let urlArgs = '';
+
+ if (secretValue) {
+ urlArgs = `?${secretParam}=${secretValue}`;
+ }
+
+ const url = baseUrl + urlArgs;
shareBtn.addEventListener('click', () => {
shareBtn.classList.add('is-active');
diff --git a/app/controllers/concerns/snippets_url.rb b/app/controllers/concerns/snippets_url.rb
new file mode 100644
index 00000000000..09e07b6db4c
--- /dev/null
+++ b/app/controllers/concerns/snippets_url.rb
@@ -0,0 +1,64 @@
+# frozen_string_literal: true
+
+module SnippetsUrl
+ extend ActiveSupport::Concern
+
+ SNIPPETS_SECRET_KEYWORD = 'secret'
+
+ private
+
+ attr_reader :snippet
+
+ def authorize_secret_snippet!
+ if snippet_is_secret?
+ return if secrets_match?(params[SNIPPETS_SECRET_KEYWORD])
+
+ return render_404
+ end
+
+ current_user ? render_404 : authenticate_user!
+ end
+
+ def snippet_is_secret?
+ snippet&.secret?
+ end
+
+ def secrets_match?(secret)
+ ActiveSupport::SecurityUtils.secure_compare(secret.to_s, snippet.secret)
+ end
+
+ def ensure_complete_url
+ redirect_to(complete_full_path.to_s) if redirect_to_complete_full_path?
+ end
+
+ def redirect_to_complete_full_path?
+ return unless snippet_is_secret?
+
+ complete_full_path != current_full_path
+ end
+
+ def complete_full_path
+ @complete_full_path ||= begin
+ path = current_full_path.clone
+ secret_query = { SNIPPETS_SECRET_KEYWORD => snippet.secret }
+ path.query = current_url_query_hash.merge(secret_query).to_query
+ path
+ end
+ end
+
+ def current_full_path
+ @current_full_path ||= begin
+ path = URI.parse(current_url.path.chomp('/'))
+ path.query = current_url_query_hash.to_query unless current_url_query_hash.empty?
+ path
+ end
+ end
+
+ def current_url
+ @current_url ||= URI.parse(request.original_url)
+ end
+
+ def current_url_query_hash
+ @current_url_query_hash ||= Rack::Utils.parse_nested_query(current_url.query)
+ end
+end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 5805d068e21..3e838b298c7 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -5,6 +5,8 @@ class SnippetsController < ApplicationController
include ToggleAwardEmoji
include SpammableActions
include SnippetsActions
+ include SnippetsHelper
+ include SnippetsUrl
include RendersBlob
include PreviewMarkdown
include PaginatedCollection
@@ -18,6 +20,9 @@ class SnippetsController < ApplicationController
# Allow read snippet
before_action :authorize_read_snippet!, only: [:show, :raw]
+ # Ensure we're displaying the correct url, specifically for secret snippets
+ before_action :ensure_complete_url, only: [:show, :raw]
+
# Allow modify snippet
before_action :authorize_update_snippet!, only: [:edit, :update]
@@ -119,17 +124,13 @@ class SnippetsController < ApplicationController
alias_method :spammable, :snippet
def spammable_path
- snippet_path(@snippet)
+ reliable_snippet_path(@snippet)
end
def authorize_read_snippet!
return if can?(current_user, :read_personal_snippet, @snippet)
- if current_user
- render_404
- else
- authenticate_user!
- end
+ authorize_secret_snippet!
end
def authorize_update_snippet!
diff --git a/app/finders/snippets_finder.rb b/app/finders/snippets_finder.rb
index bf29f15642d..90dd9556d91 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -148,6 +148,8 @@ class SnippetsFinder < UnionFinder
case scope
when 'are_private'
Snippet::PRIVATE
+ when 'are_secret'
+ Snippet::SECRET
when 'are_internal'
Snippet::INTERNAL
when 'are_public'
diff --git a/app/helpers/blob_helper.rb b/app/helpers/blob_helper.rb
index 4b0713001a1..27511624058 100644
--- a/app/helpers/blob_helper.rb
+++ b/app/helpers/blob_helper.rb
@@ -133,11 +133,7 @@ module BlobHelper
if @build && @entry
raw_project_job_artifacts_url(@project, @build, path: @entry.path, **kwargs)
elsif @snippet
- if @snippet.project_id
- raw_project_snippet_url(@project, @snippet, **kwargs)
- else
- raw_snippet_url(@snippet, **kwargs)
- end
+ reliable_raw_snippet_url(@snippet)
elsif @blob
project_raw_url(@project, @id, **kwargs)
end
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 4f73270577f..ecf483c1233 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -94,6 +94,8 @@ module IconsHelper
case level
when Gitlab::VisibilityLevel::PRIVATE
'lock'
+ when Gitlab::VisibilityLevel::SECRET
+ 'user-secret'
when Gitlab::VisibilityLevel::INTERNAL
'shield'
else # Gitlab::VisibilityLevel::PUBLIC
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 6ccc1fb2ed1..0a77741c457 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -11,22 +11,49 @@ module SnippetsHelper
end
end
- def reliable_snippet_path(snippet, opts = nil)
- if snippet.project_id?
- project_snippet_path(snippet.project, snippet, opts)
- else
- snippet_path(snippet, opts)
+ def reliable_snippet_path(snippet, opts = {})
+ reliable_snippet_url(snippet, opts, only_path: true)
+ end
+
+ def reliable_raw_snippet_path(snippet, opts = {})
+ reliable_raw_snippet_url(snippet, opts, only_path: true)
+ end
+
+ def reliable_snippet_url(snippet, opts = {}, only_path: false)
+ reliable_snippet_helper(snippet, opts) do |updated_opts|
+ if snippet.project_id?
+ project_snippet_url(snippet.project, snippet, nil, updated_opts.merge({ only_path: only_path }))
+ else
+ snippet_url(snippet, nil, updated_opts.merge({ only_path: only_path }))
+ end
end
end
- def download_snippet_path(snippet)
- if snippet.project_id
- raw_project_snippet_path(@project, snippet, inline: false)
- else
- raw_snippet_path(snippet, inline: false)
+ def reliable_raw_snippet_url(snippet, opts = {}, only_path: false)
+ reliable_snippet_helper(snippet, opts) do |updated_opts|
+ if snippet.project_id?
+ raw_project_snippet_url(snippet.project, snippet, nil, updated_opts.merge({ only_path: only_path }))
+ else
+ raw_snippet_url(snippet, nil, updated_opts.merge({ only_path: only_path }))
+ end
end
end
+ def reliable_snippet_helper(snippet, opts)
+ opts[:secret] = snippet.secret if snippet.secret?
+
+ yield(opts)
+ end
+
+ def download_raw_snippet_button(snippet)
+ link_to(icon('download'), reliable_raw_snippet_path(snippet, inline: false), target: '_blank', rel: 'noopener noreferrer', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' })
+ end
+
+ def shareable_snippets_link(snippet)
+ url = reliable_snippet_url(snippet)
+ link_to(url, url, id: 'shareable_link_url', title: 'Open')
+ end
+
# Return the path of a snippets index for a user or for a project
#
# @returns String, path to snippet index
@@ -114,8 +141,28 @@ module SnippetsHelper
{ snippet_object: snippet, snippet_chunks: snippet_chunks }
end
- def snippet_embed
- "<script src=\"#{url_for(only_path: false, overwrite_params: nil)}.js\"></script>"
+ def snippet_embed(snippet)
+ content_tag(:script, nil, src: reliable_snippet_url(snippet))
+ end
+
+ def snippet_badge(snippet)
+ attrs = snippet_badge_attributes(snippet)
+ if attrs
+ css_class, text = attrs
+ tag.span(class: ['badge', 'badge-gray']) do
+ concat(tag.i(class: ['fa', css_class]))
+ concat(' ')
+ concat(_(text))
+ end
+ end
+ end
+
+ def snippet_badge_attributes(snippet)
+ if snippet.private?
+ ['fa-lock', 'private']
+ elsif snippet.secret?
+ ['fa-user-secret', 'secret']
+ end
end
def embedded_snippet_raw_button
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index b2fca65b9e0..1326f4f84bd 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -41,17 +41,20 @@ class Snippet < ApplicationRecord
delegate :name, :email, to: :author, prefix: true, allow_nil: true
+ before_save :ensure_secret_added_if_needed
+
validates :author, presence: true
validates :title, presence: true, length: { maximum: 255 }
validates :file_name,
length: { maximum: 255 }
validates :content, presence: true
- validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values }
+ validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.all_values }
# Scopes
- scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_private, -> { where(visibility_level: Snippet::PRIVATE) }
+ scope :are_secret, -> { where(visibility_level: Snippet::SECRET) }
+ scope :are_internal, -> { where(visibility_level: Snippet::INTERNAL) }
scope :are_public, -> { where(visibility_level: Snippet::PUBLIC) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
@@ -64,6 +67,12 @@ class Snippet < ApplicationRecord
attr_spammable :title, spam_title: true
attr_spammable :content, spam_description: true
+ attr_encrypted :secret,
+ key: Gitlab::Application.secrets.otp_key_base,
+ mode: :per_attribute_iv_and_salt,
+ insecure_mode: true,
+ algorithm: 'aes-256-cbc'
+
def self.with_optional_visibility(value = nil)
if value
where(visibility_level: value)
@@ -177,9 +186,7 @@ class Snippet < ApplicationRecord
end
def embeddable?
- ability = project_id? ? :read_project_snippet : :read_personal_snippet
-
- Ability.allowed?(nil, ability, self)
+ public? || visibility_secret?
end
def notes_with_associations
@@ -226,4 +233,13 @@ class Snippet < ApplicationRecord
::Project
end
end
+
+ private
+
+ def ensure_secret_added_if_needed
+ return unless visibility_secret?
+ return if self.secret
+
+ self.secret = SecureRandom.hex
+ end
end
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index b649fe91c24..1cbd80dcf5e 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -4,7 +4,7 @@
= render 'dashboard/snippets_head'
- if current_user.snippets.exists?
- = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true }
+ = render partial: 'snippets/snippets_scope_menu', locals: { include_private: true, include_secret: true }
.d-block.d-sm-none
&nbsp;
diff --git a/app/views/projects/snippets/index.html.haml b/app/views/projects/snippets/index.html.haml
index 7682d01a5a1..a0ceb88ee7d 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -4,7 +4,7 @@
- if current_user
.top-area
- include_private = @project.team.member?(current_user) || current_user.admin?
- = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private }
+ = render partial: 'snippets/snippets_scope_menu', locals: { subject: @project, include_private: include_private, include_secret: false }
- if can?(current_user, :create_project_snippet, @project)
.nav-controls
diff --git a/app/views/search/results/_snippet_blob.html.haml b/app/views/search/results/_snippet_blob.html.haml
index f17dae0a94c..267996d305e 100644
--- a/app/views/search/results/_snippet_blob.html.haml
+++ b/app/views/search/results/_snippet_blob.html.haml
@@ -3,14 +3,20 @@
- snippet_chunks = snippet_blob[:snippet_chunks]
.search-result-row
- %span
- = snippet.title
- by
- = link_to user_snippets_path(snippet.author) do
- = image_tag avatar_icon_for_user(snippet.author), class: "avatar avatar-inline s16", alt: ''
- = snippet.author_name
- %span.light= time_ago_with_tooltip(snippet.created_at)
%h4.snippet-title
+ = link_to reliable_snippet_path(snippet) do
+ = snippet.title
+
+ .snippet-box.has-tooltip.inline.append-right-5.append-bottom-10{ title: snippet_visibility_level_description(snippet.visibility_level, snippet), data: { container: "body" } }
+ %span.sr-only
+ = visibility_level_label(snippet.visibility_level)
+ = visibility_level_icon(snippet.visibility_level, fw: false)
+ %span.creator
+ Authored
+ = time_ago_with_tooltip(snippet.created_at, placement: 'bottom', html_class: 'snippet_updated_ago')
+ by #{link_to_member(snippet.project, snippet.author, size: 24, author_class: "author item-title", avatar_class: "d-none d-sm-inline")}
+ = user_status(snippet.author)
+
- snippet_path = reliable_snippet_path(snippet)
.file-holder
.js-file-title.file-title
diff --git a/app/views/search/results/_snippet_title.html.haml b/app/views/search/results/_snippet_title.html.haml
index 1e01088d9e6..7280146720e 100644
--- a/app/views/search/results/_snippet_title.html.haml
+++ b/app/views/search/results/_snippet_title.html.haml
@@ -2,10 +2,7 @@
%h4.snippet-title.term
= link_to reliable_snippet_path(snippet_title) do
= truncate(snippet_title.title, length: 60)
- - if snippet_title.private?
- %span.badge.badge-gray
- %i.fa.fa-lock
- = _("private")
+ = snippet_badge(snippet_title)
%span.cgray.monospace.tiny.float-right.term
= snippet_title.file_name
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 82ffdc9cd13..70f5ee630b4 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,4 +1,4 @@
-- Gitlab::VisibilityLevel.values.each do |level|
+- Gitlab::VisibilityLevel.values_for(form_model).each do |level|
- disallowed = disallowed_visibility_level?(form_model, level)
- restricted = restricted_visibility_levels.include?(level)
- next if disallowed || restricted
diff --git a/app/views/shared/snippets/_blob.html.haml b/app/views/shared/snippets/_blob.html.haml
index 2132fcbccc5..6a5e777706c 100644
--- a/app/views/shared/snippets/_blob.html.haml
+++ b/app/views/shared/snippets/_blob.html.haml
@@ -8,7 +8,6 @@
.btn-group{ role: "group" }<
= copy_blob_source_button(blob)
= open_raw_blob_button(blob)
-
- = link_to icon('download'), download_snippet_path(@snippet), target: '_blank', class: "btn btn-sm has-tooltip", title: 'Download', data: { container: 'body' }
+ = download_raw_snippet_button(@snippet)
= render 'projects/blob/content', blob: blob
diff --git a/app/views/shared/snippets/_embed.html.haml b/app/views/shared/snippets/_embed.html.haml
index c7f0511d1de..9a8881795ac 100644
--- a/app/views/shared/snippets/_embed.html.haml
+++ b/app/views/shared/snippets/_embed.html.haml
@@ -1,3 +1,4 @@
+# I don't think this is in use any longer
- blob = @snippet.blob
.gitlab-embed-snippets
.js-file-title.file-title-flex-parent
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index ebb634fe75f..ce45cc2440c 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -44,7 +44,7 @@
%li
%button.js-share-btn.btn.btn-transparent{ type: 'button' }
%strong.embed-toggle-list-item= _("Share")
- %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed }
+ %input.js-snippet-url-area.snippet-embed-input.form-control{ type: "text", autocomplete: 'off', value: snippet_embed(@snippet) }
.input-group-append
= clipboard_button(title: s_('Copy to clipboard'), class: 'js-clipboard-btn snippet-clipboard-btn btn btn-default', target: '.js-snippet-url-area')
.clearfix
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
index c312226dd6c..1eb34842881 100644
--- a/app/views/snippets/_snippets_scope_menu.html.haml
+++ b/app/views/snippets/_snippets_scope_menu.html.haml
@@ -24,6 +24,13 @@
%span.badge.badge-pill
= subject.snippets.are_internal.count
+ - if include_secret
+ %li{ class: active_when(params[:scope] == "are_secret") }
+ = link_to subject_snippets_path(subject, scope: 'are_secret') do
+ Secret
+ %span.badge.badge-pill
+ = subject.snippets.are_secret.count
+
%li{ class: active_when(params[:scope] == "are_public") }
= link_to subject_snippets_path(subject, scope: 'are_public') do
= _("Public")
diff --git a/app/views/snippets/show.html.haml b/app/views/snippets/show.html.haml
index 36b4e00e8d5..afa56465258 100644
--- a/app/views/snippets/show.html.haml
+++ b/app/views/snippets/show.html.haml
@@ -10,7 +10,7 @@
%article.file-holder.snippet-file-content
= render 'shared/snippets/blob'
- .row-content-block.top-block.content-component-block
+ .row-content-block.top-block.content-component-block.append-bottom-10
= render 'award_emoji/awards_block', awardable: @snippet, inline: true
#notes.limited-width-notes= render "shared/notes/notes_with_form", :autocomplete => false
diff --git a/changelogs/unreleased/13235-secret-snippets.yml b/changelogs/unreleased/13235-secret-snippets.yml
new file mode 100644
index 00000000000..e2635abe697
--- /dev/null
+++ b/changelogs/unreleased/13235-secret-snippets.yml
@@ -0,0 +1,5 @@
+---
+title: Support Secret Snippets
+merge_request: 24042
+author:
+type: changed
diff --git a/db/migrate/20181227063544_add_secret_word_to_snippet.rb b/db/migrate/20181227063544_add_secret_word_to_snippet.rb
new file mode 100644
index 00000000000..151ef94811c
--- /dev/null
+++ b/db/migrate/20181227063544_add_secret_word_to_snippet.rb
@@ -0,0 +1,11 @@
+# frozen_string_literal: true
+
+class AddSecretWordToSnippet < ActiveRecord::Migration[5.0]
+ DOWNTIME = false
+
+ def change
+ add_column :snippets, :encrypted_secret, :string, limit: 25
+ add_column :snippets, :encrypted_secret_iv, :string, limit: 25
+ add_column :snippets, :encrypted_secret_salt, :string, limit: 25
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 70f3a42e7f8..f92a93a8518 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -3238,6 +3238,9 @@ ActiveRecord::Schema.define(version: 2019_09_05_223900) do
t.integer "cached_markdown_version"
t.text "description"
t.text "description_html"
+ t.string "encrypted_secret"
+ t.string "encrypted_secret_iv"
+ t.string "encrypted_secret_salt"
t.index ["author_id"], name: "index_snippets_on_author_id"
t.index ["file_name"], name: "index_snippets_on_file_name_trigram", opclass: :gin_trgm_ops, using: :gin
t.index ["project_id"], name: "index_snippets_on_project_id"
diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml
index 511b702553e..f89e1f06ac6 100644
--- a/lib/gitlab/import_export/import_export.yml
+++ b/lib/gitlab/import_export/import_export.yml
@@ -156,6 +156,9 @@ excluded_attributes:
- :identifier
snippets:
- :expired_at
+ - :encrypted_secret
+ - :encrypted_secret_iv
+ - :encrypted_secret_salt
merge_request_diff:
- :external_diff
- :stored_externally
diff --git a/lib/gitlab/snippet_search_results.rb b/lib/gitlab/snippet_search_results.rb
index ac3b219e0c7..b51049e0a78 100644
--- a/lib/gitlab/snippet_search_results.rb
+++ b/lib/gitlab/snippet_search_results.rb
@@ -45,13 +45,13 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def snippet_titles
- limit_snippets.search(query).order('updated_at DESC').includes(:author)
+ limit_snippets.search(query).order('updated_at DESC').inc_relations_for_view
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def snippet_blobs
- limit_snippets.search_code(query).order('updated_at DESC').includes(:author)
+ limit_snippets.search_code(query).order('updated_at DESC').inc_relations_for_view
end
# rubocop: enable CodeReuse/ActiveRecord
diff --git a/locale/gitlab.pot b/locale/gitlab.pot
index 32deab7dd68..a271dc1d627 100644
--- a/locale/gitlab.pot
+++ b/locale/gitlab.pot
@@ -13019,6 +13019,9 @@ msgstr ""
msgid "VisibilityLevel|Public"
msgstr ""
+msgid "VisibilityLevel|Secret"
+msgstr ""
+
msgid "VisibilityLevel|Unknown"
msgstr ""
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index 9c3a0fbe9b3..0b606f3daa1 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -12,6 +12,10 @@ FactoryBot.define do
visibility_level Snippet::PUBLIC
end
+ trait :secret do
+ visibility_level Snippet::SECRET
+ end
+
trait :internal do
visibility_level Snippet::INTERNAL
end
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index ce5e037f88d..8f54605e8e8 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -3,7 +3,91 @@ require 'spec_helper'
describe SnippetsHelper do
include IconsHelper
- describe '#embedded_snippet_raw_button' do
+ describe '.reliable_snippet_path' do
+ context 'personal snippets' do
+ context 'public' do
+ it 'gives a full path' do
+ snippet = create(:personal_snippet, :public)
+
+ expect(reliable_snippet_path(snippet)).to eq("/snippets/#{snippet.id}")
+ end
+ end
+
+ context 'secret' do
+ it 'gives a full path, including secret word' do
+ snippet = create(:personal_snippet, :secret)
+
+ expect(reliable_snippet_path(snippet)).to match(%r{/snippets/#{snippet.id}\?secret=\w+})
+ end
+ end
+ end
+
+ context 'project snippets' do
+ it 'gives a full path' do
+ snippet = create(:project_snippet, :public)
+
+ expect(reliable_snippet_path(snippet)).to eq("/#{snippet.project.full_path}/snippets/#{snippet.id}")
+ end
+ end
+ end
+
+ describe '.reliable_snippet_url' do
+ context 'personal snippets' do
+ context 'public' do
+ it 'gives a full url' do
+ snippet = create(:personal_snippet, :public)
+
+ expect(reliable_snippet_url(snippet)).to eq("http://test.host/snippets/#{snippet.id}")
+ end
+ end
+
+ context 'secret' do
+ it 'gives a full url, including secret word' do
+ snippet = create(:personal_snippet, :secret)
+
+ expect(reliable_snippet_url(snippet)).to match(%r{http://test.host/snippets/#{snippet.id}\?secret=\w+})
+ end
+ end
+ end
+
+ context 'project snippets' do
+ it 'gives a full url' do
+ snippet = create(:project_snippet, :public)
+
+ expect(reliable_snippet_url(snippet)).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}")
+ end
+ end
+ end
+
+ describe '.shareable_snippets_link' do
+ context 'personal snippets' do
+ context 'public' do
+ it 'gives a full link' do
+ snippet = create(:personal_snippet, :public)
+
+ expect(reliable_snippet_url(snippet)).to eq("http://test.host/snippets/#{snippet.id}")
+ end
+ end
+
+ context 'secret' do
+ it 'gives a full link, including secret word' do
+ snippet = create(:personal_snippet, :secret)
+
+ expect(reliable_snippet_url(snippet)).to match(%r{http://test.host/snippets/#{snippet.id}\?secret=\w+})
+ end
+ end
+ end
+
+ context 'project snippets' do
+ it 'gives a full link' do
+ snippet = create(:project_snippet, :public)
+
+ expect(reliable_snippet_url(snippet)).to eq("http://test.host/#{snippet.project.full_path}/snippets/#{snippet.id}")
+ end
+ end
+ end
+
+ describe '.embedded_snippet_raw_button' do
it 'gives view raw button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
@@ -17,7 +101,7 @@ describe SnippetsHelper do
end
end
- describe '#embedded_snippet_download_button' do
+ describe '.embedded_snippet_download_button' do
it 'gives download button of embedded snippets for project snippets' do
@snippet = create(:project_snippet, :public)
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 82116b8f57e..4950636021d 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -182,12 +182,12 @@ describe Gitlab::VisibilityLevel do
describe '.string_values' do
it 'returns an Array of const values (including Secret)' do
expect(described_class.string_values)
- .to eq([
- 'private',
- 'secret',
- 'internal',
- 'public'
- ])
+ .to eq(%w{
+ private
+ secret
+ internal
+ public
+ })
end
end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index 3524cdae3b8..0d47041ded4 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -462,4 +462,44 @@ describe Snippet do
end
end
end
+
+ describe '#ensure_secret_added_if_needed' do
+ let(:snippet) { create(:snippet) }
+
+ context 'visibility_level is NOT SECRET' do
+ it 'assigns a random hex value' do
+ snippet.visibility_level = Gitlab::VisibilityLevel::PUBLIC
+ snippet.save
+ expect(snippet.secret).to be_nil
+ end
+ end
+
+ context 'visibility_level is SECRET' do
+ it 'assigns a random hex value' do
+ snippet.visibility_level = Gitlab::VisibilityLevel::SECRET
+ snippet.save
+ expect(snippet.secret).not_to be_nil
+ end
+ end
+ end
+
+ describe '#visibility_secret?' do
+ let(:snippet) { create(:snippet) }
+
+ context 'for a Snippet that is not Secret' do
+ it 'returns false' do
+ snippet.visibility_level = Gitlab::VisibilityLevel::PUBLIC
+ snippet.save
+ expect(snippet.visibility_secret?).to be_falsey
+ end
+ end
+
+ context 'for a Snippet that is Secret' do
+ it 'returns true' do
+ snippet.visibility_level = Gitlab::VisibilityLevel::SECRET
+ snippet.save
+ expect(snippet.visibility_secret?).to be_truthy
+ end
+ end
+ end
end