summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAsh McKenzie <amckenzie@gitlab.com>2018-06-10 22:56:17 +1000
committerAsh McKenzie <amckenzie@gitlab.com>2018-06-13 12:11:38 +1000
commit2f297034036055aaf37f86ece22723aedf8741bf (patch)
tree731fab3c82dd23d63ba9b89ad101d11e409d2fe0
parentf646a8b9bc95fd6cecaa754f7dd0e8370c201502 (diff)
downloadgitlab-ce-ash.mckenzie/secret-snippets.tar.gz
-rw-r--r--app/controllers/concerns/snippets_url.rb39
-rw-r--r--app/controllers/snippets_controller.rb10
-rw-r--r--app/finders/snippets_finder.rb2
-rw-r--r--app/helpers/icons_helper.rb2
-rw-r--r--app/helpers/snippets_helper.rb19
-rw-r--r--app/helpers/visibility_level_helper.rb6
-rw-r--r--app/models/snippet.rb13
-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/shared/_visibility_radios.html.haml2
-rw-r--r--app/views/shared/snippets/_header.html.haml4
-rw-r--r--app/views/snippets/_snippets_scope_menu.html.haml7
-rw-r--r--db/migrate/20180610102249_add_secret_word_to_snippet.rb9
-rw-r--r--db/schema.rb3
-rw-r--r--lib/gitlab/visibility_level.rb40
-rw-r--r--spec/factories/snippets.rb3
-rw-r--r--spec/helpers/snippets_helper_spec.rb84
-rw-r--r--spec/helpers/visibility_level_helper_spec.rb31
-rw-r--r--spec/lib/gitlab/visibility_level_spec.rb25
-rw-r--r--spec/models/snippet_spec.rb19
20 files changed, 303 insertions, 19 deletions
diff --git a/app/controllers/concerns/snippets_url.rb b/app/controllers/concerns/snippets_url.rb
new file mode 100644
index 00000000000..e79b7194b27
--- /dev/null
+++ b/app/controllers/concerns/snippets_url.rb
@@ -0,0 +1,39 @@
+module SnippetsUrl
+ extend ActiveSupport::Concern
+
+ private
+
+ attr_reader :snippet
+
+ def authorize_secret_snippet!
+ if snippet.secret?
+ return if params[:secret] == snippet.secret_word
+
+ return render_404
+ end
+
+ current_user ? render_404 : authenticate_user!
+ end
+
+ def ensure_complete_url
+ redirect_to complete_url unless url_contains_secret?
+ end
+
+ def url_contains_secret?
+ request.query_parameters['secret'] == snippet.secret_word
+ end
+
+ def complete_url
+ @complete_url ||= begin
+ url = current_url
+ query_hash = Rack::Utils.parse_nested_query(url.query)
+ query_hash['secret'] = snippet.secret_word
+ url.query = query_hash.to_query
+ url.to_s
+ end
+ end
+
+ def current_url
+ @current_url ||= URI.parse(request.original_url)
+ end
+end
diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb
index 3d51520ddf4..890614c0e28 100644
--- a/app/controllers/snippets_controller.rb
+++ b/app/controllers/snippets_controller.rb
@@ -3,6 +3,7 @@ class SnippetsController < ApplicationController
include ToggleAwardEmoji
include SpammableActions
include SnippetsActions
+ include SnippetsUrl
include RendersBlob
include PreviewMarkdown
@@ -13,6 +14,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]
@@ -108,11 +112,7 @@ class SnippetsController < ApplicationController
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 d498a2d6d11..b678f481ca5 100644
--- a/app/finders/snippets_finder.rb
+++ b/app/finders/snippets_finder.rb
@@ -124,6 +124,8 @@ class SnippetsFinder < UnionFinder
Snippet::PRIVATE
when 'are_internal'
Snippet::INTERNAL
+ when 'are_secret'
+ Snippet::SECRET
when 'are_public'
Snippet::PUBLIC
else
diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb
index 2f304b040c7..12406673836 100644
--- a/app/helpers/icons_helper.rb
+++ b/app/helpers/icons_helper.rb
@@ -82,6 +82,8 @@ module IconsHelper
'lock'
when Gitlab::VisibilityLevel::INTERNAL
'shield'
+ when Gitlab::VisibilityLevel::SECRET
+ 'user-secret'
else # Gitlab::VisibilityLevel::PUBLIC
'globe'
end
diff --git a/app/helpers/snippets_helper.rb b/app/helpers/snippets_helper.rb
index 733832c1bbb..f5db47cd6a3 100644
--- a/app/helpers/snippets_helper.rb
+++ b/app/helpers/snippets_helper.rb
@@ -1,5 +1,7 @@
module SnippetsHelper
- def reliable_snippet_path(snippet, opts = nil)
+ def reliable_snippet_path(snippet, opts = {})
+ opts[:secret] = snippet.secret_word if snippet.secret?
+
if snippet.project_id?
project_snippet_path(snippet.project, snippet, opts)
else
@@ -7,6 +9,21 @@ module SnippetsHelper
end
end
+ def reliable_snippet_url(snippet, opts = {})
+ opts[:secret] = snippet.secret_word if snippet.secret?
+
+ if snippet.project_id?
+ project_snippet_url(snippet.project, snippet, opts)
+ else
+ snippet_url(snippet, opts)
+ end
+ end
+
+ def shareable_snippets_link(snippet)
+ url = reliable_snippet_url(snippet)
+ link_to(url, url, id: 'shareable_link_url', title: 'Open')
+ end
+
def download_snippet_path(snippet)
if snippet.project_id
raw_project_snippet_path(@project, snippet, inline: false)
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index e395cda03d3..f2c81c87f3b 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -60,6 +60,8 @@ module VisibilityLevelHelper
"The snippet is visible to any logged in user."
when Gitlab::VisibilityLevel::PUBLIC
"The snippet can be accessed without any authentication."
+ when Gitlab::VisibilityLevel::SECRET
+ "The snippet can be accessed without any authentication, but is not searchable."
end
end
@@ -143,9 +145,7 @@ module VisibilityLevelHelper
end
def visibility_level_label(level)
- # The visibility level can be:
- # 'VisibilityLevel|Private', 'VisibilityLevel|Internal', 'VisibilityLevel|Public'
- s_(Project.visibility_levels.key(level))
+ s_(::Gitlab::VisibilityLevel.all_options.key(level))
end
def restricted_visibility_levels(show_all = false)
diff --git a/app/models/snippet.rb b/app/models/snippet.rb
index 644120453cf..4ee229e3013 100644
--- a/app/models/snippet.rb
+++ b/app/models/snippet.rb
@@ -35,18 +35,21 @@ class Snippet < ActiveRecord::Base
delegate :name, :email, to: :author, prefix: true, allow_nil: true
+ before_save :ensure_secret_word_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_public, -> { where(visibility_level: Snippet::PUBLIC) }
+ scope :are_secret, -> { where(visibility_level: Snippet::SECRET) }
scope :public_and_internal, -> { where(visibility_level: [Snippet::PUBLIC, Snippet::INTERNAL]) }
scope :fresh, -> { order("created_at DESC") }
@@ -173,4 +176,12 @@ class Snippet < ActiveRecord::Base
::Project
end
end
+
+ private
+
+ def ensure_secret_word_added_if_needed
+ return if secret? && self.secret_word
+
+ self.secret_word = secret? ? SecureRandom.hex : nil
+ end
end
diff --git a/app/views/dashboard/snippets/index.html.haml b/app/views/dashboard/snippets/index.html.haml
index 4391624196b..7c2638baee2 100644
--- a/app/views/dashboard/snippets/index.html.haml
+++ b/app/views/dashboard/snippets/index.html.haml
@@ -3,7 +3,7 @@
- header_title "Snippets", dashboard_snippets_path
= render 'dashboard/snippets_head'
-= 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 65efc083fdd..7a3a16ad44e 100644
--- a/app/views/projects/snippets/index.html.haml
+++ b/app/views/projects/snippets/index.html.haml
@@ -3,7 +3,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 }
.nav-controls
- if can?(current_user, :create_project_snippet, @project)
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index dd6b9cce58e..8a95ab52f41 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)
- disabled = disallowed || restricted
diff --git a/app/views/shared/snippets/_header.html.haml b/app/views/shared/snippets/_header.html.haml
index 828ec870dc0..f644d4ca871 100644
--- a/app/views/shared/snippets/_header.html.haml
+++ b/app/views/shared/snippets/_header.html.haml
@@ -26,6 +26,10 @@
%textarea.hidden.js-task-list-field
= @snippet.description
+ .shareable_link
+ = shareable_snippets_link(@snippet)
+ = clipboard_button(title: s_('Copy to clipboard'), class: 'js-clipboard-btn snippet-clipboard-btn', target: '#shareable_link_url')
+
- if @snippet.updated_at != @snippet.created_at
= edited_time_ago_with_tooltip(@snippet, placement: 'bottom', html_class: 'snippet-edited-ago', exclude_author: true)
diff --git a/app/views/snippets/_snippets_scope_menu.html.haml b/app/views/snippets/_snippets_scope_menu.html.haml
index dc4b0fd9ba0..6c1ed174bc0 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/db/migrate/20180610102249_add_secret_word_to_snippet.rb b/db/migrate/20180610102249_add_secret_word_to_snippet.rb
new file mode 100644
index 00000000000..315bd48c8aa
--- /dev/null
+++ b/db/migrate/20180610102249_add_secret_word_to_snippet.rb
@@ -0,0 +1,9 @@
+class AddSecretWordToSnippet < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :snippets, :secret_word, :string
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index d05c6afbb9f..6fc0e16a768 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20180608201435) do
+ActiveRecord::Schema.define(version: 20180610102249) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -1833,6 +1833,7 @@ ActiveRecord::Schema.define(version: 20180608201435) do
t.integer "cached_markdown_version"
t.text "description"
t.text "description_html"
+ t.string "secret_word"
end
add_index "snippets", ["author_id"], name: "index_snippets_on_author_id", using: :btree
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 2612208a927..d01b2ed0ced 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -20,10 +20,9 @@ module Gitlab
PRIVATE = 0 unless const_defined?(:PRIVATE)
INTERNAL = 10 unless const_defined?(:INTERNAL)
PUBLIC = 20 unless const_defined?(:PUBLIC)
+ SECRET = 30 unless const_defined?(:SECRET)
class << self
- delegate :values, to: :options
-
def levels_for_user(user = nil)
return [PUBLIC] unless user
@@ -36,8 +35,13 @@ module Gitlab
end
end
- def string_values
- string_options.keys
+ def values_for(model)
+ case model
+ when PersonalSnippet
+ all_values
+ else
+ values
+ end
end
def options
@@ -48,11 +52,33 @@ module Gitlab
}
end
+ def values
+ options.values
+ end
+
+ def all_options
+ {
+ N_('VisibilityLevel|Private') => PRIVATE,
+ N_('VisibilityLevel|Internal') => INTERNAL,
+ N_('VisibilityLevel|Public') => PUBLIC,
+ N_('VisibilityLevel|Secret') => SECRET
+ }
+ end
+
+ def all_values
+ all_options.values
+ end
+
+ def string_values
+ string_options.keys
+ end
+
def string_options
{
'private' => PRIVATE,
'internal' => INTERNAL,
- 'public' => PUBLIC
+ 'public' => PUBLIC,
+ 'secret' => SECRET
}
end
@@ -125,6 +151,10 @@ module Gitlab
visibility_level_value == PUBLIC
end
+ def secret?
+ visibility_level_value == SECRET
+ end
+
def visibility_level_value
self[visibility_level_field]
end
diff --git a/spec/factories/snippets.rb b/spec/factories/snippets.rb
index dc12b562108..3ac445d5587 100644
--- a/spec/factories/snippets.rb
+++ b/spec/factories/snippets.rb
@@ -25,5 +25,8 @@ FactoryBot.define do
end
factory :personal_snippet, parent: :snippet, class: :PersonalSnippet do
+ trait :secret do
+ visibility_level Snippet::SECRET
+ end
end
end
diff --git a/spec/helpers/snippets_helper_spec.rb b/spec/helpers/snippets_helper_spec.rb
index 0323ffb641c..1860fee89a0 100644
--- a/spec/helpers/snippets_helper_spec.rb
+++ b/spec/helpers/snippets_helper_spec.rb
@@ -3,6 +3,90 @@ require 'spec_helper'
describe SnippetsHelper do
include IconsHelper
+ 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/1')
+ 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/2\?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('/namespace1/project1/snippets/3')
+ 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/1')
+ 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/2\?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/namespace1/project1/snippets/3')
+ 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('/snippets/1')
+ 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 eq(%r{/snippets/2\?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('/namespace1/project1/snippets/3')
+ 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)
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 5077c89d7b4..fb132bae73b 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -52,6 +52,11 @@ describe VisibilityLevelHelper do
.to eq "The snippet is visible only to project members."
end
+ it 'describes visibility for secret snippets' do
+ expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::SECRET, personal_snippet))
+ .to eq "The snippet can be accessed without any authentication, but is not searchable."
+ end
+
it 'defaults to personal snippet' do
expect(snippet_visibility_level_description(Gitlab::VisibilityLevel::PRIVATE))
.to eq "The snippet is visible only to me."
@@ -137,4 +142,30 @@ describe VisibilityLevelHelper do
end
end
end
+
+ describe '.visibility_level_label' do
+ context 'PRIVATE' do
+ it 'returns Private' do
+ expect(visibility_level_label(Gitlab::VisibilityLevel::PRIVATE)).to eq('Private')
+ end
+ end
+
+ context 'INTERNAL' do
+ it 'returns Internal' do
+ expect(visibility_level_label(Gitlab::VisibilityLevel::INTERNAL)).to eq('Internal')
+ end
+ end
+
+ context 'PUBLIC' do
+ it 'returns Public' do
+ expect(visibility_level_label(Gitlab::VisibilityLevel::PUBLIC)).to eq('Public')
+ end
+ end
+
+ context 'SECRET' do
+ it 'returns Secret' do
+ expect(visibility_level_label(Gitlab::VisibilityLevel::SECRET)).to eq('Secret')
+ end
+ end
+ end
end
diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb
index 2c1146ceff5..44163ebd05f 100644
--- a/spec/lib/gitlab/visibility_level_spec.rb
+++ b/spec/lib/gitlab/visibility_level_spec.rb
@@ -85,4 +85,29 @@ describe Gitlab::VisibilityLevel do
.to eq(described_class::PRIVATE)
end
end
+
+ describe '.values_for' do
+ context 'PersonalSnippet' do
+ it 'returns PRIVATE, INTERNAL, PUBLIC and SECRET' do
+ expect(described_class.values_for(PersonalSnippet.new))
+ .to eq([
+ Gitlab::VisibilityLevel::PRIVATE,
+ Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC,
+ Gitlab::VisibilityLevel::SECRET
+ ])
+ end
+ end
+
+ context 'any other model' do
+ it 'returns PRIVATE, INTERNAL and PUBLIC' do
+ expect(described_class.values_for(Project.new))
+ .to eq([
+ Gitlab::VisibilityLevel::PRIVATE,
+ Gitlab::VisibilityLevel::INTERNAL,
+ Gitlab::VisibilityLevel::PUBLIC
+ ])
+ end
+ end
+ end
end
diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb
index e09d89d235d..81dedc89343 100644
--- a/spec/models/snippet_spec.rb
+++ b/spec/models/snippet_spec.rb
@@ -212,4 +212,23 @@ describe Snippet do
expect(blob.data).to eq(snippet.content)
end
end
+
+ describe '#ensure_secret_word_added_if_needed' do
+ let(:snippet) { create(:snippet) }
+
+ context 'visibility_level is SECRET' do
+ it 'assigns a random hex value' do
+ snippet.visibility_level = Gitlab::VisibilityLevel::SECRET
+ snippet.save
+ expect(snippet.secret_word).not_to be_nil
+ end
+ end
+
+ context 'visibility_level is NOT SECRET' do
+ it 'assigns a random hex value' do
+ snippet.save
+ expect(snippet.secret_word).to be_nil
+ end
+ end
+ end
end