Commit fa7d62494a24584c0d27d52344aae8e382304936

Authored by Dmitriy Zaporozhets
2 parents 9aaf478f d9bb4230

Merge branch 'authenticated_public_mode' of https://github.com/jhollingsworth/gi…

…tlabhq into feature/internal_projects

Conflicts:
	app/models/project.rb

Signed-off-by: Dmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
Showing 50 changed files with 955 additions and 156 deletions   Show diff stats
app/assets/stylesheets/common.scss
@@ -365,6 +365,10 @@ table { @@ -365,6 +365,10 @@ table {
365 &.input-large { 365 &.input-large {
366 width: 210px; 366 width: 210px;
367 } 367 }
  368 +
  369 + &.input-clamp {
  370 + max-width: 100%;
  371 + }
368 } 372 }
369 373
370 .user-result { 374 .user-result {
app/assets/stylesheets/gitlab_bootstrap/common.scss
@@ -6,6 +6,7 @@ @@ -6,6 +6,7 @@
6 .cblue { color: #29A } 6 .cblue { color: #29A }
7 .cblack { color: #111 } 7 .cblack { color: #111 }
8 .cdark { color: #444 } 8 .cdark { color: #444 }
  9 +.camber { color: #ffc000 }
9 .cwhite { color: #fff!important } 10 .cwhite { color: #fff!important }
10 .bgred { background: #F2DEDE!important } 11 .bgred { background: #F2DEDE!important }
11 12
app/assets/stylesheets/sections/admin.scss
@@ -20,6 +20,15 @@ @@ -20,6 +20,15 @@
20 label { width: 110px; } 20 label { width: 110px; }
21 .controls { margin-left: 130px; } 21 .controls { margin-left: 130px; }
22 .form-actions { padding-left: 130px; background: #fff } 22 .form-actions { padding-left: 130px; background: #fff }
  23 + .visibility-levels {
  24 + .controls {
  25 + margin-bottom: 9px;
  26 + }
  27 +
  28 + i {
  29 + color: inherit;
  30 + }
  31 + }
23 } 32 }
24 33
25 .broadcast-messages { 34 .broadcast-messages {
app/assets/stylesheets/sections/projects.scss
@@ -18,6 +18,12 @@ @@ -18,6 +18,12 @@
18 border-bottom: 1px solid #DDD; 18 border-bottom: 1px solid #DDD;
19 padding-bottom: 25px; 19 padding-bottom: 25px;
20 margin-bottom: 30px; 20 margin-bottom: 30px;
  21 +
  22 + &.empty-project {
  23 + border-bottom: 0px;
  24 + padding-bottom: 15px;
  25 + margin-bottom: 0px;
  26 + }
21 27
22 .project-home-title { 28 .project-home-title {
23 font-size: 18px; 29 font-size: 18px;
@@ -45,7 +51,7 @@ @@ -45,7 +51,7 @@
45 } 51 }
46 } 52 }
47 53
48 - .public-label { 54 + .visibility-level-label {
49 font-size: 14px; 55 font-size: 14px;
50 background: #f1f1f1; 56 background: #f1f1f1;
51 padding: 8px 10px; 57 padding: 8px 10px;
@@ -53,6 +59,10 @@ @@ -53,6 +59,10 @@
53 margin-left: 10px; 59 margin-left: 10px;
54 color: #888; 60 color: #888;
55 text-shadow: 0 1px 1px #FFF; 61 text-shadow: 0 1px 1px #FFF;
  62 +
  63 + i {
  64 + color: inherit;
  65 + }
56 } 66 }
57 } 67 }
58 68
@@ -87,9 +97,33 @@ @@ -87,9 +97,33 @@
87 } 97 }
88 } 98 }
89 99
90 -.project-public-holder {  
91 - .help-inline {  
92 - padding-top: 7px; 100 +.project-visibility-level-holder {
  101 + .controls {
  102 + padding-bottom: 9px;
  103 + }
  104 +
  105 + .controls {
  106 + input {
  107 + float: left;
  108 + }
  109 + .descr {
  110 + display: block;
  111 + margin-left: 1.5em;
  112 + &.restricted {
  113 + color: #888;
  114 + }
  115 + }
  116 + .info {
  117 + display: block;
  118 + margin-top: 5px;
  119 + }
  120 + strong {
  121 + display: inline-block;
  122 + width: 4em;
  123 + }
  124 + }
  125 + i {
  126 + color: inherit;
93 } 127 }
94 } 128 }
95 129
@@ -130,7 +164,8 @@ ul.nav.nav-projects-tabs { @@ -130,7 +164,8 @@ ul.nav.nav-projects-tabs {
130 margin: 0px; 164 margin: 0px;
131 } 165 }
132 166
133 -.my-projects { 167 +.my-projects,
  168 +.public-projects {
134 li { 169 li {
135 .project-info { 170 .project-info {
136 margin-bottom: 10px; 171 margin-bottom: 10px;
app/contexts/projects/create_context.rb
@@ -8,6 +8,11 @@ module Projects @@ -8,6 +8,11 @@ module Projects
8 # get namespace id 8 # get namespace id
9 namespace_id = params.delete(:namespace_id) 9 namespace_id = params.delete(:namespace_id)
10 10
  11 + # check that user is allowed to set specified visibility_level
  12 + unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level])
  13 + params.delete(:visibility_level)
  14 + end
  15 +
11 # Load default feature settings 16 # Load default feature settings
12 default_features = Gitlab.config.gitlab.default_projects_features 17 default_features = Gitlab.config.gitlab.default_projects_features
13 18
@@ -17,7 +22,7 @@ module Projects @@ -17,7 +22,7 @@ module Projects
17 wall_enabled: default_features.wall, 22 wall_enabled: default_features.wall,
18 snippets_enabled: default_features.snippets, 23 snippets_enabled: default_features.snippets,
19 merge_requests_enabled: default_features.merge_requests, 24 merge_requests_enabled: default_features.merge_requests,
20 - public: default_features.public 25 + visibility_level: default_features.visibility_level
21 }.stringify_keys 26 }.stringify_keys
22 27
23 @project = Project.new(default_opts.merge(params)) 28 @project = Project.new(default_opts.merge(params))
app/contexts/projects/update_context.rb
@@ -2,7 +2,11 @@ module Projects @@ -2,7 +2,11 @@ module Projects
2 class UpdateContext < BaseContext 2 class UpdateContext < BaseContext
3 def execute(role = :default) 3 def execute(role = :default)
4 params[:project].delete(:namespace_id) 4 params[:project].delete(:namespace_id)
5 - params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) 5 + # check that user is allowed to set specified visibility_level
  6 + unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level])
  7 + params[:project].delete(:visibility_level)
  8 + end
  9 +
6 new_branch = params[:project].delete(:default_branch) 10 new_branch = params[:project].delete(:default_branch)
7 11
8 if project.repository.exists? && new_branch != project.default_branch 12 if project.repository.exists? && new_branch != project.default_branch
app/contexts/search_context.rb
1 class SearchContext 1 class SearchContext
2 - attr_accessor :project_ids, :params 2 + attr_accessor :project_ids, :current_user, :params
3 3
4 - def initialize(project_ids, params)  
5 - @project_ids, @params = project_ids, params.dup 4 + def initialize(project_ids, user, params)
  5 + @project_ids, @current_user, @params = project_ids, user, params.dup
6 end 6 end
7 7
8 def execute 8 def execute
@@ -10,7 +10,8 @@ class SearchContext @@ -10,7 +10,8 @@ class SearchContext
10 query = Shellwords.shellescape(query) if query.present? 10 query = Shellwords.shellescape(query) if query.present?
11 11
12 return result unless query.present? 12 return result unless query.present?
13 - result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20) 13 + visibility_levels = @current_user ? [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] : [ Gitlab::VisibilityLevel::PUBLIC ]
  14 + result[:projects] = Project.where("projects.id in (?) OR projects.visibility_level in (?)", project_ids, visibility_levels).search(query).limit(20)
