From 4c44c5ef9a569c65bca8e70078205ef2ce7d6396 Mon Sep 17 00:00:00 2001
From: Stephen Lottermoser <slottermoser@apple.com>
Date: Wed, 1 May 2013 23:52:05 -0700
Subject: [PATCH] Internally public projects

Public projects listed in the public section will be linked to the
actual project's page. Public projects now give any user Guest
permissions to the project, allowing them to download the code, read
and create issues, and view anything else in the project's pages.

Ample access tests have been added to the project_access_spec to
verify correct permissions and behavior on public projects.
- Visitors to the site who are not logged in still cannot view the
  project's pages.
- Logged-in users visiting a public project where they are not a team
  member can create issues, but not snippets. They can view the projects
  code, issues, merge requests, etc, just as if they were a Guest member
  of the project.
- Since this is a public project, the user is also granted :download_code
  permissions, a permission normally reserved for Reporters, since they
  can clone the repo anyways and browse commits and branches locally.
---
 app/controllers/application_controller.rb     |   2 +-
 app/models/ability.rb                         |   2 +-
 app/views/projects/_form.html.haml            |   4 +-
 app/views/public/projects/index.html.haml     |   2 +-
 features/project/public_projects.feature      |   8 +
 features/steps/project/public_projects.rb     |   9 +
 features/steps/shared/paths.rb                |   8 +
 spec/features/security/project_access_spec.rb | 242 ++++++++++++++++++
 8 files changed, 273 insertions(+), 4 deletions(-)
 create mode 100644 features/project/public_projects.feature
 create mode 100644 features/steps/project/public_projects.rb

diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
index 32b1246601de7..c2ca23fae53d2 100644
--- a/app/controllers/application_controller.rb
+++ b/app/controllers/application_controller.rb
@@ -88,7 +88,7 @@ def authorize_project!(action)
   end
 
   def authorize_code_access!
-    return access_denied! unless can?(current_user, :download_code, project)
+    return access_denied! unless can?(current_user, :download_code, project) or project.public?
   end
 
   def authorize_create_team!
diff --git a/app/models/ability.rb b/app/models/ability.rb
index 5b49104da8a8f..c5e4524b8ceb7 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -37,7 +37,7 @@ def project_abilities(user, project)
       elsif team.reporters.include?(user)
         rules << project_report_rules
 
-      elsif team.guests.include?(user)
+      elsif team.guests.include?(user) or project.public?
         rules << project_guest_rules
       end
 
diff --git a/app/views/projects/_form.html.haml b/app/views/projects/_form.html.haml
index 4d635e3dc68b9..0e1fd2380053e 100644
--- a/app/views/projects/_form.html.haml
+++ b/app/views/projects/_form.html.haml
@@ -48,7 +48,7 @@
                     Public mode:
                   .control-group
                     = f.label :public, class: 'control-label' do
-                      %span Public clone access
+                      %span Public access
                     .controls
                       = f.check_box :public
                       %span.descr
@@ -56,6 +56,8 @@
                         %em without any
                         authentication.
                         It will also be listed on the #{link_to "public access directory", public_root_path}.
+                        %em Any
+                        user will have #{link_to "Guest", help_permissions_path} permissions on the repository.
 
               %fieldset.features
                 %legend
diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml
index 3d0d793b2d2ed..e66851ead5b3e 100644
--- a/app/views/public/projects/index.html.haml
+++ b/app/views/public/projects/index.html.haml
@@ -9,7 +9,7 @@
       %li.clearfix
         %h5
           %i.icon-share
-          = project.name_with_namespace
+          = link_to_project project
           .pull-right
             %pre.dark.tiny git clone #{project.http_url_to_repo}
         %p.description
