diff --git a/CHANGELOG b/CHANGELOG index 0d89fca9fc1283104d07915e0726f1f7e1047da4..325a073b5c586c255c186811cc5d947bc17eb8af 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -14,6 +14,7 @@ v 8.2.0 (unreleased) - Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu) - Remove deprecated CI events from project settings page - Use issue editor as cross reference comment author when issue is edited with a new mention. + - Improve personal snippet access workflow v 8.1.1 - Fix cloning Wiki repositories via HTTP (Stan Hu) diff --git a/app/controllers/snippets_controller.rb b/app/controllers/snippets_controller.rb index 9f9f9a92f1150439032f213787824428e8f07121..8498efc89d0e2749b279553ffc11b4ed8497fc42 100644 --- a/app/controllers/snippets_controller.rb +++ b/app/controllers/snippets_controller.rb @@ -1,6 +1,9 @@ class SnippetsController < ApplicationController before_action :snippet, only: [:show, :edit, :destroy, :update, :raw] + # Allow read snippet + before_action :authorize_show_snippet!, only: [:show] + # Allow modify snippet before_action :authorize_update_snippet!, only: [:edit, :update] @@ -79,10 +82,14 @@ class SnippetsController < ApplicationController [Snippet::PUBLIC, Snippet::INTERNAL]). find(params[:id]) else - PersonalSnippet.are_public.find(params[:id]) + PersonalSnippet.find(params[:id]) end end + def authorize_show_snippet! + authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet) + end + def authorize_update_snippet! return render_404 unless can?(current_user, :update_personal_snippet, @snippet) end diff --git a/app/models/ability.rb b/app/models/ability.rb index b72178fa12608f84058cb356ee71d7c798e8ebc4..ee2f7b5f58bf3ebfea4fe7f92d66e75fc0799e7f 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -22,12 +22,17 @@ class Ability # List of possible abilities # for non-authenticated user def not_auth_abilities(user, subject) + return not_auth_personal_snippet_abilities(subject) if subject.kind_of?(PersonalSnippet) + return not_auth_project_abilities(subject) if subject.kind_of?(Project) || subject.respond_to?(:project) + return not_auth_group_abilities(subject) if subject.kind_of?(Group) || subject.respond_to?(:group) + [] + end + + def not_auth_project_abilities(subject) project = if subject.kind_of?(Project) subject - elsif subject.respond_to?(:project) - subject.project else - nil + subject.project end if project && project.public? @@ -47,19 +52,29 @@ class Ability rules - project_disabled_features_rules(project) else - group = if subject.kind_of?(Group) - subject - elsif subject.respond_to?(:group) - subject.group - else - nil - end + [] + end + end - if group && group.public_profile? - [:read_group] - else - [] - end + def not_auth_group_abilities(subject) + group = if subject.kind_of?(Group) + subject + else + subject.group + end + + if group && group.public_profile? + [:read_group] + else + [] + end + end + + def not_auth_personal_snippet_abilities(snippet) + if snippet.public? + [:read_personal_snippet] + else + [] end end @@ -278,7 +293,7 @@ class Ability end end - [:note, :project_snippet, :personal_snippet].each do |name| + [:note, :project_snippet].each do |name| define_method "#{name}_abilities" do |user, subject| rules = [] @@ -298,6 +313,24 @@ class Ability end end + def personal_snippet_abilities(user, snippet) + rules = [] + + if snippet.author == user + rules += [ + :read_personal_snippet, + :update_personal_snippet, + :admin_personal_snippet + ] + end + + if snippet.public? || snippet.internal? + rules.push(:read_snippet) + end + + rules + end + def group_member_abilities(user, subject) rules = [] target_user = subject.user diff --git a/spec/controllers/snippets_controller_spec.rb b/spec/controllers/snippets_controller_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..e9b823c523c0b5038843a0e9489a4f64e41f3907 --- /dev/null +++ b/spec/controllers/snippets_controller_spec.rb @@ -0,0 +1,118 @@ +require 'spec_helper' + +describe SnippetsController do + describe 'GET #show' do + let(:user) { create(:user) } + + context 'when the personal snippet is private' do + let(:personal_snippet) { create(:personal_snippet, :private, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + context 'when signed in user is not the author' do + let(:other_author) { create(:author) } + let(:other_personal_snippet) { create(:personal_snippet, :private, author: other_author) } + + it 'responds with status 404' do + get :show, id: other_personal_snippet.to_param + + expect(response.status).to eq(404) + end + end + + context 'when signed in user is the author' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is internal' do + let(:personal_snippet) { create(:personal_snippet, :internal, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'redirects to the sign in page' do + get :show, id: personal_snippet.to_param + + expect(response).to redirect_to(new_user_session_path) + end + end + end + + context 'when the personal snippet is public' do + let(:personal_snippet) { create(:personal_snippet, :public, author: user) } + + context 'when signed in' do + before do + sign_in(user) + end + + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + + context 'when not signed in' do + it 'renders the snippet' do + get :show, id: personal_snippet.to_param + + expect(assigns(:snippet)).to eq(personal_snippet) + expect(response.status).to eq(200) + end + end + end + + context 'when the personal snippet does not exist' do + context 'when signed in' do + before do + sign_in(user) + end + + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + + context 'when not signed in' do + it 'responds with status 404' do + get :show, id: 'doesntexist' + + expect(response.status).to eq(404) + end + end + end + end +end diff --git a/spec/factories.rb b/spec/factories.rb index 200f18f660d52b4b32e36c7fadff1f3702c66051..4bf93adabe27c31e5977daa3eb85726ce88e2a17 100644 --- a/spec/factories.rb +++ b/spec/factories.rb @@ -165,6 +165,18 @@ FactoryGirl.define do title content file_name + + trait :public do + visibility_level Gitlab::VisibilityLevel::PUBLIC + end + + trait :internal do + visibility_level Gitlab::VisibilityLevel::INTERNAL + end + + trait :private do + visibility_level Gitlab::VisibilityLevel::PRIVATE + end end factory :snippet do