14 15
15 # Search inside single project 16 # Search inside single project
16 single_project_search(Project.where(id: project_ids), query) 17 single_project_search(Project.where(id: project_ids), query)
app/controllers/admin/projects_controller.rb
@@ -8,7 +8,7 @@ class Admin::ProjectsController &lt; Admin::ApplicationController @@ -8,7 +8,7 @@ class Admin::ProjectsController &lt; Admin::ApplicationController
8 user = User.find_by_id(owner_id) 8 user = User.find_by_id(owner_id)
9 9
10 @projects = user ? user.owned_projects : Project.scoped 10 @projects = user ? user.owned_projects : Project.scoped
11 - @projects = @projects.where(public: true) if params[:public_only].present? 11 + @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present?
12 @projects = @projects.with_push if params[:with_push].present? 12 @projects = @projects.with_push if params[:with_push].present?
13 @projects = @projects.abandoned if params[:abandoned].present? 13 @projects = @projects.abandoned if params[:abandoned].present?
14 @projects = @projects.search(params[:name]) if params[:name].present? 14 @projects = @projects.search(params[:name]) if params[:name].present?
app/controllers/application_controller.rb
@@ -102,7 +102,7 @@ class ApplicationController &lt; ActionController::Base @@ -102,7 +102,7 @@ class ApplicationController &lt; ActionController::Base
102 end 102 end
103 103
104 def authorize_code_access! 104 def authorize_code_access!
105 - return access_denied! unless can?(current_user, :download_code, project) or project.public? 105 + return access_denied! unless can?(current_user, :download_code, project)
106 end 106 end
107 107
108 def authorize_push! 108 def authorize_push!
app/controllers/projects/application_controller.rb
@@ -10,7 +10,7 @@ class Projects::ApplicationController &lt; ApplicationController @@ -10,7 +10,7 @@ class Projects::ApplicationController &lt; ApplicationController
10 id = params[:project_id] || params[:id] 10 id = params[:project_id] || params[:id]
11 @project = Project.find_with_namespace(id) 11 @project = Project.find_with_namespace(id)
12 12
13 - return if @project && @project.public 13 + return if @project && @project.public?
14 end 14 end
15 15
16 super 16 super
app/controllers/projects_controller.rb
@@ -55,7 +55,7 @@ class ProjectsController &lt; ApplicationController @@ -55,7 +55,7 @@ class ProjectsController &lt; ApplicationController
55 end 55 end
56 56
57 def show 57 def show
58 - return authenticate_user! unless @project.public || current_user 58 + return authenticate_user! unless @project.public? || current_user
59 59
60 limit = (params[:limit] || 20).to_i 60 limit = (params[:limit] || 20).to_i
61 @events = @project.events.recent 61 @events = @project.events.recent
app/controllers/public/projects_controller.rb
@@ -6,7 +6,7 @@ class Public::ProjectsController &lt; ApplicationController @@ -6,7 +6,7 @@ class Public::ProjectsController &lt; ApplicationController
6 layout 'public' 6 layout 'public'
7 7
8 def index 8 def index
9 - @projects = Project.public_only 9 + @projects = Project.public_or_internal_only(current_user)
10 @projects = @projects.search(params[:search]) if params[:search].present? 10 @projects = @projects.search(params[:search]) if params[:search].present?
11 @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) 11 @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
12 end 12 end
app/controllers/search_controller.rb
@@ -14,7 +14,7 @@ class SearchController &lt; ApplicationController @@ -14,7 +14,7 @@ class SearchController &lt; ApplicationController
14 project_ids.select! { |id| id == project_id.to_i} 14 project_ids.select! { |id| id == project_id.to_i}
15 end 15 end
16 16
17 - result = SearchContext.new(project_ids, params).execute 17 + result = SearchContext.new(project_ids, current_user, params).execute
18 18
19 @projects = result[:projects] 19 @projects = result[:projects]
20 @merge_requests = result[:merge_requests] 20 @merge_requests = result[:merge_requests]
app/helpers/icons_helper.rb
@@ -11,6 +11,10 @@ module IconsHelper @@ -11,6 +11,10 @@ module IconsHelper
11 content_tag :i, nil, class: 'icon-globe cblue' 11 content_tag :i, nil, class: 'icon-globe cblue'
12 end 12 end
13 13
  14 + def internal_icon
  15 + content_tag :i, nil, class: 'icon-shield camber'
  16 + end
  17 +
14 def private_icon 18 def private_icon
15 content_tag :i, nil, class: 'icon-lock cgreen' 19 content_tag :i, nil, class: 'icon-lock cgreen'
16 end 20 end
app/helpers/search_helper.rb
1 module SearchHelper 1 module SearchHelper
2 def search_autocomplete_source 2 def search_autocomplete_source
3 return unless current_user 3 return unless current_user
4 -  
5 [ 4 [
6 groups_autocomplete, 5 groups_autocomplete,
7 projects_autocomplete, 6 projects_autocomplete,
  7 + public_projects_autocomplete,
8 default_autocomplete, 8 default_autocomplete,
9 project_autocomplete, 9 project_autocomplete,
10 help_autocomplete 10 help_autocomplete
@@ -75,4 +75,11 @@ module SearchHelper @@ -75,4 +75,11 @@ module SearchHelper
75 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } 75 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
76 end 76 end
77 end 77 end
  78 +
  79 + # Autocomplete results for the current user's projects
  80 + def public_projects_autocomplete
  81 + Project.public_or_internal_only(current_user).map do |p|
  82 + { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
  83 + end
  84 + end
78 end 85 end
app/helpers/visibility_level_helper.rb 0 → 100644
@@ -0,0 +1,55 @@ @@ -0,0 +1,55 @@
  1 +module VisibilityLevelHelper
  2 + def visibility_level_color(level)
  3 + case level
  4 + when Gitlab::VisibilityLevel::PRIVATE
  5 + 'cgreen'
  6 + when Gitlab::VisibilityLevel::INTERNAL
  7 + 'camber'
  8 + when Gitlab::VisibilityLevel::PUBLIC
  9 + 'cblue'
  10 + end
  11 + end
  12 +
  13 + def visibility_level_description(level)
  14 + capture_haml do
  15 + haml_tag :span do
  16 + case level
  17 + when Gitlab::VisibilityLevel::PRIVATE
  18 + haml_concat "Project access must be granted explicitly for each user."
  19 + when Gitlab::VisibilityLevel::INTERNAL
  20 + haml_concat "The project can be cloned by"
  21 + haml_tag :em, "any logged in user."
  22 + haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users."
  23 + haml_tag :em, "Any logged in user"
  24 + haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository."
  25 + when Gitlab::VisibilityLevel::PUBLIC
  26 + haml_concat "The project can be cloned"
  27 + haml_tag :em, "without any"
  28 + haml_concat "authentication."
  29 + haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path}."
  30 + haml_tag :em, "Any logged in user"
  31 + haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository."
  32 + end
  33 + end
  34 + end
  35 + end
  36 +
  37 + def visibility_level_icon(level)
  38 + case level
  39 + when Gitlab::VisibilityLevel::PRIVATE
  40 + private_icon
  41 + when Gitlab::VisibilityLevel::INTERNAL
  42 + internal_icon
  43 + when Gitlab::VisibilityLevel::PUBLIC
  44 + public_icon
  45 + end
  46 + end
  47 +
  48 + def visibility_level_label(level)
  49 + Project.visibility_levels.key(level)
  50 + end
  51 +
  52 + def restricted_visibility_levels
  53 + current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels
  54 + end
  55 +end
0 \ No newline at end of file 56 \ No newline at end of file
app/models/ability.rb
@@ -29,7 +29,7 @@ class Ability @@ -29,7 +29,7 @@ class Ability
29 nil 29 nil
30 end 30 end
31 31
32 - if project && project.public 32 + if project && project.public?
33 [ 33 [
34 :read_project, 34 :read_project,
35 :read_wiki, 35 :read_wiki,
@@ -71,7 +71,7 @@ class Ability @@ -71,7 +71,7 @@ class Ability
71 rules << project_guest_rules 71 rules << project_guest_rules
72 end 72 end
73 73
74 - if project.public? 74 + if project.public? || project.internal?
75 rules << public_project_rules 75 rules << public_project_rules
76 end 76 end
77 77
@@ -89,7 +89,7 @@ class Ability @@ -89,7 +89,7 @@ class Ability
89 def public_project_rules 89 def public_project_rules
90 project_guest_rules + [ 90 project_guest_rules + [
91 :download_code, 91 :download_code,
92 - :fork_project, 92 + :fork_project
93 ] 93 ]
94 end 94 end
95 95
@@ -145,7 +145,7 @@ class Ability @@ -145,7 +145,7 @@ class Ability
145 def project_admin_rules 145 def project_admin_rules
146 project_master_rules + [ 146 project_master_rules + [
147 :change_namespace, 147 :change_namespace,
148 - :change_public_mode, 148 + :change_visibility_level,
149 :rename_project, 149 :rename_project,
150 :remove_project 150 :remove_project
151 ] 151 ]
app/models/project.rb
@@ -14,24 +14,25 @@ @@ -14,24 +14,25 @@
14 # merge_requests_enabled :boolean default(TRUE), not null 14 # merge_requests_enabled :boolean default(TRUE), not null
15 # wiki_enabled :boolean default(TRUE), not null 15 # wiki_enabled :boolean default(TRUE), not null
16 # namespace_id :integer 16 # namespace_id :integer
17 -# public :boolean default(FALSE), not null  
18 # issues_tracker :string(255) default("gitlab"), not null 17 # issues_tracker :string(255) default("gitlab"), not null
19 # issues_tracker_id :string(255) 18 # issues_tracker_id :string(255)
20 # snippets_enabled :boolean default(TRUE), not null 19 # snippets_enabled :boolean default(TRUE), not null
21 # last_activity_at :datetime 20 # last_activity_at :datetime
22 # imported :boolean default(FALSE), not null 21 # imported :boolean default(FALSE), not null
23 # import_url :string(255) 22 # import_url :string(255)
  23 +# visibility_level :integer default(0), not null
24 # 24 #
25 25
26 class Project < ActiveRecord::Base 26 class Project < ActiveRecord::Base
27 include Gitlab::ShellAdapter 27 include Gitlab::ShellAdapter
  28 + include Gitlab::VisibilityLevel
28 extend Enumerize 29 extend Enumerize
29 30
30 ActsAsTaggableOn.strict_case_match = true 31 ActsAsTaggableOn.strict_case_match = true
31 32
32 attr_accessible :name, :path, :description, :issues_tracker, :label_list, 33 attr_accessible :name, :path, :description, :issues_tracker, :label_list,
33 :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, 34 :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id,
34 - :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin] 35 + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin]
35 36
36 attr_accessible :namespace_id, :creator_id, as: :admin 37 attr_accessible :namespace_id, :creator_id, as: :admin
37 38
@@ -108,7 +109,8 @@ class Project &lt; ActiveRecord::Base @@ -108,7 +109,8 @@ class Project &lt; ActiveRecord::Base
108 scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } 109 scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
109 scope :personal, ->(user) { where(namespace_id: user.namespace_id) } 110 scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
110 scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } 111 scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) }
111 - scope :public_only, -> { where(public: true) } 112 + scope :public_only, -> { where(visibility_level: PUBLIC) }
  113 + scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) }
112 114
113 enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab 115 enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab
114 116
@@ -140,6 +142,10 @@ class Project &lt; ActiveRecord::Base @@ -140,6 +142,10 @@ class Project &lt; ActiveRecord::Base
140 where(path: id, namespace_id: nil).last 142 where(path: id, namespace_id: nil).last
141 end 143 end
142 end 144 end
  145 +
  146 + def visibility_levels
  147 + Gitlab::VisibilityLevel.options
  148 + end
