Commit d9bb4230cc3aa161876df821c34d8e9c53d2e7a6

Authored by Jason Hollingsworth
1 parent 51b5509b

Adding authenticated public mode (internal).

Added visibility_level icons to project view (rather than just text).
Added public projects to search results.
Added ability to restrict visibility levels standard users can set.
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.repository.root_ref
... ...
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
... ... @@ -451,4 +457,8 @@ class Project &lt; ActiveRecord::Base
451 457 def default_branch
452 458 @default_branch ||= repository.root_ref if repository.exists?
453 459 end
  460 +
  461 + def visibility_level_field
  462 + visibility_level
  463 + end
454 464 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
... ...