From 0e31b424fb9a07ea5ba8f6d864ff726533e8ba85 Mon Sep 17 00:00:00 2001 From: Ash McKenzie Date: Thu, 27 Dec 2018 17:46:38 +1100 Subject: Add Secret support for 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. --- app/assets/javascripts/snippet/snippet_embed.js | 14 +++- app/controllers/concerns/snippets_url.rb | 64 ++++++++++++++++ app/controllers/snippets_controller.rb | 13 ++-- app/finders/snippets_finder.rb | 2 + app/helpers/blob_helper.rb | 6 +- app/helpers/icons_helper.rb | 2 + app/helpers/snippets_helper.rb | 71 ++++++++++++++--- app/models/snippet.rb | 26 +++++-- app/views/dashboard/snippets/index.html.haml | 2 +- app/views/projects/snippets/index.html.haml | 2 +- app/views/search/results/_snippet_blob.html.haml | 20 +++-- app/views/search/results/_snippet_title.html.haml | 5 +- app/views/shared/_visibility_radios.html.haml | 2 +- app/views/shared/snippets/_blob.html.haml | 3 +- app/views/shared/snippets/_embed.html.haml | 1 + app/views/shared/snippets/_header.html.haml | 2 +- app/views/snippets/_snippets_scope_menu.html.haml | 7 ++ app/views/snippets/show.html.haml | 2 +- changelogs/unreleased/13235-secret-snippets.yml | 5 ++ .../20181227063544_add_secret_word_to_snippet.rb | 11 +++ db/schema.rb | 3 + lib/gitlab/import_export/import_export.yml | 3 + lib/gitlab/snippet_search_results.rb | 4 +- locale/gitlab.pot | 3 + spec/factories/snippets.rb | 4 + spec/helpers/snippets_helper_spec.rb | 88 +++++++++++++++++++++- spec/lib/gitlab/visibility_level_spec.rb | 12 +-- spec/models/snippet_spec.rb | 40 ++++++++++ 28 files changed, 360 insertions(+), 57 deletions(-) create mode 100644 app/controllers/concerns/snippets_url.rb create mode 100644 changelogs/unreleased/13235-secret-snippets.yml create mode 100644 db/migrate/20181227063544_add_secret_word_to_snippet.rb 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 - "" + 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   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 -- cgit v1.2.3