143 end 149 end
144 150
145 def team 151 def team
@@ -456,4 +462,8 @@ class Project &lt; ActiveRecord::Base @@ -456,4 +462,8 @@ class Project &lt; ActiveRecord::Base
456 @default_branch = nil 462 @default_branch = nil
457 default_branch 463 default_branch
458 end 464 end
  465 +
  466 + def visibility_level_field
  467 + visibility_level
  468 + end
459 end 469 end
app/views/admin/projects/index.html.haml
@@ -10,11 +10,15 @@ @@ -10,11 +10,15 @@
10 .control-group 10 .control-group
11 = label_tag :owner_id, 'Owner:', class: 'control-label' 11 = label_tag :owner_id, 'Owner:', class: 'control-label'
12 .controls 12 .controls
13 - = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large'  
14 - .control-group  
15 - = label_tag :public_only, 'Public Only', class: 'control-label'  
16 - .controls  
17 - = check_box_tag :public_only, 1, params[:public_only] 13 + = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp'
  14 + .control-group.visibility-levels
  15 + = label_tag :visibility_level, 'Visibility Levels', class: 'control-label'
  16 + - Project.visibility_levels.each do |label, level|
  17 + .controls
  18 + = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s)
  19 + %span.descr
  20 + = visibility_level_icon(level)
  21 + = label
18 .control-group 22 .control-group
19 = label_tag :with_push, 'Not empty', class: 'control-label' 23 = label_tag :with_push, 'Not empty', class: 'control-label'
20 .controls 24 .controls
@@ -42,10 +46,7 @@ @@ -42,10 +46,7 @@
42 %ul.well-list 46 %ul.well-list
43 - @projects.each do |project| 47 - @projects.each do |project|
44 %li 48 %li
45 - - if project.public  
46 - = public_icon  
47 - - else  
48 - = private_icon 49 + = visibility_level_icon(project.visibility_level)
49 = link_to project.name_with_namespace, [:admin, project] 50 = link_to project.name_with_namespace, [:admin, project]
50 .pull-right 51 .pull-right
51 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 52 = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
app/views/admin/projects/show.html.haml
@@ -66,14 +66,10 @@ @@ -66,14 +66,10 @@
66 %li 66 %li
67 %span.light access: 67 %span.light access:
68 %strong 68 %strong
69 - - if @project.public  
70 - %span.cblue  
71 - %i.icon-share  
72 - Public  
73 - - else  
74 - %span.cgreen  
75 - %i.icon-lock  
76 - Private 69 + %span{ class: visibility_level_color(@project.visibility_level) }
  70 + = visibility_level_icon(@project.visibility_level)
  71 + = visibility_level_label(@project.visibility_level)
  72 +
77 .ui-box 73 .ui-box
78 .title 74 .title
79 Transfer project 75 Transfer project
@@ -88,9 +84,6 @@ @@ -88,9 +84,6 @@
88 .controls 84 .controls
89 = f.submit 'Transfer', class: 'btn btn-primary' 85 = f.submit 'Transfer', class: 'btn btn-primary'
90 86
91 -  
92 -  
93 -  
94 .span6 87 .span6
95 - if @group 88 - if @group
96 .ui-box 89 .ui-box
app/views/dashboard/projects.html.haml
@@ -58,10 +58,10 @@ @@ -58,10 +58,10 @@
58 %h4.project-title 58 %h4.project-title
59 = link_to project_path(project), class: dom_class(project) do 59 = link_to project_path(project), class: dom_class(project) do
60 = project.name_with_namespace 60 = project.name_with_namespace
61 - - if project.public 61 + - unless project.private?
62 %small.access-icon 62 %small.access-icon
63 - = public_icon  
64 - Public 63 + = visibility_level_icon(project.visibility_level)
  64 + = visibility_level_label(project.visibility_level)
65 65
66 - if current_user.can_leave_project?(project) 66 - if current_user.can_leave_project?(project)
67 .pull-right 67 .pull-right
app/views/groups/edit.html.haml
@@ -51,10 +51,7 @@ @@ -51,10 +51,7 @@
51 %ul.well-list 51 %ul.well-list
52 - @group.projects.each do |project| 52 - @group.projects.each do |project|
53 %li 53 %li
54 - - if project.public  
55 - = public_icon  
56 - - else  
57 - = private_icon 54 + = visibility_level_icon(project.visibility_level)
58 = link_to project.name_with_namespace, project 55 = link_to project.name_with_namespace, project
59 .pull-right 56 .pull-right
60 = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" 57 = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small"
app/views/help/permissions.html.haml
@@ -143,7 +143,7 @@ @@ -143,7 +143,7 @@
143 %td.permission-x &#10003; 143 %td.permission-x &#10003;
144 %td.permission-x &#10003; 144 %td.permission-x &#10003;
145 %tr 145 %tr
146 - %td Switch public mode 146 + %td Switch visibility level
147 %td 147 %td
148 %td 148 %td
149 %td 149 %td
app/views/help/public_access.html.haml
@@ -2,14 +2,20 @@ @@ -2,14 +2,20 @@
2 %h3.page-title Public Access 2 %h3.page-title Public Access
3 3
4 %p 4 %p
5 - GitLab allows you to open selected projects to be accessed publicly.  
6 - These projects will be cloneable 5 + GitLab allows you to open selected projects to be accessed publicly or internally.
  6 + Projects with either of these visibility levels will be listed in the #{link_to "public access directory", public_root_path}. Internal projects will only be available to authenticated users.
  7 + %p
  8 + = public_icon
  9 + Public projects will be cloneable
7 %em without any 10 %em without any
8 authentication. 11 authentication.
9 - Also they will be listed on the #{link_to "public access directory", public_root_path}. 12 + %p
  13 + = internal_icon
  14 + Internal projects will be cloneable by
  15 + %em any authenticated user.
10 16
11 %ol 17 %ol
12 %li Go to your project dashboard 18 %li Go to your project dashboard
13 %li Click on the "Edit" tab 19 %li Click on the "Edit" tab
14 - %li Select "Public clone access" 20 + %li Change "Visibility Level"
15 21
app/views/projects/_home_panel.html.haml 0 → 100644
@@ -0,0 +1,31 @@ @@ -0,0 +1,31 @@
  1 +- empty_repo = @project.empty_repo?
  2 +.project-home-panel{:class => ("empty-project" if empty_repo)}
  3 + .row
  4 + .span5
  5 + %h4.project-home-title
  6 + = @project.name_with_namespace
  7 + %span.visibility-level-label
  8 + = visibility_level_icon(@project.visibility_level)
  9 + = visibility_level_label(@project.visibility_level)
  10 +
  11 + .span7
  12 + - unless empty_repo
  13 + .project-home-dropdown
  14 + = render "dropdown"
  15 + .form-horizontal
  16 + = render "shared/clone_panel"
  17 +
  18 + .project-home-extra.clearfix
  19 + .project-home-desc
  20 + - if @project.description.present?
  21 + = @project.description
  22 + - if can?(current_user, :admin_project, @project)
  23 + &ndash;
  24 + %strong= link_to 'Edit', edit_project_path
  25 +
  26 + - unless empty_repo
  27 + .project-home-links
  28 + = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)
  29 + = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)
  30 + = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)
  31 + %span.light.prepend-left-20= repository_size
0 \ No newline at end of file 32 \ No newline at end of file
app/views/projects/_visibility_level.html.haml 0 → 100644
@@ -0,0 +1,23 @@ @@ -0,0 +1,23 @@
  1 +.control-group.project-visibility-level-holder
  2 + = f.label :visibility_level, "Visibility Level"
  3 + - if can_change_visibility_level
  4 + - Gitlab::VisibilityLevel.values.each do |level|
  5 + - restricted = restricted_visibility_levels.include?(level)
  6 + .controls
  7 + = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted
  8 + %span.descr{:class => ("restricted" if restricted)}
  9 + = visibility_level_icon(level)
  10 + %strong
  11 + = visibility_level_label(level)
  12 + = visibility_level_description(level)
  13 + - unless restricted_visibility_levels.empty?
  14 + .controls
  15 + %span.info
  16 + Some visibility level settings have been restricted by the administrator.
  17 + - else
  18 + .controls
  19 + %span.info
  20 + = visibility_level_icon(visibility_level)
  21 + %strong
  22 + = visibility_level_label(visibility_level)
  23 + = visibility_level_description(visibility_level)
