summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-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
18 files changed, 197 insertions, 47 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