Improve personal snippet access workflow. Fixes #3258
This commit is contained in:
parent
ead3ffd7a5
commit
c8fe421512
5 changed files with 188 additions and 17 deletions
|
@ -14,6 +14,7 @@ v 8.2.0 (unreleased)
|
||||||
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
|
- Fix: 500 error returned if destroy request without HTTP referer (Kazuki Shimizu)
|
||||||
- Remove deprecated CI events from project settings page
|
- Remove deprecated CI events from project settings page
|
||||||
- Use issue editor as cross reference comment author when issue is edited with a new mention.
|
- 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
|
v 8.1.1
|
||||||
- Fix cloning Wiki repositories via HTTP (Stan Hu)
|
- Fix cloning Wiki repositories via HTTP (Stan Hu)
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
class SnippetsController < ApplicationController
|
class SnippetsController < ApplicationController
|
||||||
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
|
before_action :snippet, only: [:show, :edit, :destroy, :update, :raw]
|
||||||
|
|
||||||
|
# Allow read snippet
|
||||||
|
before_action :authorize_show_snippet!, only: [:show]
|
||||||
|
|
||||||
# Allow modify snippet
|
# Allow modify snippet
|
||||||
before_action :authorize_update_snippet!, only: [:edit, :update]
|
before_action :authorize_update_snippet!, only: [:edit, :update]
|
||||||
|
|
||||||
|
@ -79,10 +82,14 @@ class SnippetsController < ApplicationController
|
||||||
[Snippet::PUBLIC, Snippet::INTERNAL]).
|
[Snippet::PUBLIC, Snippet::INTERNAL]).
|
||||||
find(params[:id])
|
find(params[:id])
|
||||||
else
|
else
|
||||||
PersonalSnippet.are_public.find(params[:id])
|
PersonalSnippet.find(params[:id])
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def authorize_show_snippet!
|
||||||
|
authenticate_user! unless can?(current_user, :read_personal_snippet, @snippet)
|
||||||
|
end
|
||||||
|
|
||||||
def authorize_update_snippet!
|
def authorize_update_snippet!
|
||||||
return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
|
return render_404 unless can?(current_user, :update_personal_snippet, @snippet)
|
||||||
end
|
end
|
||||||
|
|
|
@ -22,12 +22,17 @@ class Ability
|
||||||
# List of possible abilities
|
# List of possible abilities
|
||||||
# for non-authenticated user
|
# for non-authenticated user
|
||||||
def not_auth_abilities(user, subject)
|
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)
|
project = if subject.kind_of?(Project)
|
||||||
subject
|
subject
|
||||||
elsif subject.respond_to?(:project)
|
|
||||||
subject.project
|
|
||||||
else
|
else
|
||||||
nil
|
subject.project
|
||||||
end
|
end
|
||||||
|
|
||||||
if project && project.public?
|
if project && project.public?
|
||||||
|
@ -47,12 +52,15 @@ class Ability
|
||||||
|
|
||||||
rules - project_disabled_features_rules(project)
|
rules - project_disabled_features_rules(project)
|
||||||
else
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def not_auth_group_abilities(subject)
|
||||||
group = if subject.kind_of?(Group)
|
group = if subject.kind_of?(Group)
|
||||||
subject
|
subject
|
||||||
elsif subject.respond_to?(:group)
|
|
||||||
subject.group
|
|
||||||
else
|
else
|
||||||
nil
|
subject.group
|
||||||
end
|
end
|
||||||
|
|
||||||
if group && group.public_profile?
|
if group && group.public_profile?
|
||||||
|
@ -61,6 +69,13 @@ class Ability
|
||||||
[]
|
[]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def not_auth_personal_snippet_abilities(snippet)
|
||||||
|
if snippet.public?
|
||||||
|
[:read_personal_snippet]
|
||||||
|
else
|
||||||
|
[]
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def global_abilities(user)
|
def global_abilities(user)
|
||||||
|
@ -278,7 +293,7 @@ class Ability
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
[:note, :project_snippet, :personal_snippet].each do |name|
|
[:note, :project_snippet].each do |name|
|
||||||
define_method "#{name}_abilities" do |user, subject|
|
define_method "#{name}_abilities" do |user, subject|
|
||||||
rules = []
|
rules = []
|
||||||
|
|
||||||
|
@ -298,6 +313,24 @@ class Ability
|
||||||
end
|
end
|
||||||
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)
|
def group_member_abilities(user, subject)
|
||||||
rules = []
|
rules = []
|
||||||
target_user = subject.user
|
target_user = subject.user
|
||||||
|
|
118
spec/controllers/snippets_controller_spec.rb
Normal file
118
spec/controllers/snippets_controller_spec.rb
Normal file
|
@ -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
|
|
@ -165,6 +165,18 @@ FactoryGirl.define do
|
||||||
title
|
title
|
||||||
content
|
content
|
||||||
file_name
|
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
|
end
|
||||||
|
|
||||||
factory :snippet do
|
factory :snippet do
|
||||||
|
|
Loading…
Reference in a new issue