0 \ No newline at end of file 24 \ No newline at end of file
app/views/projects/edit.html.haml
@@ -29,22 +29,7 @@ @@ -29,22 +29,7 @@
29 .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'}) 29 .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'})
30 30
31 31
32 - - if can?(current_user, :change_public_mode, @project)  
33 - %fieldset.public-mode  
34 - %legend  
35 - Public mode:  
36 - .control-group  
37 - = f.label :public, class: 'control-label' do  
38 - %span Public access  
39 - .controls  
40 - = f.check_box :public  
41 - %span.descr  
42 - If checked, this project can be cloned  
43 - %em without any  
44 - authentication.  
45 - It will also be listed on the #{link_to "public access directory", public_root_path}.  
46 - %em Any  
47 - user will have #{link_to "Guest", help_permissions_path} permissions on the repository. 32 + = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project)
48 33
49 %fieldset.features 34 %fieldset.features
50 %legend 35 %legend
app/views/projects/empty.html.haml
1 -%h3.page-title  
2 - = @project.name_with_namespace  
3 - .form-horizontal.pull-right  
4 - = render "shared/clone_panel" 1 += render "home_panel"
5 2
6 - if @project.import? && !@project.imported 3 - if @project.import? && !@project.imported
7 .save-project-loader 4 .save-project-loader
app/views/projects/new.html.haml
@@ -47,12 +47,7 @@ @@ -47,12 +47,7 @@
47 %span.light (optional) 47 %span.light (optional)
48 .controls 48 .controls
49 = f.text_area :description, placeholder: "Awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3 49 = f.text_area :description, placeholder: "Awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3
50 - .control-group.project-public-holder  
51 - = f.label :public do  
52 - %span Public project  
53 - .controls  
54 - = f.check_box :public, { checked: gitlab_config.default_projects_features.public }, true, false  
55 - %span.help-inline Make project visible to everyone 50 + = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true
56 51
57 .form-actions 52 .form-actions
58 = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 53 = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4
app/views/projects/show.html.haml
1 -.project-home-panel  
2 - .row  
3 - .span5  
4 - %h4.project-home-title  
5 - = @project.name_with_namespace  
6 - - if @project.public  
7 - %span.public-label Public  
8 - - else  
9 - %span.public-label Private  
10 -  
11 - .span7  
12 - .project-home-dropdown  
13 - = render "dropdown"  
14 - .form-horizontal  
15 - = render "shared/clone_panel"  
16 -  
17 - .project-home-extra.clearfix  
18 - .project-home-desc  
19 - - if @project.description.present?  
20 - = @project.description  
21 - - if can?(current_user, :admin_project, @project)  
22 - &ndash;  
23 - %strong= link_to 'Edit', edit_project_path  
24 -  
25 - .project-home-links  
26 - = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref)  
27 - = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project)  
28 - = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project)  
29 - %span.light.prepend-left-20= repository_size 1 += render "home_panel"
30 2
31 .row 3 .row
32 .span9 4 .span9
app/views/public/projects/index.html.haml
@@ -19,6 +19,10 @@ @@ -19,6 +19,10 @@
19 %h4 19 %h4
20 = link_to project_path(project) do 20 = link_to project_path(project) do
21 = project.name_with_namespace 21 = project.name_with_namespace
  22 + - if project.internal?
  23 + %small.access-icon
  24 + = internal_icon
  25 + Internal
22 .pull-right 26 .pull-right
23 %pre.public-clone git clone #{project.http_url_to_repo} 27 %pre.public-clone git clone #{project.http_url_to_repo}
24 28
config/gitlab.yml.example
@@ -55,6 +55,10 @@ production: &amp;base @@ -55,6 +55,10 @@ production: &amp;base
55 # default: false - Account passwords are not sent via the email if signup is enabled. 55 # default: false - Account passwords are not sent via the email if signup is enabled.
56 # signup_enabled: true 56 # signup_enabled: true
57 57
  58 + # Restrict setting visibility levels for non-admin users.
  59 + # The default is to allow all levels.
  60 + #restricted_visibility_levels: [ "public" ]
  61 +
58 ## Automatic issue closing 62 ## Automatic issue closing
59 # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. 63 # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
60 # This happens when the commit is pushed or merged into the default branch of a project. 64 # This happens when the commit is pushed or merged into the default branch of a project.
@@ -68,7 +72,7 @@ production: &amp;base @@ -68,7 +72,7 @@ production: &amp;base
68 wiki: true 72 wiki: true
69 wall: false 73 wall: false
70 snippets: false 74 snippets: false
71 - public: false 75 + visibility_level: "private" # can be "private" | "internal" | "public"
72 76
73 ## External issues trackers 77 ## External issues trackers
74 issues_tracker: 78 issues_tracker:
config/initializers/1_settings.rb
@@ -30,6 +30,29 @@ class Settings &lt; Settingslogic @@ -30,6 +30,29 @@ class Settings &lt; Settingslogic
30 gitlab.relative_url_root 30 gitlab.relative_url_root
31 ].join('') 31 ].join('')
32 end 32 end
  33 +
  34 + # check that values in `current` (string or integer) is a contant in `modul`.
  35 + def verify_constant_array(modul, current, default)
  36 + values = default || []
  37 + if !current.nil?
  38 + values = []
  39 + current.each do |constant|
  40 + values.push(verify_constant(modul, constant, nil))
  41 + end
  42 + values.delete_if { |value| value.nil? }
  43 + end
  44 + values
  45 + end
  46 +
  47 + # check that `current` (string or integer) is a contant in `modul`.
  48 + def verify_constant(modul, current, default)
  49 + constant = modul.constants.find{ |name| modul.const_get(name) == current }
  50 + value = constant.nil? ? default : modul.const_get(constant)
  51 + if current.is_a? String
  52 + value = modul.const_get(current.upcase) rescue default
  53 + end
  54 + value
  55 + end
33 end 56 end
34 end 57 end
35 58
@@ -68,6 +91,7 @@ rescue ArgumentError # no user configured @@ -68,6 +91,7 @@ rescue ArgumentError # no user configured
68 '/home/' + Settings.gitlab['user'] 91 '/home/' + Settings.gitlab['user']
69 end 92 end
70 Settings.gitlab['signup_enabled'] ||= false 93 Settings.gitlab['signup_enabled'] ||= false
  94 +Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], [])
71 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? 95 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
72 Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? 96 Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
73 Settings.gitlab['default_projects_features'] ||= {} 97 Settings.gitlab['default_projects_features'] ||= {}
@@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features[&#39;merge_requests&#39;] = true if Settings.g @@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features[&#39;merge_requests&#39;] = true if Settings.g
76 Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? 100 Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
77 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil? 101 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
78 Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? 102 Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil?
79 -Settings.gitlab.default_projects_features['public'] = false if Settings.gitlab.default_projects_features['public'].nil? 103 +Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE)
80 104
81 # 105 #
82 # Gravatar 106 # Gravatar
db/migrate/20131112220935_add_visibility_level_to_projects.rb 0 → 100644
@@ -0,0 +1,13 @@ @@ -0,0 +1,13 @@
  1 +class AddVisibilityLevelToProjects < ActiveRecord::Migration
  2 + def self.up
  3 + add_column :projects, :visibility_level, :integer, :default => 0, :null => false
  4 + Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  5 + remove_column :projects, :public
  6 + end
  7 +
  8 + def self.down
  9 + add_column :projects, :public, :boolean, :default => false, :null => false
  10 + Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true)
  11 + remove_column :projects, :visibility_level
  12 + end
  13 +end
@@ -11,7 +11,7 @@ @@ -11,7 +11,7 @@
11 # 11 #
12 # It's strongly recommended to check this file into your version control system. 12 # It's strongly recommended to check this file into your version control system.
13 13
14 -ActiveRecord::Schema.define(:version => 20131112114325) do 14 +ActiveRecord::Schema.define(:version => 20131112220935) do
15 15
16 create_table "broadcast_messages", :force => true do |t| 16 create_table "broadcast_messages", :force => true do |t|
17 t.text "message", :null => false 17 t.text "message", :null => false
@@ -185,13 +185,13 @@ ActiveRecord::Schema.define(:version =&gt; 20131112114325) do @@ -185,13 +185,13 @@ ActiveRecord::Schema.define(:version =&gt; 20131112114325) do
185 t.boolean "merge_requests_enabled", :default => true, :null => false 185 t.boolean "merge_requests_enabled", :default => true, :null => false
186 t.boolean "wiki_enabled", :default => true, :null => false 186 t.boolean "wiki_enabled", :default => true, :null => false
187 t.integer "namespace_id" 187 t.integer "namespace_id"
188 - t.boolean "public", :default => false, :null => false  
189 t.string "issues_tracker", :default => "gitlab", :null => false 188 t.string "issues_tracker", :default => "gitlab", :null => false
190 t.string "issues_tracker_id" 189 t.string "issues_tracker_id"
191 t.boolean "snippets_enabled", :default => true, :null => false 190 t.boolean "snippets_enabled", :default => true, :null => false
192 t.datetime "last_activity_at" 191 t.datetime "last_activity_at"
193 t.boolean "imported", :default => false, :null => false 192 t.boolean "imported", :default => false, :null => false
194 t.string "import_url" 193 t.string "import_url"
  194 + t.integer "visibility_level", :default => 0, :null => false
195 end 195 end
196 196
197 add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" 197 add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
doc/api/projects.md
@@ -15,6 +15,7 @@ GET /projects @@ -15,6 +15,7 @@ GET /projects
15 "description": null, 15 "description": null,
16 "default_branch": "master", 16 "default_branch": "master",
17 "public": false, 17 "public": false,
  18 + "visibility_level": 0,
18 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", 19 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
19 "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", 20 "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
20 "web_url": "http://example.com/diaspora/diaspora-client", 21 "web_url": "http://example.com/diaspora/diaspora-client",
@@ -49,6 +50,7 @@ GET /projects @@ -49,6 +50,7 @@ GET /projects
49 "description": null, 50 "description": null,
50 "default_branch": "master", 51 "default_branch": "master",
51 "public": false, 52 "public": false,
  53 + "visibility_level": 0,
52 "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", 54 "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
53 "http_url_to_repo": "http://example.com/brightbox/puppet.git", 55 "http_url_to_repo": "http://example.com/brightbox/puppet.git",
54 "web_url": "http://example.com/brightbox/puppet", 56 "web_url": "http://example.com/brightbox/puppet",
@@ -117,6 +119,7 @@ Parameters: @@ -117,6 +119,7 @@ Parameters:
117 "description": null, 119 "description": null,
118 "default_branch": "master", 120 "default_branch": "master",
119 "public": false, 121 "public": false,
  122 + "visibility_level": 0,
120 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", 123 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
121 "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", 124 "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
122 "web_url": "http://example.com/diaspora/diaspora-project-site", 125 "web_url": "http://example.com/diaspora/diaspora-project-site",
@@ -234,7 +237,8 @@ Parameters: @@ -234,7 +237,8 @@ Parameters:
234 + `merge_requests_enabled` (optional) 237 + `merge_requests_enabled` (optional)
235 + `wiki_enabled` (optional) 238 + `wiki_enabled` (optional)
236 + `snippets_enabled` (optional) 239 + `snippets_enabled` (optional)
237 -+ `public` (optional) 240 ++ `public` (optional) - if `true` same as setting visibility_level = 20
  241 ++ `visibility_level` (optional)