diff --git a/features/project/public_projects.feature b/features/project/public_projects.feature
new file mode 100644
index 0000000000000..c5a9da14c5416
--- /dev/null
+++ b/features/project/public_projects.feature
@@ -0,0 +1,8 @@
+Feature: Public Projects
+  Background:
+    Given I sign in as a user
+
+  Scenario: I should see the list of public projects
+    When I visit the public projects area
+    Then I should see the list of public projects
+
diff --git a/features/steps/project/public_projects.rb b/features/steps/project/public_projects.rb
new file mode 100644
index 0000000000000..7063e7d56aefc
--- /dev/null
+++ b/features/steps/project/public_projects.rb
@@ -0,0 +1,9 @@
+class PublicProjects < Spinach::FeatureSteps
+  include SharedAuthentication
+  include SharedProject
+  include SharedPaths
+
+  Then 'I should see the list of public projects' do
+    page.should have_content "Public Projects"
+  end
+end
diff --git a/features/steps/shared/paths.rb b/features/steps/shared/paths.rb
index 27ca65b22dd66..38730cc2cd61a 100644
--- a/features/steps/shared/paths.rb
+++ b/features/steps/shared/paths.rb
@@ -263,6 +263,14 @@ module SharedPaths
     visit project_wiki_path(@project, :home)
   end
 
+  # ----------------------------------------
+  # Public Projects
+  # ----------------------------------------
+
+  Given 'I visit the public projects area' do
+    visit public_root_path
+  end
+
   def root_ref
     @project.repository.root_ref
   end
diff --git a/spec/features/security/project_access_spec.rb b/spec/features/security/project_access_spec.rb
index cfbb8f135ab0c..a00b2b0375ab7 100644
--- a/spec/features/security/project_access_spec.rb
+++ b/spec/features/security/project_access_spec.rb
@@ -229,4 +229,246 @@
       it { should be_denied_for :visitor }
     end
   end
+
+
+  describe "PublicProject" do
+    let(:project)  { create(:project) }
+
+    let(:master)   { create(:user) }
+    let(:guest)    { create(:user) }
+    let(:reporter) { create(:user) }
+
+    let(:admin)    { create(:user) }
+
+    before do
+      # public project
+      project.public = true
+      project.save!
+
+      # full access
+      project.team << [master, :master]
+
+      # readonly
+      project.team << [reporter, :reporter]
+
+    end
+
+    describe "Project should be public" do
+      subject { project }
+
+      its(:public?) { should be_true }
+    end
+
+    describe "GET /project_code" do
+      subject { project_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/tree/master" do
+      subject { project_tree_path(project, project.repository.root_ref) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/commits/master" do
+      subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/commit/:sha" do
+      subject { project_commit_path(project, project.repository.commit) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/compare" do
+      subject { project_compare_index_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/team" do
+      subject { project_team_index_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/wall" do
+      subject { project_wall_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/blob" do
+      before do
+        commit = project.repository.commit
+        path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob)}.first.name
+        @blob_path = project_blob_path(project, File.join(commit.id, path))
+      end
+
+      it { @blob_path.should be_allowed_for master }
+      it { @blob_path.should be_allowed_for reporter }
+      it { @blob_path.should be_allowed_for :admin }
+      it { @blob_path.should be_allowed_for guest }
+      it { @blob_path.should be_allowed_for :user }
+      it { @blob_path.should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/edit" do
+      subject { edit_project_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_denied_for reporter }
+      it { should be_denied_for :admin }
+      it { should be_denied_for guest }
+      it { should be_denied_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/deploy_keys" do
+      subject { project_deploy_keys_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_denied_for reporter }
+      it { should be_denied_for :admin }
+      it { should be_denied_for guest }
+      it { should be_denied_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/issues" do
+      subject { project_issues_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/snippets" do
+      subject { project_snippets_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/snippets/new" do
+      subject { new_project_snippet_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_denied_for :admin }
+      it { should be_denied_for guest }
+      it { should be_denied_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/merge_requests" do
+      subject { project_merge_requests_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/repository" do
+      subject { project_repository_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/repository/branches" do
+      subject { branches_project_repository_path(project) }
+
+      before do
+        # Speed increase
+        Project.any_instance.stub(:branches).and_return([])
+      end
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/repository/tags" do
+      subject { tags_project_repository_path(project) }
+
+      before do
+        # Speed increase
+        Project.any_instance.stub(:tags).and_return([])
+      end
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+
+    describe "GET /project_code/hooks" do
+      subject { project_hooks_path(project) }
+
+      it { should be_allowed_for master }
+      it { should be_allowed_for reporter }
+      it { should be_allowed_for :admin }
+      it { should be_allowed_for guest }
+      it { should be_allowed_for :user }
+      it { should be_denied_for :visitor }
+    end
+  end
 end
-- 
GitLab