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 365 &.input-large {
366 366 width: 210px;
367 367 }
  368 +
  369 + &.input-clamp {
  370 + max-width: 100%;
  371 + }
368 372 }
369 373  
370 374 .user-result {
... ...
app/assets/stylesheets/gitlab_bootstrap/common.scss
... ... @@ -6,6 +6,7 @@
6 6 .cblue { color: #29A }
7 7 .cblack { color: #111 }
8 8 .cdark { color: #444 }
  9 +.camber { color: #ffc000 }
9 10 .cwhite { color: #fff!important }
10 11 .bgred { background: #F2DEDE!important }
11 12  
... ...
app/assets/stylesheets/sections/admin.scss
... ... @@ -20,6 +20,15 @@
20 20 label { width: 110px; }
21 21 .controls { margin-left: 130px; }
22 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 34 .broadcast-messages {
... ...
app/assets/stylesheets/sections/projects.scss
... ... @@ -18,6 +18,12 @@
18 18 border-bottom: 1px solid #DDD;
19 19 padding-bottom: 25px;
20 20 margin-bottom: 30px;
  21 +
  22 + &.empty-project {
  23 + border-bottom: 0px;
  24 + padding-bottom: 15px;
  25 + margin-bottom: 0px;
  26 + }
21 27  
22 28 .project-home-title {
23 29 font-size: 18px;
... ... @@ -45,7 +51,7 @@
45 51 }
46 52 }
47 53  
48   - .public-label {
  54 + .visibility-level-label {
49 55 font-size: 14px;
50 56 background: #f1f1f1;
51 57 padding: 8px 10px;
... ... @@ -53,6 +59,10 @@
53 59 margin-left: 10px;
54 60 color: #888;
55 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 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 164 margin: 0px;
131 165 }
132 166  
133   -.my-projects {
  167 +.my-projects,
  168 +.public-projects {
134 169 li {
135 170 .project-info {
136 171 margin-bottom: 10px;
... ...
app/contexts/projects/create_context.rb
... ... @@ -8,6 +8,11 @@ module Projects
8 8 # get namespace id
9 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 16 # Load default feature settings
12 17 default_features = Gitlab.config.gitlab.default_projects_features
13 18  
... ... @@ -17,7 +22,7 @@ module Projects
17 22 wall_enabled: default_features.wall,
18 23 snippets_enabled: default_features.snippets,
19 24 merge_requests_enabled: default_features.merge_requests,
20   - public: default_features.public
  25 + visibility_level: default_features.visibility_level
21 26 }.stringify_keys
22 27  
23 28 @project = Project.new(default_opts.merge(params))
... ...
app/contexts/projects/update_context.rb
... ... @@ -2,7 +2,11 @@ module Projects
2 2 class UpdateContext < BaseContext
3 3 def execute(role = :default)
4 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 10 new_branch = params[:project].delete(:default_branch)
7 11  
8 12 if project.repository.exists? && new_branch != project.default_branch
... ...
app/contexts/search_context.rb
1 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 6 end
7 7  
8 8 def execute
... ... @@ -10,7 +10,8 @@ class SearchContext
10 10 query = Shellwords.shellescape(query) if query.present?
11 11  
12 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 16 # Search inside single project
16 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 8 user = User.find_by_id(owner_id)
9 9  
10 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 12 @projects = @projects.with_push if params[:with_push].present?
13 13 @projects = @projects.abandoned if params[:abandoned].present?
14 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 102 end
103 103  
104 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 106 end
107 107  
108 108 def authorize_push!
... ...
app/controllers/projects/application_controller.rb
... ... @@ -10,7 +10,7 @@ class Projects::ApplicationController &lt; ApplicationController
10 10 id = params[:project_id] || params[:id]
11 11 @project = Project.find_with_namespace(id)
12 12  
13   - return if @project && @project.public
  13 + return if @project && @project.public?
14 14 end
15 15  
16 16 super
... ...
app/controllers/projects_controller.rb
... ... @@ -55,7 +55,7 @@ class ProjectsController &lt; ApplicationController
55 55 end
56 56  
57 57 def show
58   - return authenticate_user! unless @project.public || current_user
  58 + return authenticate_user! unless @project.public? || current_user
59 59  
60 60 limit = (params[:limit] || 20).to_i
61 61 @events = @project.events.recent
... ...
app/controllers/public/projects_controller.rb
... ... @@ -6,7 +6,7 @@ class Public::ProjectsController &lt; ApplicationController
6 6 layout 'public'
7 7  
8 8 def index
9   - @projects = Project.public_only
  9 + @projects = Project.public_or_internal_only(current_user)
10 10 @projects = @projects.search(params[:search]) if params[:search].present?
11 11 @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20)
12 12 end
... ...
app/controllers/search_controller.rb
... ... @@ -14,7 +14,7 @@ class SearchController &lt; ApplicationController
14 14 project_ids.select! { |id| id == project_id.to_i}
15 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 19 @projects = result[:projects]
20 20 @merge_requests = result[:merge_requests]
... ...
app/helpers/icons_helper.rb
... ... @@ -11,6 +11,10 @@ module IconsHelper
11 11 content_tag :i, nil, class: 'icon-globe cblue'
12 12 end
13 13  
  14 + def internal_icon
  15 + content_tag :i, nil, class: 'icon-shield camber'
  16 + end
  17 +
14 18 def private_icon
15 19 content_tag :i, nil, class: 'icon-lock cgreen'
16 20 end
... ...
app/helpers/search_helper.rb
1 1 module SearchHelper
2 2 def search_autocomplete_source
3 3 return unless current_user
4   -
5 4 [
6 5 groups_autocomplete,
7 6 projects_autocomplete,
  7 + public_projects_autocomplete,
8 8 default_autocomplete,
9 9 project_autocomplete,
10 10 help_autocomplete
... ... @@ -75,4 +75,11 @@ module SearchHelper
75 75 { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) }
76 76 end
77 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 85 end
... ...
app/helpers/visibility_level_helper.rb 0 → 100644
... ... @@ -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 56 \ No newline at end of file
... ...
app/models/ability.rb
... ... @@ -29,7 +29,7 @@ class Ability
29 29 nil
30 30 end
31 31  
32   - if project && project.public
  32 + if project && project.public?
33 33 [
34 34 :read_project,
35 35 :read_wiki,
... ... @@ -71,7 +71,7 @@ class Ability
71 71 rules << project_guest_rules
72 72 end
73 73  
74   - if project.public?
  74 + if project.public? || project.internal?
75 75 rules << public_project_rules
76 76 end
77 77  
... ... @@ -89,7 +89,7 @@ class Ability
89 89 def public_project_rules
90 90 project_guest_rules + [
91 91 :download_code,
92   - :fork_project,
  92 + :fork_project
93 93 ]
94 94 end
95 95  
... ... @@ -145,7 +145,7 @@ class Ability
145 145 def project_admin_rules
146 146 project_master_rules + [
147 147 :change_namespace,
148   - :change_public_mode,
  148 + :change_visibility_level,
149 149 :rename_project,
150 150 :remove_project
151 151 ]
... ...
app/models/project.rb
... ... @@ -14,24 +14,25 @@
14 14 # merge_requests_enabled :boolean default(TRUE), not null
15 15 # wiki_enabled :boolean default(TRUE), not null
16 16 # namespace_id :integer
17   -# public :boolean default(FALSE), not null
18 17 # issues_tracker :string(255) default("gitlab"), not null
19 18 # issues_tracker_id :string(255)
20 19 # snippets_enabled :boolean default(TRUE), not null
21 20 # last_activity_at :datetime
22 21 # imported :boolean default(FALSE), not null
23 22 # import_url :string(255)
  23 +# visibility_level :integer default(0), not null
24 24 #
25 25  
26 26 class Project < ActiveRecord::Base
27 27 include Gitlab::ShellAdapter
  28 + include Gitlab::VisibilityLevel
28 29 extend Enumerize
29 30  
30 31 ActsAsTaggableOn.strict_case_match = true
31 32  
32 33 attr_accessible :name, :path, :description, :issues_tracker, :label_list,
33 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 37 attr_accessible :namespace_id, :creator_id, as: :admin
37 38  
... ... @@ -108,7 +109,8 @@ class Project &lt; ActiveRecord::Base
108 109 scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") }
109 110 scope :personal, ->(user) { where(namespace_id: user.namespace_id) }
110 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 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 142 where(path: id, namespace_id: nil).last
141 143 end
142 144 end
  145 +
  146 + def visibility_levels
  147 + Gitlab::VisibilityLevel.options
  148 + end
143 149 end
144 150  
145 151 def team
... ... @@ -456,4 +462,8 @@ class Project &lt; ActiveRecord::Base
456 462 @default_branch = nil
457 463 default_branch
458 464 end
  465 +
  466 + def visibility_level_field
  467 + visibility_level
  468 + end
459 469 end
... ...
app/views/admin/projects/index.html.haml
... ... @@ -10,11 +10,15 @@
10 10 .control-group
11 11 = label_tag :owner_id, 'Owner:', class: 'control-label'
12 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 22 .control-group
19 23 = label_tag :with_push, 'Not empty', class: 'control-label'
20 24 .controls
... ... @@ -42,10 +46,7 @@
42 46 %ul.well-list
43 47 - @projects.each do |project|
44 48 %li
45   - - if project.public
46   - = public_icon
47   - - else
48   - = private_icon
  49 + = visibility_level_icon(project.visibility_level)
49 50 = link_to project.name_with_namespace, [:admin, project]
50 51 .pull-right
51 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 66 %li
67 67 %span.light access:
68 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 73 .ui-box
78 74 .title
79 75 Transfer project
... ... @@ -88,9 +84,6 @@
88 84 .controls
89 85 = f.submit 'Transfer', class: 'btn btn-primary'
90 86  
91   -
92   -
93   -
94 87 .span6
95 88 - if @group
96 89 .ui-box
... ...
app/views/dashboard/projects.html.haml
... ... @@ -58,10 +58,10 @@
58 58 %h4.project-title
59 59 = link_to project_path(project), class: dom_class(project) do
60 60 = project.name_with_namespace
61   - - if project.public
  61 + - unless project.private?
62 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 66 - if current_user.can_leave_project?(project)
67 67 .pull-right
... ...
app/views/groups/edit.html.haml
... ... @@ -51,10 +51,7 @@
51 51 %ul.well-list
52 52 - @group.projects.each do |project|
53 53 %li
54   - - if project.public
55   - = public_icon
56   - - else
57   - = private_icon
  54 + = visibility_level_icon(project.visibility_level)
58 55 = link_to project.name_with_namespace, project
59 56 .pull-right
60 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 143 %td.permission-x &#10003;
144 144 %td.permission-x &#10003;
145 145 %tr
146   - %td Switch public mode
  146 + %td Switch visibility level
147 147 %td
148 148 %td
149 149 %td
... ...
app/views/help/public_access.html.haml
... ... @@ -2,14 +2,20 @@
2 2 %h3.page-title Public Access
3 3  
4 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 10 %em without any
8 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 17 %ol
12 18 %li Go to your project dashboard
13 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 @@
  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 32 \ No newline at end of file
... ...
app/views/projects/_visibility_level.html.haml 0 → 100644
... ... @@ -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 24 \ No newline at end of file
... ...
app/views/projects/edit.html.haml
... ... @@ -29,22 +29,7 @@
29 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 34 %fieldset.features
50 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 3 - if @project.import? && !@project.imported
7 4 .save-project-loader
... ...
app/views/projects/new.html.haml
... ... @@ -47,12 +47,7 @@
47 47 %span.light (optional)
48 48 .controls
49 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 52 .form-actions
58 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 3 .row
32 4 .span9
... ...
app/views/public/projects/index.html.haml
... ... @@ -19,6 +19,10 @@
19 19 %h4
20 20 = link_to project_path(project) do
21 21 = project.name_with_namespace
  22 + - if project.internal?
  23 + %small.access-icon
  24 + = internal_icon
  25 + Internal
22 26 .pull-right
23 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 55 # default: false - Account passwords are not sent via the email if signup is enabled.
56 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 62 ## Automatic issue closing
59 63 # If a commit message matches this regular expression, all issues referenced from the matched text will be closed.
60 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 72 wiki: true
69 73 wall: false
70 74 snippets: false
71   - public: false
  75 + visibility_level: "private" # can be "private" | "internal" | "public"
72 76  
73 77 ## External issues trackers
74 78 issues_tracker:
... ...
config/initializers/1_settings.rb
... ... @@ -30,6 +30,29 @@ class Settings &lt; Settingslogic
30 30 gitlab.relative_url_root
31 31 ].join('')
32 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 56 end
34 57 end
35 58  
... ... @@ -68,6 +91,7 @@ rescue ArgumentError # no user configured
68 91 '/home/' + Settings.gitlab['user']
69 92 end
70 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 95 Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil?
72 96 Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil?
73 97 Settings.gitlab['default_projects_features'] ||= {}
... ... @@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features[&#39;merge_requests&#39;] = true if Settings.g
76 100 Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil?
77 101 Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil?
78 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 106 # Gravatar
... ...
db/migrate/20131112220935_add_visibility_level_to_projects.rb 0 → 100644
... ... @@ -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
... ...
db/schema.rb
... ... @@ -11,7 +11,7 @@
11 11 #
12 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 16 create_table "broadcast_messages", :force => true do |t|
17 17 t.text "message", :null => false
... ... @@ -185,13 +185,13 @@ ActiveRecord::Schema.define(:version =&gt; 20131112114325) do
185 185 t.boolean "merge_requests_enabled", :default => true, :null => false
186 186 t.boolean "wiki_enabled", :default => true, :null => false
187 187 t.integer "namespace_id"
188   - t.boolean "public", :default => false, :null => false
189 188 t.string "issues_tracker", :default => "gitlab", :null => false
190 189 t.string "issues_tracker_id"
191 190 t.boolean "snippets_enabled", :default => true, :null => false
192 191 t.datetime "last_activity_at"
193 192 t.boolean "imported", :default => false, :null => false
194 193 t.string "import_url"
  194 + t.integer "visibility_level", :default => 0, :null => false
195 195 end
196 196  
197 197 add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id"
... ...
doc/api/projects.md
... ... @@ -15,6 +15,7 @@ GET /projects
15 15 "description": null,
16 16 "default_branch": "master",
17 17 "public": false,
  18 + "visibility_level": 0,
18 19 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git",
19 20 "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git",
20 21 "web_url": "http://example.com/diaspora/diaspora-client",
... ... @@ -49,6 +50,7 @@ GET /projects
49 50 "description": null,
50 51 "default_branch": "master",
51 52 "public": false,
  53 + "visibility_level": 0,
52 54 "ssh_url_to_repo": "git@example.com:brightbox/puppet.git",
53 55 "http_url_to_repo": "http://example.com/brightbox/puppet.git",
54 56 "web_url": "http://example.com/brightbox/puppet",
... ... @@ -117,6 +119,7 @@ Parameters:
117 119 "description": null,
118 120 "default_branch": "master",
119 121 "public": false,
  122 + "visibility_level": 0,
120 123 "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git",
121 124 "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git",
122 125 "web_url": "http://example.com/diaspora/diaspora-project-site",
... ... @@ -234,7 +237,8 @@ Parameters:
234 237 + `merge_requests_enabled` (optional)
235 238 + `wiki_enabled` (optional)
236 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 244 ### Create project for user
... ... @@ -256,7 +260,8 @@ Parameters:
256 260 + `merge_requests_enabled` (optional)
257 261 + `wiki_enabled` (optional)
258 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 267 ## Remove project
... ...
features/public/public_projects.feature
1 1 Feature: Public Projects Feature
2 2 Background:
3 3 Given public project "Community"
  4 + And internal project "Internal"
4 5 And private project "Enterprise"
5 6  
6 7 Scenario: I visit public area
7 8 When I visit the public projects area
8 9 Then I should see project "Community"
  10 + And I should not see project "Internal"
9 11 And I should not see project "Enterprise"
10 12  
11 13 Scenario: I visit public project page
12 14 When I visit project "Community" page
13 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 25 Scenario: I visit an empty public project page
16 26 Given public empty project "Empty Public Project"
17 27 When I visit empty project page
18 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 1 class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps
  2 + include SharedAuthentication
2 3 include SharedPaths
  4 + include SharedProject
3 5  
4 6 step 'I should see project "Community"' do
5 7 page.should have_content "Community"
... ... @@ -23,11 +25,11 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps
23 25 end
24 26  
25 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 29 end
28 30  
29 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 33 end
32 34  
33 35 step 'I visit empty project page' do
... ... @@ -48,16 +50,38 @@ class Spinach::Features::PublicProjectsFeature &lt; Spinach::FeatureSteps
48 50 create :project, name: 'Enterprise'
49 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 58 step 'I should see project "Community" home page' do
52 59 within '.project-home-title' do
53 60 page.should have_content 'Community'
54 61 end
55 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 85 end
62 86 end
63 87  
... ...
lib/api/entities.rb
... ... @@ -31,11 +31,13 @@ module API
31 31 end
32 32  
33 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 37 expose :owner, using: Entities::UserBasic
36 38 expose :name, :name_with_namespace
37 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 41 expose :namespace
40 42 expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? }
41 43 end
... ...
lib/api/projects.rb
... ... @@ -11,6 +11,13 @@ module API
11 11 end
12 12 not_found!
13 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 21 end
15 22  
16 23 # Get a projects list for authenticated user
... ... @@ -76,7 +83,8 @@ module API
76 83 # wiki_enabled (optional)
77 84 # snippets_enabled (optional)
78 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 88 # Example Request
81 89 # POST /projects
82 90 post do
... ... @@ -90,7 +98,9 @@ module API
90 98 :wiki_enabled,
91 99 :snippets_enabled,
92 100 :namespace_id,
93   - :public]
  101 + :public,
  102 + :visibility_level]
  103 + attrs = map_public_to_visibility_level(attrs)
94 104 @project = ::Projects::CreateContext.new(current_user, attrs).execute
95 105 if @project.saved?
96 106 present @project, with: Entities::Project
... ... @@ -114,7 +124,8 @@ module API
114 124 # merge_requests_enabled (optional)
115 125 # wiki_enabled (optional)
116 126 # snippets_enabled (optional)
117   - # public (optional)
  127 + # public (optional) - if true same as setting visibility_level = 20
  128 + # visibility_level (optional)
118 129 # Example Request
119 130 # POST /projects/user/:user_id
120 131 post "user/:user_id" do
... ... @@ -128,7 +139,9 @@ module API
128 139 :merge_requests_enabled,
129 140 :wiki_enabled,
130 141 :snippets_enabled,
131   - :public]
  142 + :public,
  143 + :visibility_level]
  144 + attrs = map_public_to_visibility_level(attrs)
132 145 @project = ::Projects::CreateContext.new(user, attrs).execute
133 146 if @project.saved?
134 147 present @project, with: Entities::Project
... ... @@ -290,7 +303,8 @@ module API
290 303 # GET /projects/search/:query
291 304 get "/search/:query" do
292 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 308 present paginate(projects), with: Entities::Project
295 309 end
296 310 end
... ...
lib/gitlab/backend/grack_auth.rb
... ... @@ -58,7 +58,7 @@ module Grack
58 58 end
59 59  
60 60 else
61   - return unauthorized unless project.public
  61 + return unauthorized unless project.public?
62 62 end
63 63  
64 64 if authorized_git_request?
... ... @@ -80,7 +80,7 @@ module Grack
80 80 def authorize_request(service)
81 81 case service
82 82 when 'git-upload-pack'
83   - project.public || can?(user, :download_code, project)
  83 + can?(user, :download_code, project)
84 84 when'git-receive-pack'
85 85 refs.each do |ref|
86 86 action = if project.protected_branch?(ref)
... ...
lib/gitlab/visibility_level.rb 0 → 100644
... ... @@ -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 7 describe :create_by_user do
8 8 before do
9 9 @user = create :user
  10 + @admin = create :user, admin: true
10 11 @opts = {
11 12 name: "GitLab",
12 13 namespace: @user.namespace
... ... @@ -37,7 +38,7 @@ describe Projects::CreateContext do
37 38 it { @project.namespace.should == @group }
38 39 end
39 40  
40   - context 'respect configured public setting' do
  41 + context 'respect configured visibility setting' do
41 42 before(:each) do
42 43 @settings = double("settings")
43 44 @settings.stub(:issues) { true }
... ... @@ -46,25 +47,90 @@ describe Projects::CreateContext do
46 47 @settings.stub(:wall) { true }
47 48 @settings.stub(:snippets) { true }
48 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 53 Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings)
50 54 end
51 55  
52 56 context 'should be public when setting is public' do
53 57 before do
54   - @settings.stub(:public) { true }
  58 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC }
55 59 @project = create_project(@user, @opts)
56 60 end
57 61  
58   - it { @project.public.should be_true }
  62 + it { @project.public?.should be_true }
59 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 66 before do
63   - @settings.stub(:public) { false }
  67 + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE }
64 68 @project = create_project(@user, @opts)
65 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 134 end
69 135 end
70 136 end
... ... @@ -73,3 +139,4 @@ describe Projects::CreateContext do
73 139 Projects::CreateContext.new(user, opts).execute
74 140 end
75 141 end
  142 +
... ...
spec/contexts/projects_update_context_spec.rb 0 → 100644
... ... @@ -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 112 \ No newline at end of file
... ...
spec/contexts/search_context_spec.rb
... ... @@ -3,23 +3,39 @@ require &#39;spec_helper&#39;
3 3 describe SearchContext do
4 4 let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') }
5 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 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 19 describe '#execute' do
15 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 22 results = context.execute
18 23 results[:projects].should == [found_project, public_project]
19 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 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 39 results = context.execute
24 40 results[:projects].should == [found_project]
25 41 end
... ...
spec/features/security/project/internal_access_spec.rb 0 → 100644
... ... @@ -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 15 project.team << [reporter, :reporter]
16 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 24 describe "GET /:project_path" do
19 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 9  
10 10 before do
11 11 # public project
12   - project.public = true
  12 + project.visibility_level = Gitlab::VisibilityLevel::PUBLIC
13 13 project.save!
14 14  
15 15 # full access
... ...
spec/models/project_spec.rb
... ... @@ -14,13 +14,13 @@
14 14 # merge_requests_enabled :boolean default(TRUE), not null
15 15 # wiki_enabled :boolean default(TRUE), not null
16 16 # namespace_id :integer
17   -# public :boolean default(FALSE), not null
18 17 # issues_tracker :string(255) default("gitlab"), not null
19 18 # issues_tracker_id :string(255)
20 19 # snippets_enabled :boolean default(TRUE), not null
21 20 # last_activity_at :datetime
22 21 # imported :boolean default(FALSE), not null
23 22 # import_url :string(255)
  23 +# visibility_level :integer default(0), not null
24 24 #
25 25  
26 26 require 'spec_helper'
... ...
spec/requests/api/projects_spec.rb
... ... @@ -132,15 +132,45 @@ describe API::API do
132 132 end
133 133  
134 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 142 project = attributes_for(:project, { public: true })
136 143 post api("/projects", user), project
137 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 160 end
139 161  
140 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 170 project = attributes_for(:project, { public: false })
142 171 post api("/projects", user), project
143 172 json_response['public'].should be_false
  173 + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE
144 174 end
145 175 end
146 176  
... ... @@ -183,19 +213,46 @@ describe API::API do
183 213 end
184 214  
185 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 223 project = attributes_for(:project, { public: true })
187 224 post api("/projects/user/#{user.id}", admin), project
188 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 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 238 post api("/projects/user/#{user.id}", admin), project
195 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 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 256 end
200 257  
201 258 describe "GET /projects/:id" do
... ... @@ -649,10 +706,10 @@ describe API::API do
649 706  
650 707 describe :fork_admin do
651 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 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 714 it "shouldn't available for non admin users" do
658 715 post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user)
... ... @@ -721,8 +778,10 @@ describe API::API do
721 778 let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) }
722 779 let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) }
723 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 786 context "when unauthenticated" do
728 787 it "should return authentication error" do
... ... @@ -736,7 +795,7 @@ describe API::API do
736 795 get api("/projects/search/#{query}",user)
737 796 response.status.should == 200
738 797 json_response.should be_an Array
739   - json_response.size.should == 5
  798 + json_response.size.should == 6
740 799 json_response.each {|project| project['name'].should =~ /.*query.*/}
741 800 end
742 801 end
... ... @@ -746,8 +805,8 @@ describe API::API do
746 805 get api("/projects/search/#{query}", user2)
747 806 response.status.should == 200
748 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 810 end
752 811 end
753 812 end
... ...