238 242
239 243
240 ### Create project for user 244 ### Create project for user
@@ -256,7 +260,8 @@ Parameters: @@ -256,7 +260,8 @@ Parameters:
256 + `merge_requests_enabled` (optional) 260 + `merge_requests_enabled` (optional)
257 + `wiki_enabled` (optional) 261 + `wiki_enabled` (optional)
258 + `snippets_enabled` (optional) 262 + `snippets_enabled` (optional)
259 -+ `public` (optional) 263 ++ `public` (optional) - if `true` same as setting visibility_level = 20
  264 ++ `visibility_level` (optional)
260 265
261 266
262 ## Remove project 267 ## Remove project
features/public/public_projects.feature
1 Feature: Public Projects Feature 1 Feature: Public Projects Feature
2 Background: 2 Background:
3 Given public project "Community" 3 Given public project "Community"
  4 + And internal project "Internal"
4 And private project "Enterprise" 5 And private project "Enterprise"
5 6
6 Scenario: I visit public area 7 Scenario: I visit public area
7 When I visit the public projects area 8 When I visit the public projects area
8 Then I should see project "Community" 9 Then I should see project "Community"
  10 + And I should not see project "Internal"
9 And I should not see project "Enterprise" 11 And I should not see project "Enterprise"
10 12
11 Scenario: I visit public project page 13 Scenario: I visit public project page
12 When I visit project "Community" page 14 When I visit project "Community" page
13 Then I should see project "Community" home page 15 Then I should see project "Community" home page
14 16
  17 + Scenario: I visit internal project page
  18 + When I visit project "Internal" page
  19 + Then page status code should be 404
  20 +
  21 + Scenario: I visit private project page
  22 + When I visit project "Enterprise" page
  23 + Then page status code should be 404
  24 +
15 Scenario: I visit an empty public project page 25 Scenario: I visit an empty public project page
16 Given public empty project "Empty Public Project" 26 Given public empty project "Empty Public Project"
17 When I visit empty project page 27 When I visit empty project page
18 Then I should see empty public project details 28 Then I should see empty public project details
  29 +
  30 + Scenario: I visit public area as user
  31 + Given I sign in as a user
  32 + When I visit the public projects area
  33 + Then I should see project "Community"
  34 + And I should see project "Internal"
  35 + And I should not see project "Enterprise"
  36 +
  37 + Scenario: I visit internal project page as user
  38 + Given I sign in as a user
  39 + When I visit project "Internal" page
  40 + Then I should see project "Internal" home page
features/steps/public/projects_feature.rb
1 class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps 1 class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
  2 + include SharedAuthentication
2 include SharedPaths 3 include SharedPaths
  4 + include SharedProject
3 5
4 step 'I should see project "Community"' do 6 step 'I should see project "Community"' do
5 page.should have_content "Community" 7 page.should have_content "Community"
@@ -23,11 +25,11 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps @@ -23,11 +25,11 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps
23 end 25 end
24 26
25 step 'public project "Community"' do 27 step 'public project "Community"' do
26 - create :project_with_code, name: 'Community', public: true 28 + create :project_with_code, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC
27 end 29 end
28 30
29 step 'public empty project "Empty Public Project"' do 31 step 'public empty project "Empty Public Project"' do
30 - create :project, name: 'Empty Public Project', public: true 32 + create :project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC
31 end 33 end
32 34
33 step 'I visit empty project page' do 35 step 'I visit empty project page' do
@@ -48,16 +50,38 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps @@ -48,16 +50,38 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps
48 create :project, name: 'Enterprise' 50 create :project, name: 'Enterprise'
49 end 51 end
50 52
  53 + step 'I visit project "Enterprise" page' do
  54 + project = Project.find_by_name('Enterprise')
  55 + visit project_path(project)
  56 + end
  57 +
51 step 'I should see project "Community" home page' do 58 step 'I should see project "Community" home page' do
52 within '.project-home-title' do 59 within '.project-home-title' do
53 page.should have_content 'Community' 60 page.should have_content 'Community'
54 end 61 end
55 end 62 end
56 63
57 - private 64 + step 'internal project "Internal"' do
  65 + create :project_with_code, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL
  66 + end
58 67
59 - def project  
60 - @project ||= Project.find_by_name("Community") 68 + step 'I should see project "Internal"' do
  69 + page.should have_content "Internal"
  70 + end
  71 +
  72 + step 'I should not see project "Internal"' do
  73 + page.should_not have_content "Internal"
  74 + end
  75 +
  76 + step 'I visit project "Internal" page' do
  77 + project = Project.find_by_name('Internal')
  78 + visit project_path(project)
  79 + end
  80 +
  81 + step 'I should see project "Internal" home page' do
  82 + within '.project-home-title' do
  83 + page.should have_content 'Internal'
  84 + end
61 end 85 end
62 end 86 end
63 87
lib/api/entities.rb
@@ -31,11 +31,13 @@ module API @@ -31,11 +31,13 @@ module API
31 end 31 end
32 32
33 class Project < Grape::Entity 33 class Project < Grape::Entity
34 - expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url 34 + expose :id, :description, :default_branch
  35 + expose :public?, as: :public
  36 + expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url
35 expose :owner, using: Entities::UserBasic 37 expose :owner, using: Entities::UserBasic
36 expose :name, :name_with_namespace 38 expose :name, :name_with_namespace
37 expose :path, :path_with_namespace 39 expose :path, :path_with_namespace
38 - expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public 40 + expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at
39 expose :namespace 41 expose :namespace
40 expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } 42 expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
41 end 43 end
lib/api/projects.rb
@@ -11,6 +11,13 @@ module API @@ -11,6 +11,13 @@ module API
11 end 11 end
12 not_found! 12 not_found!
13 end 13 end
  14 +
  15 + def map_public_to_visibility_level(attrs)
  16 + publik = attrs.delete(:public)
  17 + publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik)
  18 + attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true
  19 + attrs
  20 + end
14 end 21 end
15 22
16 # Get a projects list for authenticated user 23 # Get a projects list for authenticated user
@@ -76,7 +83,8 @@ module API @@ -76,7 +83,8 @@ module API
76 # wiki_enabled (optional) 83 # wiki_enabled (optional)
77 # snippets_enabled (optional) 84 # snippets_enabled (optional)
78 # namespace_id (optional) - defaults to user namespace 85 # namespace_id (optional) - defaults to user namespace
79 - # public (optional) - false by default 86 + # public (optional) - if true same as setting visibility_level = 20
  87 + # visibility_level (optional) - 0 by default
80 # Example Request 88 # Example Request
81 # POST /projects 89 # POST /projects
82 post do 90 post do
@@ -90,7 +98,9 @@ module API @@ -90,7 +98,9 @@ module API
90 :wiki_enabled, 98 :wiki_enabled,
91 :snippets_enabled, 99 :snippets_enabled,
92 :namespace_id, 100 :namespace_id,
93 - :public] 101 + :public,
  102 + :visibility_level]
  103 + attrs = map_public_to_visibility_level(attrs)
94 @project = ::Projects::CreateContext.new(current_user, attrs).execute 104 @project = ::Projects::CreateContext.new(current_user, attrs).execute
95 if @project.saved? 105 if @project.saved?
96 present @project, with: Entities::Project 106 present @project, with: Entities::Project
@@ -114,7 +124,8 @@ module API @@ -114,7 +124,8 @@ module API
114 # merge_requests_enabled (optional) 124 # merge_requests_enabled (optional)
115 # wiki_enabled (optional) 125 # wiki_enabled (optional)
116 # snippets_enabled (optional) 126 # snippets_enabled (optional)
117 - # public (optional) 127 + # public (optional) - if true same as setting visibility_level = 20
  128 + # visibility_level (optional)
118 # Example Request 129 # Example Request
119 # POST /projects/user/:user_id 130 # POST /projects/user/:user_id
120 post "user/:user_id" do 131 post "user/:user_id" do
@@ -128,7 +139,9 @@ module API @@ -128,7 +139,9 @@ module API
128 :merge_requests_enabled, 139 :merge_requests_enabled,
129 :wiki_enabled, 140 :wiki_enabled,
130 :snippets_enabled, 141 :snippets_enabled,
131 - :public] 142 + :public,
  143 + :visibility_level]
  144 + attrs = map_public_to_visibility_level(attrs)
132 @project = ::Projects::CreateContext.new(user, attrs).execute 145 @project = ::Projects::CreateContext.new(user, attrs).execute
133 if @project.saved? 146 if @project.saved?
134 present @project, with: Entities::Project 147 present @project, with: Entities::Project
@@ -290,7 +303,8 @@ module API @@ -290,7 +303,8 @@ module API
290 # GET /projects/search/:query 303 # GET /projects/search/:query
291 get "/search/:query" do 304 get "/search/:query" do
292 ids = current_user.authorized_projects.map(&:id) 305 ids = current_user.authorized_projects.map(&:id)
293 - projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") 306 + visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ]
  307 + projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%")
294 present paginate(projects), with: Entities::Project 308 present paginate(projects), with: Entities::Project
295 end 309 end
296 end 310 end
lib/gitlab/backend/grack_auth.rb
@@ -58,7 +58,7 @@ module Grack @@ -58,7 +58,7 @@ module Grack
58 end 58 end
59 59
60 else 60 else
61 - return unauthorized unless project.public 61 + return unauthorized unless project.public?
62 end 62 end
63 63
64 if authorized_git_request? 64 if authorized_git_request?
@@ -80,7 +80,7 @@ module Grack @@ -80,7 +80,7 @@ module Grack
80 def authorize_request(service) 80 def authorize_request(service)
81 case service 81 case service
82 when 'git-upload-pack' 82 when 'git-upload-pack'
83 - project.public || can?(user, :download_code, project) 83 + can?(user, :download_code, project)
84 when'git-receive-pack' 84 when'git-receive-pack'
85 refs.each do |ref| 85 refs.each do |ref|
86 action = if project.protected_branch?(ref) 86 action = if project.protected_branch?(ref)
lib/gitlab/visibility_level.rb 0 → 100644
@@ -0,0 +1,42 @@ @@ -0,0 +1,42 @@
  1 +# Gitlab::VisibilityLevel module
  2 +#
  3 +# Define allowed public modes that can be used for
  4 +# GitLab projects to determine project public mode
  5 +#
  6 +module Gitlab
  7 + module VisibilityLevel
  8 + PRIVATE = 0
  9 + INTERNAL = 10
  10 + PUBLIC = 20
  11 +
  12 + class << self
  13 + def values
  14 + options.values
  15 + end
  16 +
  17 + def options
  18 + {
  19 + 'Private' => PRIVATE,
  20 + 'Internal' => INTERNAL,
  21 + 'Public' => PUBLIC
  22 + }
  23 + end
  24 +
  25 + def allowed_for?(user, level)
  26 + user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level)
  27 + end
  28 + end
  29 +
  30 + def private?
  31 + visibility_level_field == PRIVATE
  32 + end
  33 +
  34 + def internal?
  35 + visibility_level_field == INTERNAL
  36 + end
  37 +
  38 + def public?
  39 + visibility_level_field == PUBLIC
  40 + end
  41 + end
  42 +end
spec/contexts/projects_create_context_spec.rb
@@ -7,6 +7,7 @@ describe Projects::CreateContext do @@ -7,6 +7,7 @@ describe Projects::CreateContext do
7 describe :create_by_user do 7 describe :create_by_user do
8 before do 8 before do
9 @user = create :user 9 @user = create :user
  10 + @admin = create :user, admin: true
10 @opts = { 11 @opts = {
11 name: "GitLab", 12 name: "GitLab",
12 namespace: @user.namespace 13 namespace: @user.namespace
@@ -37,7 +38,7 @@ describe Projects::CreateContext do @@ -37,7 +38,7 @@ describe Projects::CreateContext do
37 it { @project.namespace.should == @group } 38 it { @project.namespace.should == @group }
38 end 39 end
39 40
40 - context 'respect configured public setting' do 41 + context 'respect configured visibility setting' do
41 before(:each) do 42 before(:each) do
42 @settings = double("settings") 43 @settings = double("settings")
43 @settings.stub(:issues) { true } 44 @settings.stub(:issues) { true }
@@ -46,25 +47,90 @@ describe Projects::CreateContext do @@ -46,25 +47,90 @@ describe Projects::CreateContext do
46 @settings.stub(:wall) { true } 47 @settings.stub(:wall) { true }
47 @settings.stub(:snippets) { true } 48 @settings.stub(:snippets) { true }
48 stub_const("Settings", Class.new) 49 stub_const("Settings", Class.new)
  50 + @restrictions = double("restrictions")
  51 + @restrictions.stub(:restricted_visibility_levels) { [] }
  52 + Settings.stub_chain(:gitlab).and_return(@restrictions)
49 Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) 53 Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
50 end 54 end
51 55
52 context 'should be public when setting is public' do 56 context 'should be public when setting is public' do
53 before do 57 before do
54 - @settings.stub(:public) { true } 58 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
55 @project = create_project(@user, @opts) 59 @project = create_project(@user, @opts)
56 end 60 end
57 61
58 - it { @project.public.should be_true } 62 + it { @project.public?.should be_true }
59 end 63 end
60 64
61 - context 'should be private when setting is not public' do 65 + context 'should be private when setting is private' do
62 before do 66 before do
63 - @settings.stub(:public) { false } 67 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
64 @project = create_project(@user, @opts) 68 @project = create_project(@user, @opts)
65 end 69 end
66 70
67 - it { @project.public.should be_false } 71 + it { @project.private?.should be_true }
  72 + end
  73 +
  74 + context 'should be internal when setting is internal' do
  75 + before do
  76 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::INTERNAL }
  77 + @project = create_project(@user, @opts)
  78 + end
  79 +
  80 + it { @project.internal?.should be_true }
  81 + end
  82 + end
  83 +
  84 + context 'respect configured visibility restrictions setting' do
  85 + before(:each) do
  86 + @settings = double("settings")
  87 + @settings.stub(:issues) { true }
  88 + @settings.stub(:merge_requests) { true }
  89 + @settings.stub(:wiki) { true }
  90 + @settings.stub(:wall) { true }
  91 + @settings.stub(:snippets) { true }
  92 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
  93 + stub_const("Settings", Class.new)
  94 + @restrictions = double("restrictions")
  95 + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] }
  96 + Settings.stub_chain(:gitlab).and_return(@restrictions)
  97 + Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
  98 + end
  99 +
  100 + context 'should be private when option is public' do
  101 + before do
  102 + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  103 + @project = create_project(@user, @opts)
  104 + end
  105 +
  106 + it { @project.private?.should be_true }
  107 + end
  108 +
  109 + context 'should be public when option is public for admin' do
  110 + before do
  111 + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  112 + @project = create_project(@admin, @opts)
  113 + end
  114 +
  115 + it { @project.public?.should be_true }
  116 + end
  117 +
  118 + context 'should be private when option is private' do
  119 + before do
  120 + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
  121 + @project = create_project(@user, @opts)
  122 + end
  123 +
  124 + it { @project.private?.should be_true }
  125 + end
  126 +
  127 + context 'should be internal when option is internal' do
  128 + before do
  129 + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
  130 + @project = create_project(@user, @opts)
  131 + end
  132 +
  133 + it { @project.internal?.should be_true }
68 end 134 end
69 end 135 end
70 end 136 end
@@ -73,3 +139,4 @@ describe Projects::CreateContext do @@ -73,3 +139,4 @@ describe Projects::CreateContext do
73 Projects::CreateContext.new(user, opts).execute 139 Projects::CreateContext.new(user, opts).execute
74 end 140 end
75 end 141 end
  142 +
spec/contexts/projects_update_context_spec.rb 0 → 100644
@@ -0,0 +1,111 @@ @@ -0,0 +1,111 @@
  1 +require 'spec_helper'
  2 +
  3 +describe Projects::UpdateContext do
  4 + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) }
  5 + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) }
  6 +
  7 + describe :update_by_user do
  8 + before do
  9 + @user = create :user
  10 + @admin = create :user, admin: true
  11 + @project = create :project, creator_id: @user.id, namespace: @user.namespace
  12 + @opts = { project: {} }
  13 + end
  14 +
  15 + context 'should be private when updated to private' do
  16 + before do
  17 + @created_private = @project.private?
  18 +
  19 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
  20 + update_project(@project, @user, @opts)
  21 + end
  22 +
  23 + it { @created_private.should be_true }
  24 + it { @project.private?.should be_true }
  25 + end
  26 +
  27 + context 'should be internal when updated to internal' do
  28 + before do
  29 + @created_private = @project.private?
  30 +
  31 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
  32 + update_project(@project, @user, @opts)
  33 + end
  34 +
  35 + it { @created_private.should be_true }
  36 + it { @project.internal?.should be_true }
  37 + end
  38 +
  39 + context 'should be public when updated to public' do
  40 + before do
  41 + @created_private = @project.private?
  42 +
  43 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  44 + update_project(@project, @user, @opts)
  45 + end
  46 +
  47 + it { @created_private.should be_true }
  48 + it { @project.public?.should be_true }
  49 + end
  50 +
  51 + context 'respect configured visibility restrictions setting' do
  52 + before(:each) do
  53 + @restrictions = double("restrictions")
  54 + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] }
  55 + Settings.stub_chain(:gitlab).and_return(@restrictions)
  56 + end
  57 +
  58 + context 'should be private when updated to private' do
  59 + before do
  60 + @created_private = @project.private?
  61 +
  62 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
  63 + update_project(@project, @user, @opts)
  64 + end
  65 +
  66 + it { @created_private.should be_true }
  67 + it { @project.private?.should be_true }
  68 + end
  69 +
  70 + context 'should be internal when updated to internal' do
  71 + before do
  72 + @created_private = @project.private?
  73 +
  74 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL)
  75 + update_project(@project, @user, @opts)
  76 + end
  77 +
  78 + it { @created_private.should be_true }
  79 + it { @project.internal?.should be_true }
  80 + end
  81 +
  82 + context 'should be private when updated to public' do
  83 + before do
  84 + @created_private = @project.private?
  85 +
  86 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  87 + update_project(@project, @user, @opts)
  88 + end
  89 +
  90 + it { @created_private.should be_true }
  91 + it { @project.private?.should be_true }
  92 + end
  93 +
  94 + context 'should be public when updated to public by admin' do
  95 + before do
  96 + @created_private = @project.private?
  97 +
  98 + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC)
  99 + update_project(@project, @admin, @opts)
  100 + end
  101 +
  102 + it { @created_private.should be_true }
  103 + it { @project.public?.should be_true }
  104 + end
  105 + end
  106 + end
  107 +
  108 + def update_project(project, user, opts)
  109 + Projects::UpdateContext.new(project, user, opts).execute
  110 + end
  111 +end
0 \ No newline at end of file 112 \ No newline at end of file
spec/contexts/search_context_spec.rb
@@ -3,23 +3,39 @@ require &#39;spec_helper&#39; @@ -3,23 +3,39 @@ require &#39;spec_helper&#39;
3 describe SearchContext do 3 describe SearchContext do
4 let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') } 4 let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') }
5 let(:user) { create(:user, namespace: found_namespace) } 5 let(:user) { create(:user, namespace: found_namespace) }
6 - let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, public: false) } 6 + let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
7 7
8 let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') } 8 let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') }
9 - let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, public: false) }  
10 - let(:public_namespace) { create(:namespace, path: 'something_else',name: 'searchable public namespace') }  
11 - let(:other_user) { create(:user, namespace: public_namespace) }  
12 - let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: other_user.id, namespace: public_namespace, public: true) } 9 + let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
  10 +
  11 + let(:internal_namespace) { create(:namespace, path: 'something_internal',name: 'searchable internal namespace') }
  12 + let(:internal_user) { create(:user, namespace: internal_namespace) }
  13 + let!(:internal_project) { create(:project, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace, visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
  14 +
  15 + let(:public_namespace) { create(:namespace, path: 'something_public',name: 'searchable public namespace') }
  16 + let(:public_user) { create(:user, namespace: public_namespace) }
  17 + let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
13 18
14 describe '#execute' do 19 describe '#execute' do
15 it 'public projects should be searchable' do 20 it 'public projects should be searchable' do
16 - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable"}) 21 + context = SearchContext.new([found_project.id], nil, {search_code: false, search: "searchable"})
17 results = context.execute 22 results = context.execute
18 results[:projects].should == [found_project, public_project] 23 results[:projects].should == [found_project, public_project]
19 end 24 end
20 25
  26 + it 'internal projects should be searchable' do
  27 + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable"})
  28 + results = context.execute
  29 + # can't seem to rely on the return order, so check this way
  30 + #subject { results[:projects] }
  31 + results[:projects].should have(3).items
  32 + results[:projects].should include(found_project)
  33 + results[:projects].should include(internal_project)
  34 + results[:projects].should include(public_project)
  35 + end
  36 +
21 it 'namespace name should be searchable' do 37 it 'namespace name should be searchable' do
22 - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable namespace"}) 38 + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable namespace"})
23 results = context.execute 39 results = context.execute
24 results[:projects].should == [found_project] 40 results[:projects].should == [found_project]
25 end 41 end
spec/features/security/project/internal_access_spec.rb 0 → 100644
@@ -0,0 +1,251 @@ @@ -0,0 +1,251 @@
  1 +require 'spec_helper'
  2 +
  3 +describe "Internal Project Access" do
  4 + let(:project) { create(:project_with_code) }
  5 +
  6 + let(:master) { create(:user) }
  7 + let(:guest) { create(:user) }
  8 + let(:reporter) { create(:user) }
  9 +
  10 + before do
  11 + # internal project
  12 + project.visibility_level = Gitlab::VisibilityLevel::INTERNAL
  13 + project.save!
  14 +
  15 + # full access
  16 + project.team << [master, :master]
  17 +
  18 + # readonly
  19 + project.team << [reporter, :reporter]
  20 +
  21 + end
  22 +
  23 + describe "Project should be internal" do
  24 + subject { project }
  25 +
  26 + its(:internal?) { should be_true }
  27 + end
  28 +
  29 + describe "GET /:project_path" do
  30 + subject { project_path(project) }
  31 +
  32 + it { should be_allowed_for master }
  33 + it { should be_allowed_for reporter }
  34 + it { should be_allowed_for :admin }
  35 + it { should be_allowed_for guest }
  36 + it { should be_allowed_for :user }
  37 + it { should be_denied_for :visitor }
  38 + end
  39 +
  40 + describe "GET /:project_path/tree/master" do
  41 + subject { project_tree_path(project, project.repository.root_ref) }
  42 +
  43 + it { should be_allowed_for master }
  44 + it { should be_allowed_for reporter }
  45 + it { should be_allowed_for :admin }
  46 + it { should be_allowed_for guest }
  47 + it { should be_allowed_for :user }
  48 + it { should be_denied_for :visitor }
  49 + end
  50 +
  51 + describe "GET /:project_path/commits/master" do
  52 + subject { project_commits_path(project, project.repository.root_ref, limit: 1) }
  53 +
  54 + it { should be_allowed_for master }
  55 + it { should be_allowed_for reporter }
  56 + it { should be_allowed_for :admin }
  57 + it { should be_allowed_for guest }
  58 + it { should be_allowed_for :user }
  59 + it { should be_denied_for :visitor }
  60 + end
  61 +
  62 + describe "GET /:project_path/commit/:sha" do
  63 + subject { project_commit_path(project, project.repository.commit) }
  64 +
  65 + it { should be_allowed_for master }
  66 + it { should be_allowed_for reporter }
  67 + it { should be_allowed_for :admin }
  68 + it { should be_allowed_for guest }
  69 + it { should be_allowed_for :user }
  70 + it { should be_denied_for :visitor }
  71 + end
  72 +
  73 + describe "GET /:project_path/compare" do
  74 + subject { project_compare_index_path(project) }
  75 +
  76 + it { should be_allowed_for master }
  77 + it { should be_allowed_for reporter }
  78 + it { should be_allowed_for :admin }
  79 + it { should be_allowed_for guest }
  80 + it { should be_allowed_for :user }
  81 + it { should be_denied_for :visitor }
  82 + end
  83 +
  84 + describe "GET /:project_path/team" do
  85 + subject { project_team_index_path(project) }
  86 +
  87 + it { should be_allowed_for master }
  88 + it { should be_denied_for reporter }
  89 + it { should be_allowed_for :admin }
  90 + it { should be_denied_for guest }
  91 + it { should be_denied_for :user }
  92 + it { should be_denied_for :visitor }
  93 + end
  94 +
  95 + describe "GET /:project_path/wall" do
  96 + subject { project_wall_path(project) }
  97 +
  98 + it { should be_allowed_for master }
  99 + it { should be_allowed_for reporter }
  100 + it { should be_allowed_for :admin }
  101 + it { should be_allowed_for guest }
  102 + it { should be_allowed_for :user }
  103 + it { should be_denied_for :visitor }
  104 + end
  105 +
  106 + describe "GET /:project_path/blob" do
  107 + before do
  108 + commit = project.repository.commit
  109 + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name
  110 + @blob_path = project_blob_path(project, File.join(commit.id, path))
  111 + end
  112 +
  113 + it { @blob_path.should be_allowed_for master }
  114 + it { @blob_path.should be_allowed_for reporter }
  115 + it { @blob_path.should be_allowed_for :admin }
  116 + it { @blob_path.should be_allowed_for guest }
  117 + it { @blob_path.should be_allowed_for :user }
  118 + it { @blob_path.should be_denied_for :visitor }
  119 + end
  120 +
  121 + describe "GET /:project_path/edit" do
  122 + subject { edit_project_path(project) }
  123 +
  124 + it { should be_allowed_for master }
  125 + it { should be_denied_for reporter }
  126 + it { should be_allowed_for :admin }
  127 + it { should be_denied_for guest }
  128 + it { should be_denied_for :user }
  129 + it { should be_denied_for :visitor }
  130 + end
  131 +
  132 + describe "GET /:project_path/deploy_keys" do
  133 + subject { project_deploy_keys_path(project) }
  134 +
  135 + it { should be_allowed_for master }
  136 + it { should be_denied_for reporter }
  137 + it { should be_allowed_for :admin }
  138 + it { should be_denied_for guest }
  139 + it { should be_denied_for :user }
  140 + it { should be_denied_for :visitor }
  141 + end
  142 +
  143 + describe "GET /:project_path/issues" do
  144 + subject { project_issues_path(project) }
  145 +
  146 + it { should be_allowed_for master }
  147 + it { should be_allowed_for reporter }
  148 + it { should be_allowed_for :admin }
  149 + it { should be_allowed_for guest }
  150 + it { should be_allowed_for :user }
  151 + it { should be_denied_for :visitor }
  152 + end
  153 +
  154 + describe "GET /:project_path/snippets" do
  155 + subject { project_snippets_path(project) }
  156 +
  157 + it { should be_allowed_for master }
  158 + it { should be_allowed_for reporter }
  159 + it { should be_allowed_for :admin }
  160 + it { should be_allowed_for guest }
  161 + it { should be_allowed_for :user }
  162 + it { should be_denied_for :visitor }
  163 + end
  164 +
  165 + describe "GET /:project_path/snippets/new" do
  166 + subject { new_project_snippet_path(project) }
  167 +
  168 + it { should be_allowed_for master }
  169 + it { should be_allowed_for reporter }
  170 + it { should be_allowed_for :admin }
  171 + it { should be_denied_for guest }
  172 + it { should be_denied_for :user }
  173 + it { should be_denied_for :visitor }
  174 + end
  175 +
  176 + describe "GET /:project_path/merge_requests" do
  177 + subject { project_merge_requests_path(project) }
  178 +
  179 + it { should be_allowed_for master }
  180 + it { should be_allowed_for reporter }
  181 + it { should be_allowed_for :admin }
  182 + it { should be_allowed_for guest }
  183 + it { should be_allowed_for :user }
  184 + it { should be_denied_for :visitor }
  185 + end
  186 +
  187 + describe "GET /:project_path/merge_requests/new" do
  188 + subject { new_project_merge_request_path(project) }
  189 +
  190 + it { should be_allowed_for master }
  191 + it { should be_denied_for reporter }
  192 + it { should be_allowed_for :admin }
  193 + it { should be_denied_for guest }
  194 + it { should be_denied_for :user }
  195 + it { should be_denied_for :visitor }
  196 + end
  197 +
  198 + describe "GET /:project_path/branches/recent" do
  199 + subject { recent_project_branches_path(project) }
  200 +
  201 + it { should be_allowed_for master }
  202 + it { should be_allowed_for reporter }
  203 + it { should be_allowed_for :admin }
  204 + it { should be_allowed_for guest }
  205 + it { should be_allowed_for :user }
  206 + it { should be_denied_for :visitor }
  207 + end
  208 +
  209 + describe "GET /:project_path/branches" do
  210 + subject { project_branches_path(project) }
  211 +
  212 + before do
  213 + # Speed increase
  214 + Project.any_instance.stub(:branches).and_return([])
  215 + end
  216 +
  217 + it { should be_allowed_for master }
  218 + it { should be_allowed_for reporter }
  219 + it { should be_allowed_for :admin }
  220 + it { should be_allowed_for guest }
  221 + it { should be_allowed_for :user }
  222 + it { should be_denied_for :visitor }
  223 + end
  224 +
  225 + describe "GET /:project_path/tags" do
  226 + subject { project_tags_path(project) }
  227 +
  228 + before do
  229 + # Speed increase
  230 + Project.any_instance.stub(:tags).and_return([])
  231 + end
  232 +
  233 + it { should be_allowed_for master }
  234 + it { should be_allowed_for reporter }
  235 + it { should be_allowed_for :admin }
  236 + it { should be_allowed_for guest }
  237 + it { should be_allowed_for :user }
  238 + it { should be_denied_for :visitor }
  239 + end
  240 +
  241 + describe "GET /:project_path/hooks" do
  242 + subject { project_hooks_path(project) }
  243 +
  244 + it { should be_allowed_for master }
  245 + it { should be_denied_for reporter }
  246 + it { should be_allowed_for :admin }
  247 + it { should be_denied_for guest }
  248 + it { should be_denied_for :user }
  249 + it { should be_denied_for :visitor }
  250 + end
  251 +end
spec/features/security/project/private_access_spec.rb
@@ -15,6 +15,12 @@ describe &quot;Private Project Access&quot; do @@ -15,6 +15,12 @@ describe &quot;Private Project Access&quot; do
15 project.team << [reporter, :reporter] 15 project.team << [reporter, :reporter]
16 end 16 end
17 17
  18 + describe "Project should be private" do
  19 + subject { project }
  20 +
  21 + its(:private?) { should be_true }
  22 + end
  23 +
18 describe "GET /:project_path" do 24 describe "GET /:project_path" do
19 subject { project_path(project) } 25 subject { project_path(project) }
20 26
spec/features/security/project/public_access_spec.rb
@@ -9,7 +9,7 @@ describe &quot;Public Project Access&quot; do @@ -9,7 +9,7 @@ describe &quot;Public Project Access&quot; do
9 9
10 before do 10 before do
11 # public project 11 # public project
12 - project.public = true 12 + project.visibility_level = Gitlab::VisibilityLevel::PUBLIC
13 project.save! 13 project.save!
14 14
15 # full access 15 # full access
spec/models/project_spec.rb
@@ -14,13 +14,13 @@ @@ -14,13 +14,13 @@
14 # merge_requests_enabled :boolean default(TRUE), not null 14 # merge_requests_enabled :boolean default(TRUE), not null
15 # wiki_enabled :boolean default(TRUE), not null 15 # wiki_enabled :boolean default(TRUE), not null
16 # namespace_id :integer 16 # namespace_id :integer
17 -# public :boolean default(FALSE), not null  
18 # issues_tracker :string(255) default("gitlab"), not null 17 # issues_tracker :string(255) default("gitlab"), not null
19 # issues_tracker_id :string(255) 18 # issues_tracker_id :string(255)
20 # snippets_enabled :boolean default(TRUE), not null 19 # snippets_enabled :boolean default(TRUE), not null
21 # last_activity_at :datetime 20 # last_activity_at :datetime
22 # imported :boolean default(FALSE), not null 21 # imported :boolean default(FALSE), not null
23 # import_url :string(255) 22 # import_url :string(255)
  23 +# visibility_level :integer default(0), not null
24 # 24 #
25 25
26 require 'spec_helper' 26 require 'spec_helper'
spec/requests/api/projects_spec.rb
@@ -132,15 +132,45 @@ describe API::API do @@ -132,15 +132,45 @@ describe API::API do
132 end 132 end
133 133
134 it "should set a project as public" do 134 it "should set a project as public" do
  135 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
  136 + post api("/projects", user), project
  137 + json_response['public'].should be_true
  138 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
  139 + end
  140 +
  141 + it "should set a project as public using :public" do
135 project = attributes_for(:project, { public: true }) 142 project = attributes_for(:project, { public: true })
136 post api("/projects", user), project 143 post api("/projects", user), project
137 json_response['public'].should be_true 144 json_response['public'].should be_true
  145 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
  146 + end
  147 +
  148 + it "should set a project as internal" do
  149 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
  150 + post api("/projects", user), project
  151 + json_response['public'].should be_false
  152 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
  153 + end
  154 +
  155 + it "should set a project as internal overriding :public" do
  156 + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
  157 + post api("/projects", user), project
  158 + json_response['public'].should be_false
  159 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
138 end 160 end
139 161
140 it "should set a project as private" do 162 it "should set a project as private" do
  163 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
  164 + post api("/projects", user), project
  165 + json_response['public'].should be_false
  166 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
  167 + end
  168 +
  169 + it "should set a project as private using :public" do
141 project = attributes_for(:project, { public: false }) 170 project = attributes_for(:project, { public: false })
142 post api("/projects", user), project 171 post api("/projects", user), project
143 json_response['public'].should be_false 172 json_response['public'].should be_false
  173 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
144 end 174 end
145 end 175 end
146 176
@@ -183,19 +213,46 @@ describe API::API do @@ -183,19 +213,46 @@ describe API::API do
183 end 213 end
184 214
185 it "should set a project as public" do 215 it "should set a project as public" do
  216 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC })
  217 + post api("/projects/user/#{user.id}", admin), project
  218 + json_response['public'].should be_true
  219 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
  220 + end
  221 +
  222 + it "should set a project as public using :public" do
186 project = attributes_for(:project, { public: true }) 223 project = attributes_for(:project, { public: true })
187 post api("/projects/user/#{user.id}", admin), project 224 post api("/projects/user/#{user.id}", admin), project
188 json_response['public'].should be_true 225 json_response['public'].should be_true
  226 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC
  227 + end
189 228
  229 + it "should set a project as internal" do
  230 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL })
  231 + post api("/projects/user/#{user.id}", admin), project
  232 + json_response['public'].should be_false
  233 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
190 end 234 end
191 235
192 - it "should set a project as private" do  
193 - project = attributes_for(:project, { public: false }) 236 + it "should set a project as internal overriding :public" do
  237 + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL })
194 post api("/projects/user/#{user.id}", admin), project 238 post api("/projects/user/#{user.id}", admin), project
195 json_response['public'].should be_false 239 json_response['public'].should be_false
  240 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL
  241 + end
196 242
  243 + it "should set a project as private" do
  244 + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE })
  245 + post api("/projects/user/#{user.id}", admin), project
  246 + json_response['public'].should be_false
  247 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
197 end 248 end
198 249
  250 + it "should set a project as private using :public" do
  251 + project = attributes_for(:project, { public: false })
  252 + post api("/projects/user/#{user.id}", admin), project
  253 + json_response['public'].should be_false
  254 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
  255 + end
199 end 256 end
200 257
201 describe "GET /projects/:id" do 258 describe "GET /projects/:id" do
@@ -649,10 +706,10 @@ describe API::API do @@ -649,10 +706,10 @@ describe API::API do
649 706
650 describe :fork_admin do 707 describe :fork_admin do
651 let(:project_fork_target) { create(:project) } 708 let(:project_fork_target) { create(:project) }
652 - let(:project_fork_source) { create(:project, public: true) } 709 + let(:project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
653 710
654 describe "POST /projects/:id/fork/:forked_from_id" do 711 describe "POST /projects/:id/fork/:forked_from_id" do
655 - let(:new_project_fork_source) { create(:project, public: true) } 712 + let(:new_project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
656 713
657 it "shouldn't available for non admin users" do 714 it "shouldn't available for non admin users" do
658 post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) 715 post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
@@ -721,8 +778,10 @@ describe API::API do @@ -721,8 +778,10 @@ describe API::API do
721 let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } 778 let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
722 let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } 779 let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) }
723 let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } 780 let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) }
724 - let!(:public) { create(:project, name: "another #{query}",public: true) }  
725 - let!(:unfound_public) { create(:project, name: 'unfound public', public: true) } 781 + let!(:internal) { create(:project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
  782 + let!(:unfound_internal) { create(:project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) }
  783 + let!(:public) { create(:project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
  784 + let!(:unfound_public) { create(:project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) }
726 785
727 context "when unauthenticated" do 786 context "when unauthenticated" do
728 it "should return authentication error" do 787 it "should return authentication error" do
@@ -736,7 +795,7 @@ describe API::API do @@ -736,7 +795,7 @@ describe API::API do
736 get api("/projects/search/#{query}",user) 795 get api("/projects/search/#{query}",user)
737 response.status.should == 200 796 response.status.should == 200
738 json_response.should be_an Array 797 json_response.should be_an Array
739 - json_response.size.should == 5 798 + json_response.size.should == 6
740 json_response.each {|project| project['name'].should =~ /.*query.*/} 799 json_response.each {|project| project['name'].should =~ /.*query.*/}
741 end 800 end
742 end 801 end
@@ -746,8 +805,8 @@ describe API::API do @@ -746,8 +805,8 @@ describe API::API do
746 get api("/projects/search/#{query}", user2) 805 get api("/projects/search/#{query}", user2)
747 response.status.should == 200 806 response.status.should == 200
748 json_response.should be_an Array 807 json_response.should be_an Array
749 - json_response.size.should == 1  
750 - json_response.first['name'].should == "another #{query}" 808 + json_response.size.should == 2
  809 + json_response.each {|project| project['name'].should =~ /(internal|public) query/}
751 end 810 end
752 end 811 end
753 end 812 end