From 111ebe54712aca16f3ad0f3d859c1c036ccb9f09 Mon Sep 17 00:00:00 2001
From: Valery Sizov <vsv2711@gmail.com>
Date: Mon, 6 Jul 2015 15:38:43 +0300
Subject: [PATCH] Fork visibility level fix

---
 CHANGELOG                                     |  1 +
 app/helpers/projects_helper.rb                | 10 +++++
 app/helpers/visibility_level_helper.rb        |  6 +++
 app/views/projects/edit.html.haml             |  2 +-
 app/views/shared/_visibility_radios.html.haml |  1 +
 lib/gitlab/visibility_level.rb                |  4 ++
 spec/helpers/projects_helper_spec.rb          | 44 +++++++++++++++++++
 spec/helpers/visibility_level_helper_spec.rb  | 39 ++++++++++++++++
 8 files changed, 106 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG b/CHANGELOG
index d538bb42992..c48a69bff5f 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -37,6 +37,7 @@ v 7.13.0 (unreleased)
   - Correctly show anonymous authorized applications under Profile > Applications.
   - Query Optimization in MySQL.
   - Allow users to be blocked and unblocked via the API
+  - A fork can’t have a visibility level that is greater than the original project.
 
 v 7.12.2
   - Correctly show anonymous authorized applications under Profile > Applications.
diff --git a/app/helpers/projects_helper.rb b/app/helpers/projects_helper.rb
index ec65e473919..d18f3e91614 100644
--- a/app/helpers/projects_helper.rb
+++ b/app/helpers/projects_helper.rb
@@ -139,6 +139,16 @@ module ProjectsHelper
     end
   end
 
+  def can_change_visibility_level?(project, current_user)
+    return false unless can?(current_user, :change_visibility_level, project)
+
+    if project.forked?
+      project.forked_from_project.visibility_level > Gitlab::VisibilityLevel::PRIVATE
+    else
+      true
+    end
+  end
+
   private
 
   def get_project_nav_tabs(project, current_user)
diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb
index 00d4c7f1051..b52cd23aba2 100644
--- a/app/helpers/visibility_level_helper.rb
+++ b/app/helpers/visibility_level_helper.rb
@@ -86,4 +86,10 @@ module VisibilityLevelHelper
   def default_snippet_visibility
     current_application_settings.default_snippet_visibility
   end
+
+  def skip_level?(form_model, level)
+    form_model.is_a?(Project) &&
+    form_model.forked? &&
+    !Gitlab::VisibilityLevel.allowed_fork_levels(form_model.forked_from_project.visibility_level).include?(level)
+  end
 end
diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml
index 3fecd25c324..99078e43937 100644
--- a/app/views/projects/edit.html.haml
+++ b/app/views/projects/edit.html.haml
@@ -29,7 +29,7 @@
                 .col-sm-10= f.select(:default_branch, @repository.branch_names, {}, {class: 'select2 select-wide'})
 
 
-          = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project), form_model: @project
+          = render 'shared/visibility_level', f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can_change_visibility_level?(@project, current_user), form_model: @project
 
           .form-group
             = f.label :tag_list, "Tags", class: 'control-label'
diff --git a/app/views/shared/_visibility_radios.html.haml b/app/views/shared/_visibility_radios.html.haml
index 02416125a72..ebe2eb0433d 100644
--- a/app/views/shared/_visibility_radios.html.haml
+++ b/app/views/shared/_visibility_radios.html.haml
@@ -1,4 +1,5 @@
 - Gitlab::VisibilityLevel.values.each do |level|
+  - next if skip_level?(form_model, level)
   .radio
     - restricted = restricted_visibility_levels.include?(level)
     = form.label "#{model_method}_#{level}" do
diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb
index 582fc759efd..335dc44be19 100644
--- a/lib/gitlab/visibility_level.rb
+++ b/lib/gitlab/visibility_level.rb
@@ -47,6 +47,10 @@ module Gitlab
       def valid_level?(level)
         options.has_value?(level)
       end
+
+      def allowed_fork_levels(origin_level)
+        [PRIVATE, INTERNAL, PUBLIC].select{ |level| level <= origin_level }
+      end
     end
 
     def private?
diff --git a/spec/helpers/projects_helper_spec.rb b/spec/helpers/projects_helper_spec.rb
index 0f78725e3d9..beb9b4e438e 100644
--- a/spec/helpers/projects_helper_spec.rb
+++ b/spec/helpers/projects_helper_spec.rb
@@ -8,4 +8,48 @@ describe ProjectsHelper do
       expect(project_status_css_class("finished")).to eq("success")
     end
   end
+
+  describe "can_change_visibility_level?" do
+    let(:project) { create(:project) }
+
+    let(:fork_project) do
+      fork_project = create(:forked_project_with_submodules)
+      fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+      fork_project.save
+
+      fork_project
+    end
+
+    let(:user) { create(:user) }
+
+    it "returns false if there are no approipriate permissions" do
+      allow(helper).to receive(:can?) { false }
+
+      expect(helper.can_change_visibility_level?(project, user)).to be_falsey
+    end
+
+    it "returns true if there are permissions and it is not fork" do
+      allow(helper).to receive(:can?) { true }
+
+      expect(helper.can_change_visibility_level?(project, user)).to be_truthy
+    end
+
+    context "forks" do
+      it "returns false if there are permissions and origin project is PRIVATE" do
+        allow(helper).to receive(:can?) { true }
+
+        project.update visibility_level:  Gitlab::VisibilityLevel::PRIVATE
+
+        expect(helper.can_change_visibility_level?(fork_project, user)).to be_falsey
+      end
+
+      it "returns true if there are permissions and origin project is INTERNAL" do
+        allow(helper).to receive(:can?) { true }
+
+        project.update visibility_level:  Gitlab::VisibilityLevel::INTERNAL
+
+        expect(helper.can_change_visibility_level?(fork_project, user)).to be_truthy
+      end
+    end
+  end
 end
diff --git a/spec/helpers/visibility_level_helper_spec.rb b/spec/helpers/visibility_level_helper_spec.rb
index 3840e64981f..c4f7693329c 100644
--- a/spec/helpers/visibility_level_helper_spec.rb
+++ b/spec/helpers/visibility_level_helper_spec.rb
@@ -72,4 +72,43 @@ describe VisibilityLevelHelper do
       end
     end
   end
+
+  describe "skip_level?" do
+    describe "forks" do
+      let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+      let(:fork_project) { create(:forked_project_with_submodules) }
+
+      before do
+        fork_project.build_forked_project_link(forked_to_project_id: fork_project.id, forked_from_project_id: project.id)
+        fork_project.save
+      end
+
+      it "skips levels" do
+        expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PUBLIC)).to be_truthy
+        expect(skip_level?(fork_project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+        expect(skip_level?(fork_project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+      end
+    end
+
+    describe "non-forked project" do
+      let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+      it "skips levels" do
+        expect(skip_level?(project, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+        expect(skip_level?(project, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+        expect(skip_level?(project, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+      end
+    end
+
+    describe "Snippet" do
+      let(:snippet) { create(:snippet, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
+
+      it "skips levels" do
+        expect(skip_level?(snippet, Gitlab::VisibilityLevel::PUBLIC)).to be_falsey
+        expect(skip_level?(snippet, Gitlab::VisibilityLevel::INTERNAL)).to be_falsey
+        expect(skip_level?(snippet, Gitlab::VisibilityLevel::PRIVATE)).to be_falsey
+      end
+    end
+
+  end
 end
-- 
GitLab