Commit 5e9e5692 authored by Douwe Maan's avatar Douwe Maan Committed by Robert Speicher

Merge branch...

Merge branch 'security-10-4-25223-snippets-finder-doesnt-obey-feature-visibility' into 'security-10-4'

[Port for security-10-4]: Makes SnippetFinder ensure feature visibility
parent 721fab66
# Snippets Finder
#
# Used to filter Snippets collections by a set of params
#
# Arguments.
#
# current_user - The current user, nil also can be used.
# params:
# visibility (integer) - Individual snippet visibility: Public(20), internal(10) or private(0).
# project (Project) - Project related.
# author (User) - Author related.
#
# params are optional
class SnippetsFinder < UnionFinder class SnippetsFinder < UnionFinder
attr_accessor :current_user, :params include Gitlab::Allowable
attr_accessor :current_user, :params, :project
def initialize(current_user, params = {}) def initialize(current_user, params = {})
@current_user = current_user @current_user = current_user
@params = params @params = params
@project = params[:project]
end end
def execute def execute
items = init_collection items = init_collection
items = by_project(items)
items = by_author(items) items = by_author(items)
items = by_visibility(items) items = by_visibility(items)
...@@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder ...@@ -18,25 +32,42 @@ class SnippetsFinder < UnionFinder
private private
def init_collection def init_collection
items = Snippet.all if project.present?
authorized_snippets_from_project
else
authorized_snippets
end
end
accessible(items) def authorized_snippets_from_project
if can?(current_user, :read_project_snippet, project)
if project.team.member?(current_user)
project.snippets
else
project.snippets.public_to_user(current_user)
end
else
Snippet.none
end
end end
def accessible(items) def authorized_snippets
segments = [] Snippet.where(feature_available_projects.or(not_project_related)).public_or_visible_to_user(current_user)
segments << items.public_to_user(current_user) end
segments << authorized_to_user(items) if current_user
find_union(segments, Snippet.includes(:author)) def feature_available_projects
projects = Project.public_or_visible_to_user(current_user)
.with_feature_available_for_user(:snippets, current_user).select(:id)
arel_query = Arel::Nodes::SqlLiteral.new(projects.to_sql)
table[:project_id].in(arel_query)
end end
def authorized_to_user(items) def not_project_related
items.where( table[:project_id].eq(nil)
'author_id = :author_id end
OR project_id IN (:project_ids)',
author_id: current_user.id, def table
project_ids: current_user.authorized_projects.select(:id)) Snippet.arel_table
end end
def by_visibility(items) def by_visibility(items)
...@@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder ...@@ -53,12 +84,6 @@ class SnippetsFinder < UnionFinder
items.where(author_id: params[:author].id) items.where(author_id: params[:author].id)
end end
def by_project(items)
return items unless params[:project]
items.where(project_id: params[:project].id)
end
def visibility_from_scope def visibility_from_scope
case params[:scope].to_s case params[:scope].to_s
when 'are_private' when 'are_private'
......
...@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base ...@@ -74,6 +74,27 @@ class Snippet < ActiveRecord::Base
@link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/) @link_reference_pattern ||= super("snippets", /(?<snippet>\d+)/)
end end
# Returns a collection of snippets that are either public or visible to the
# logged in user.
#
# This method does not verify the user actually has the access to the project
# the snippet is in, so it should be only used on a relation that's already scoped
# for project access
def self.public_or_visible_to_user(user = nil)
if user
authorized = user
.project_authorizations
.select(1)
.where('project_authorizations.project_id = snippets.project_id')
levels = Gitlab::VisibilityLevel.levels_for_user(user)
where('EXISTS (?) OR snippets.visibility_level IN (?) or snippets.author_id = (?)', authorized, levels, user.id)
else
public_to_user
end
end
def to_reference(from = nil, full: false) def to_reference(from = nil, full: false)
reference = "#{self.class.reference_prefix}#{id}" reference = "#{self.class.reference_prefix}#{id}"
......
...@@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy ...@@ -119,7 +119,6 @@ class ProjectPolicy < BasePolicy
enable :create_note enable :create_note
enable :upload_file enable :upload_file
enable :read_cycle_analytics enable :read_cycle_analytics
enable :read_project_snippet
end end
rule { can?(:reporter_access) }.policy do rule { can?(:reporter_access) }.policy do
......
require 'spec_helper' require 'spec_helper'
describe SnippetsFinder do describe SnippetsFinder do
let(:user) { create :user } include Gitlab::Allowable
let(:user1) { create :user } using RSpec::Parameterized::TableSyntax
let(:group) { create :group, :public }
let(:project1) { create(:project, :public, group: group) }
let(:project2) { create(:project, :private, group: group) }
context 'all snippets visible to a user' do
let!(:snippet1) { create(:personal_snippet, :private) }
let!(:snippet2) { create(:personal_snippet, :internal) }
let!(:snippet3) { create(:personal_snippet, :public) }
let!(:project_snippet1) { create(:project_snippet, :private) }
let!(:project_snippet2) { create(:project_snippet, :internal) }
let!(:project_snippet3) { create(:project_snippet, :public) }
it "returns all private and internal snippets" do
snippets = described_class.new(user, scope: :all).execute
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
expect(snippets).not_to include(snippet1, project_snippet1)
end
it "returns all public snippets" do
snippets = described_class.new(nil, scope: :all).execute
expect(snippets).to include(snippet3, project_snippet3)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
it "returns all public and internal snippets for normal user" do
snippets = described_class.new(user).execute
expect(snippets).to include(snippet2, snippet3, project_snippet2, project_snippet3)
expect(snippets).not_to include(snippet1, project_snippet1)
end
it "returns all public snippets for non authorized user" do
snippets = described_class.new(nil).execute
expect(snippets).to include(snippet3, project_snippet3)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
it "returns all public and authored snippets for external user" do
external_user = create(:user, :external)
authored_snippet = create(:personal_snippet, :internal, author: external_user)
snippets = described_class.new(external_user).execute
expect(snippets).to include(snippet3, project_snippet3, authored_snippet)
expect(snippets).not_to include(snippet1, snippet2, project_snippet1, project_snippet2)
end
end
context 'filter by visibility' do context 'filter by visibility' do
let!(:snippet1) { create(:personal_snippet, :private) } let!(:snippet1) { create(:personal_snippet, :private) }
...@@ -67,6 +18,7 @@ describe SnippetsFinder do ...@@ -67,6 +18,7 @@ describe SnippetsFinder do
end end
context 'filter by scope' do context 'filter by scope' do
let(:user) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) }
...@@ -84,7 +36,7 @@ describe SnippetsFinder do ...@@ -84,7 +36,7 @@ describe SnippetsFinder do
expect(snippets).not_to include(snippet2, snippet3) expect(snippets).not_to include(snippet2, snippet3)
end end
it "returns all snippets for 'are_interna;' scope" do it "returns all snippets for 'are_internal' scope" do
snippets = described_class.new(user, scope: :are_internal).execute snippets = described_class.new(user, scope: :are_internal).execute
expect(snippets).to include(snippet2) expect(snippets).to include(snippet2)
...@@ -100,6 +52,8 @@ describe SnippetsFinder do ...@@ -100,6 +52,8 @@ describe SnippetsFinder do
end end
context 'filter by author' do context 'filter by author' do
let(:user) { create :user }
let(:user1) { create :user }
let!(:snippet1) { create(:personal_snippet, :private, author: user) } let!(:snippet1) { create(:personal_snippet, :private, author: user) }
let!(:snippet2) { create(:personal_snippet, :internal, author: user) } let!(:snippet2) { create(:personal_snippet, :internal, author: user) }
let!(:snippet3) { create(:personal_snippet, :public, author: user) } let!(:snippet3) { create(:personal_snippet, :public, author: user) }
...@@ -147,6 +101,10 @@ describe SnippetsFinder do ...@@ -147,6 +101,10 @@ describe SnippetsFinder do
end end
context 'filter by project' do context 'filter by project' do
let(:user) { create :user }
let(:group) { create :group, :public }
let(:project1) { create(:project, :public, group: group) }
before do before do
@snippet1 = create(:project_snippet, :private, project: project1) @snippet1 = create(:project_snippet, :private, project: project1)
@snippet2 = create(:project_snippet, :internal, project: project1) @snippet2 = create(:project_snippet, :internal, project: project1)
...@@ -203,4 +161,9 @@ describe SnippetsFinder do ...@@ -203,4 +161,9 @@ describe SnippetsFinder do
expect(snippets).to include(@snippet1) expect(snippets).to include(@snippet1)
end end
end end
describe "#execute" do
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
include_examples 'snippet visibility', described_class
end
end end
require 'spec_helper' require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe PersonalSnippetPolicy do describe PersonalSnippetPolicy do
let(:regular_user) { create(:user) } let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
......
require 'spec_helper' require 'spec_helper'
# Snippet visibility scenarios are included in more details in spec/support/snippet_visibility.rb
describe ProjectSnippetPolicy do describe ProjectSnippetPolicy do
let(:regular_user) { create(:user) } let(:regular_user) { create(:user) }
let(:external_user) { create(:user, :external) } let(:external_user) { create(:user, :external) }
......
...@@ -32,6 +32,27 @@ describe API::Snippets do ...@@ -32,6 +32,27 @@ describe API::Snippets do
expect(json_response).to be_an Array expect(json_response).to be_an Array
expect(json_response.size).to eq(0) expect(json_response.size).to eq(0)
end end
it 'returns 404 for non-authenticated' do
create(:personal_snippet, :internal)
get api("/snippets/")
expect(response).to have_gitlab_http_status(401)
end
it 'does not return snippets related to a project with disable feature visibility' do
project = create(:project)
create(:project_member, project: project, user: user)
public_snippet = create(:personal_snippet, :public, author: user, project: project)
project.project_feature.update_attribute(:snippets_access_level, 0)
get api("/snippets/", user)
json_response.each do |snippet|
expect(snippet["id"]).not_to eq(public_snippet.id)
end
end
end end
describe 'GET /snippets/public' do describe 'GET /snippets/public' do
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Search::SnippetService do describe Search::SnippetService do
let(:author) { create(:author) } let(:author) { create(:author) }
let(:project) { create(:project) } let(:project) { create(:project, :public) }
let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') } let!(:public_snippet) { create(:snippet, :public, content: 'password: XXX') }
let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') } let!(:internal_snippet) { create(:snippet, :internal, content: 'password: XXX') }
......
This diff is collapsed.
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment