diff options
author | Jason Hollingsworth <jhworth.developer@gmail.com> | 2013-11-06 19:13:21 +0400 |
---|---|---|
committer | Jason Hollingsworth <jhworth.developer@gmail.com> | 2013-11-27 08:22:07 +0400 |
commit | d9bb4230cc3aa161876df821c34d8e9c53d2e7a6 (patch) | |
tree | d9923bbf4ebe3f6cf96f71cdcfe4c07cc94b6752 | |
parent | 51b5509bacdfba1d3ca84a4b56c6bd21942f1d2e (diff) |
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.
50 files changed, 955 insertions, 156 deletions
diff --git a/app/assets/stylesheets/common.scss b/app/assets/stylesheets/common.scss index 935d24e58ff..ce897292c37 100644 --- a/app/assets/stylesheets/common.scss +++ b/app/assets/stylesheets/common.scss @@ -365,6 +365,10 @@ table { &.input-large { width: 210px; } + + &.input-clamp { + max-width: 100%; + } } .user-result { diff --git a/app/assets/stylesheets/gitlab_bootstrap/common.scss b/app/assets/stylesheets/gitlab_bootstrap/common.scss index b5fd1fce30b..26fe02e4928 100644 --- a/app/assets/stylesheets/gitlab_bootstrap/common.scss +++ b/app/assets/stylesheets/gitlab_bootstrap/common.scss @@ -6,6 +6,7 @@ .cblue { color: #29A } .cblack { color: #111 } .cdark { color: #444 } +.camber { color: #ffc000 } .cwhite { color: #fff!important } .bgred { background: #F2DEDE!important } diff --git a/app/assets/stylesheets/sections/admin.scss b/app/assets/stylesheets/sections/admin.scss index 82556e91da3..8ad9bc732b2 100644 --- a/app/assets/stylesheets/sections/admin.scss +++ b/app/assets/stylesheets/sections/admin.scss @@ -20,6 +20,15 @@ label { width: 110px; } .controls { margin-left: 130px; } .form-actions { padding-left: 130px; background: #fff } + .visibility-levels { + .controls { + margin-bottom: 9px; + } + + i { + color: inherit; + } + } } .broadcast-messages { diff --git a/app/assets/stylesheets/sections/projects.scss b/app/assets/stylesheets/sections/projects.scss index e9162b3c167..22bd6fb1807 100644 --- a/app/assets/stylesheets/sections/projects.scss +++ b/app/assets/stylesheets/sections/projects.scss @@ -18,6 +18,12 @@ border-bottom: 1px solid #DDD; padding-bottom: 25px; margin-bottom: 30px; + + &.empty-project { + border-bottom: 0px; + padding-bottom: 15px; + margin-bottom: 0px; + } .project-home-title { font-size: 18px; @@ -45,7 +51,7 @@ } } - .public-label { + .visibility-level-label { font-size: 14px; background: #f1f1f1; padding: 8px 10px; @@ -53,6 +59,10 @@ margin-left: 10px; color: #888; text-shadow: 0 1px 1px #FFF; + + i { + color: inherit; + } } } @@ -87,9 +97,33 @@ } } -.project-public-holder { - .help-inline { - padding-top: 7px; +.project-visibility-level-holder { + .controls { + padding-bottom: 9px; + } + + .controls { + input { + float: left; + } + .descr { + display: block; + margin-left: 1.5em; + &.restricted { + color: #888; + } + } + .info { + display: block; + margin-top: 5px; + } + strong { + display: inline-block; + width: 4em; + } + } + i { + color: inherit; } } @@ -130,7 +164,8 @@ ul.nav.nav-projects-tabs { margin: 0px; } -.my-projects { +.my-projects, +.public-projects { li { .project-info { margin-bottom: 10px; diff --git a/app/contexts/projects/create_context.rb b/app/contexts/projects/create_context.rb index 904c60a0f23..2acb9fbfe14 100644 --- a/app/contexts/projects/create_context.rb +++ b/app/contexts/projects/create_context.rb @@ -8,6 +8,11 @@ module Projects # get namespace id namespace_id = params.delete(:namespace_id) + # check that user is allowed to set specified visibility_level + unless Gitlab::VisibilityLevel.allowed_for?(current_user, params[:visibility_level]) + params.delete(:visibility_level) + end + # Load default feature settings default_features = Gitlab.config.gitlab.default_projects_features @@ -17,7 +22,7 @@ module Projects wall_enabled: default_features.wall, snippets_enabled: default_features.snippets, merge_requests_enabled: default_features.merge_requests, - public: default_features.public + visibility_level: default_features.visibility_level }.stringify_keys @project = Project.new(default_opts.merge(params)) diff --git a/app/contexts/projects/update_context.rb b/app/contexts/projects/update_context.rb index 9564dd94688..27d7a95724a 100644 --- a/app/contexts/projects/update_context.rb +++ b/app/contexts/projects/update_context.rb @@ -2,7 +2,11 @@ module Projects class UpdateContext < BaseContext def execute(role = :default) params[:project].delete(:namespace_id) - params[:project].delete(:public) unless can?(current_user, :change_public_mode, project) + # check that user is allowed to set specified visibility_level + unless can?(current_user, :change_visibility_level, project) && Gitlab::VisibilityLevel.allowed_for?(current_user, params[:project][:visibility_level]) + params[:project].delete(:visibility_level) + end + new_branch = params[:project].delete(:default_branch) if project.repository.exists? && new_branch != project.repository.root_ref diff --git a/app/contexts/search_context.rb b/app/contexts/search_context.rb index 2f9438f6bb4..5985ab1fb0c 100644 --- a/app/contexts/search_context.rb +++ b/app/contexts/search_context.rb @@ -1,8 +1,8 @@ class SearchContext - attr_accessor :project_ids, :params + attr_accessor :project_ids, :current_user, :params - def initialize(project_ids, params) - @project_ids, @params = project_ids, params.dup + def initialize(project_ids, user, params) + @project_ids, @current_user, @params = project_ids, user, params.dup end def execute @@ -10,7 +10,8 @@ class SearchContext query = Shellwords.shellescape(query) if query.present? return result unless query.present? - result[:projects] = Project.where("projects.id in (?) OR projects.public = true", project_ids).search(query).limit(20) + visibility_levels = @current_user ? [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] : [ Gitlab::VisibilityLevel::PUBLIC ] + result[:projects] = Project.where("projects.id in (?) OR projects.visibility_level in (?)", project_ids, visibility_levels).search(query).limit(20) # Search inside single project single_project_search(Project.where(id: project_ids), query) diff --git a/app/controllers/admin/projects_controller.rb b/app/controllers/admin/projects_controller.rb index 4de0a65b5fe..0e8335f3d8b 100644 --- a/app/controllers/admin/projects_controller.rb +++ b/app/controllers/admin/projects_controller.rb @@ -8,7 +8,7 @@ class Admin::ProjectsController < Admin::ApplicationController user = User.find_by_id(owner_id) @projects = user ? user.owned_projects : Project.scoped - @projects = @projects.where(public: true) if params[:public_only].present? + @projects = @projects.where("visibility_level IN (?)", params[:visibility_levels]) if params[:visibility_levels].present? @projects = @projects.with_push if params[:with_push].present? @projects = @projects.abandoned if params[:abandoned].present? @projects = @projects.search(params[:name]) if params[:name].present? diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index cfa3cac5e88..68ea636ccfe 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -102,7 +102,7 @@ class ApplicationController < ActionController::Base end def authorize_code_access! - return access_denied! unless can?(current_user, :download_code, project) or project.public? + return access_denied! unless can?(current_user, :download_code, project) end def authorize_push! diff --git a/app/controllers/projects/application_controller.rb b/app/controllers/projects/application_controller.rb index 80aeb5cd6cc..7e4580017dd 100644 --- a/app/controllers/projects/application_controller.rb +++ b/app/controllers/projects/application_controller.rb @@ -10,7 +10,7 @@ class Projects::ApplicationController < ApplicationController id = params[:project_id] || params[:id] @project = Project.find_with_namespace(id) - return if @project && @project.public + return if @project && @project.public? end super diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 66d6edd810c..60003187a9d 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -55,7 +55,7 @@ class ProjectsController < ApplicationController end def show - return authenticate_user! unless @project.public || current_user + return authenticate_user! unless @project.public? || current_user limit = (params[:limit] || 20).to_i @events = @project.events.recent diff --git a/app/controllers/public/projects_controller.rb b/app/controllers/public/projects_controller.rb index 87e903a1d2d..8d66250d7b6 100644 --- a/app/controllers/public/projects_controller.rb +++ b/app/controllers/public/projects_controller.rb @@ -6,7 +6,7 @@ class Public::ProjectsController < ApplicationController layout 'public' def index - @projects = Project.public_only + @projects = Project.public_or_internal_only(current_user) @projects = @projects.search(params[:search]) if params[:search].present? @projects = @projects.includes(:namespace).order("namespaces.path, projects.name ASC").page(params[:page]).per(20) end diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f5c3bb133ed..2a2748dc1fb 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -14,7 +14,7 @@ class SearchController < ApplicationController project_ids.select! { |id| id == project_id.to_i} end - result = SearchContext.new(project_ids, params).execute + result = SearchContext.new(project_ids, current_user, params).execute @projects = result[:projects] @merge_requests = result[:merge_requests] diff --git a/app/helpers/icons_helper.rb b/app/helpers/icons_helper.rb index e4dfc236ca1..1688cfc40b1 100644 --- a/app/helpers/icons_helper.rb +++ b/app/helpers/icons_helper.rb @@ -11,6 +11,10 @@ module IconsHelper content_tag :i, nil, class: 'icon-globe cblue' end + def internal_icon + content_tag :i, nil, class: 'icon-shield camber' + end + def private_icon content_tag :i, nil, class: 'icon-lock cgreen' end diff --git a/app/helpers/search_helper.rb b/app/helpers/search_helper.rb index 33c5e4fb9db..8ff0bc67b71 100644 --- a/app/helpers/search_helper.rb +++ b/app/helpers/search_helper.rb @@ -1,10 +1,10 @@ module SearchHelper def search_autocomplete_source return unless current_user - [ groups_autocomplete, projects_autocomplete, + public_projects_autocomplete, default_autocomplete, project_autocomplete, help_autocomplete @@ -75,4 +75,11 @@ module SearchHelper { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } end end + + # Autocomplete results for the current user's projects + def public_projects_autocomplete + Project.public_or_internal_only(current_user).map do |p| + { label: "project: #{simple_sanitize(p.name_with_namespace)}", url: project_path(p) } + end + end end diff --git a/app/helpers/visibility_level_helper.rb b/app/helpers/visibility_level_helper.rb new file mode 100644 index 00000000000..5cec7f8b4e6 --- /dev/null +++ b/app/helpers/visibility_level_helper.rb @@ -0,0 +1,55 @@ +module VisibilityLevelHelper + def visibility_level_color(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + 'cgreen' + when Gitlab::VisibilityLevel::INTERNAL + 'camber' + when Gitlab::VisibilityLevel::PUBLIC + 'cblue' + end + end + + def visibility_level_description(level) + capture_haml do + haml_tag :span do + case level + when Gitlab::VisibilityLevel::PRIVATE + haml_concat "Project access must be granted explicitly for each user." + when Gitlab::VisibilityLevel::INTERNAL + haml_concat "The project can be cloned by" + haml_tag :em, "any logged in user." + haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path} for logged in users." + haml_tag :em, "Any logged in user" + haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository." + when Gitlab::VisibilityLevel::PUBLIC + haml_concat "The project can be cloned" + haml_tag :em, "without any" + haml_concat "authentication." + haml_concat "It will also be listed on the #{link_to "public access directory", public_root_path}." + haml_tag :em, "Any logged in user" + haml_concat "will have #{link_to "Guest", help_permissions_path} permissions on the repository." + end + end + end + end + + def visibility_level_icon(level) + case level + when Gitlab::VisibilityLevel::PRIVATE + private_icon + when Gitlab::VisibilityLevel::INTERNAL + internal_icon + when Gitlab::VisibilityLevel::PUBLIC + public_icon + end + end + + def visibility_level_label(level) + Project.visibility_levels.key(level) + end + + def restricted_visibility_levels + current_user.is_admin? ? [] : gitlab_config.restricted_visibility_levels + end +end
\ No newline at end of file diff --git a/app/models/ability.rb b/app/models/ability.rb index 85476089145..6df56eed5b8 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -29,7 +29,7 @@ class Ability nil end - if project && project.public + if project && project.public? [ :read_project, :read_wiki, @@ -71,7 +71,7 @@ class Ability rules << project_guest_rules end - if project.public? + if project.public? || project.internal? rules << public_project_rules end @@ -89,7 +89,7 @@ class Ability def public_project_rules project_guest_rules + [ :download_code, - :fork_project, + :fork_project ] end @@ -145,7 +145,7 @@ class Ability def project_admin_rules project_master_rules + [ :change_namespace, - :change_public_mode, + :change_visibility_level, :rename_project, :remove_project ] diff --git a/app/models/project.rb b/app/models/project.rb index eab7c14d6c6..9ac078fcfa6 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -14,24 +14,25 @@ # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer -# public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) # snippets_enabled :boolean default(TRUE), not null # last_activity_at :datetime # imported :boolean default(FALSE), not null # import_url :string(255) +# visibility_level :integer default(0), not null # class Project < ActiveRecord::Base include Gitlab::ShellAdapter + include Gitlab::VisibilityLevel extend Enumerize ActsAsTaggableOn.strict_case_match = true attr_accessible :name, :path, :description, :issues_tracker, :label_list, :issues_enabled, :wall_enabled, :merge_requests_enabled, :snippets_enabled, :issues_tracker_id, - :wiki_enabled, :public, :import_url, :last_activity_at, as: [:default, :admin] + :wiki_enabled, :visibility_level, :import_url, :last_activity_at, as: [:default, :admin] attr_accessible :namespace_id, :creator_id, as: :admin @@ -108,7 +109,8 @@ class Project < ActiveRecord::Base scope :sorted_by_activity, -> { reorder("projects.last_activity_at DESC") } scope :personal, ->(user) { where(namespace_id: user.namespace_id) } scope :joined, ->(user) { where("namespace_id != ?", user.namespace_id) } - scope :public_only, -> { where(public: true) } + scope :public_only, -> { where(visibility_level: PUBLIC) } + scope :public_or_internal_only, ->(user) { where("visibility_level IN (:levels)", levels: user ? [ INTERNAL, PUBLIC ] : [ PUBLIC ]) } enumerize :issues_tracker, in: (Gitlab.config.issues_tracker.keys).append(:gitlab), default: :gitlab @@ -140,6 +142,10 @@ class Project < ActiveRecord::Base where(path: id, namespace_id: nil).last end end + + def visibility_levels + Gitlab::VisibilityLevel.options + end end def team @@ -451,4 +457,8 @@ class Project < ActiveRecord::Base def default_branch @default_branch ||= repository.root_ref if repository.exists? end + + def visibility_level_field + visibility_level + end end diff --git a/app/views/admin/projects/index.html.haml b/app/views/admin/projects/index.html.haml index bc297209ae5..05236e320b5 100644 --- a/app/views/admin/projects/index.html.haml +++ b/app/views/admin/projects/index.html.haml @@ -10,11 +10,15 @@ .control-group = label_tag :owner_id, 'Owner:', class: 'control-label' .controls - = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large' - .control-group - = label_tag :public_only, 'Public Only', class: 'control-label' - .controls - = check_box_tag :public_only, 1, params[:public_only] + = users_select_tag :owner_id, selected: params[:owner_id], class: 'input-large input-clamp' + .control-group.visibility-levels + = label_tag :visibility_level, 'Visibility Levels', class: 'control-label' + - Project.visibility_levels.each do |label, level| + .controls + = check_box_tag 'visibility_levels[]', level, params[:visibility_levels].present? && params[:visibility_levels].include?(level.to_s) + %span.descr + = visibility_level_icon(level) + = label .control-group = label_tag :with_push, 'Not empty', class: 'control-label' .controls @@ -42,10 +46,7 @@ %ul.well-list - @projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, [:admin, project] .pull-right = link_to 'Edit', edit_project_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml index c9c9c38d9f5..42c427aad63 100644 --- a/app/views/admin/projects/show.html.haml +++ b/app/views/admin/projects/show.html.haml @@ -66,14 +66,10 @@ %li %span.light access: %strong - - if @project.public - %span.cblue - %i.icon-share - Public - - else - %span.cgreen - %i.icon-lock - Private + %span{ class: visibility_level_color(@project.visibility_level) } + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) + .ui-box .title Transfer project @@ -88,9 +84,6 @@ .controls = f.submit 'Transfer', class: 'btn btn-primary' - - - .span6 - if @group .ui-box diff --git a/app/views/dashboard/projects.html.haml b/app/views/dashboard/projects.html.haml index 904ac2d00a2..5ac90593c3a 100644 --- a/app/views/dashboard/projects.html.haml +++ b/app/views/dashboard/projects.html.haml @@ -58,10 +58,10 @@ %h4.project-title = link_to project_path(project), class: dom_class(project) do = project.name_with_namespace - - if project.public + - unless project.private? %small.access-icon - = public_icon - Public + = visibility_level_icon(project.visibility_level) + = visibility_level_label(project.visibility_level) - if current_user.can_leave_project?(project) .pull-right diff --git a/app/views/groups/edit.html.haml b/app/views/groups/edit.html.haml index 10b974ea222..5b5f8a20c19 100644 --- a/app/views/groups/edit.html.haml +++ b/app/views/groups/edit.html.haml @@ -51,10 +51,7 @@ %ul.well-list - @group.projects.each do |project| %li - - if project.public - = public_icon - - else - = private_icon + = visibility_level_icon(project.visibility_level) = link_to project.name_with_namespace, project .pull-right = link_to 'Members', project_team_index_path(project), id: "edit_#{dom_id(project)}", class: "btn btn-small" diff --git a/app/views/help/permissions.html.haml b/app/views/help/permissions.html.haml index df35f41fc90..15e3bf3a135 100644 --- a/app/views/help/permissions.html.haml +++ b/app/views/help/permissions.html.haml @@ -143,7 +143,7 @@ %td.permission-x ✓ %td.permission-x ✓ %tr - %td Switch public mode + %td Switch visibility level %td %td %td diff --git a/app/views/help/public_access.html.haml b/app/views/help/public_access.html.haml index c67402ee319..3a8aedc780e 100644 --- a/app/views/help/public_access.html.haml +++ b/app/views/help/public_access.html.haml @@ -2,14 +2,20 @@ %h3.page-title Public Access %p - GitLab allows you to open selected projects to be accessed publicly. - These projects will be cloneable + GitLab allows you to open selected projects to be accessed publicly or internally. + 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. + %p + = public_icon + Public projects will be cloneable %em without any authentication. - Also they will be listed on the #{link_to "public access directory", public_root_path}. + %p + = internal_icon + Internal projects will be cloneable by + %em any authenticated user. %ol %li Go to your project dashboard %li Click on the "Edit" tab - %li Select "Public clone access" + %li Change "Visibility Level" diff --git a/app/views/projects/_home_panel.html.haml b/app/views/projects/_home_panel.html.haml new file mode 100644 index 00000000000..19c150b54fb --- /dev/null +++ b/app/views/projects/_home_panel.html.haml @@ -0,0 +1,31 @@ +- empty_repo = @project.empty_repo? +.project-home-panel{:class => ("empty-project" if empty_repo)} + .row + .span5 + %h4.project-home-title + = @project.name_with_namespace + %span.visibility-level-label + = visibility_level_icon(@project.visibility_level) + = visibility_level_label(@project.visibility_level) + + .span7 + - unless empty_repo + .project-home-dropdown + = render "dropdown" + .form-horizontal + = render "shared/clone_panel" + + .project-home-extra.clearfix + .project-home-desc + - if @project.description.present? + = @project.description + - if can?(current_user, :admin_project, @project) + – + %strong= link_to 'Edit', edit_project_path + + - unless empty_repo + .project-home-links + = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref) + = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project) + = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project) + %span.light.prepend-left-20= repository_size
\ No newline at end of file diff --git a/app/views/projects/_visibility_level.html.haml b/app/views/projects/_visibility_level.html.haml new file mode 100644 index 00000000000..1bce3ba8cda --- /dev/null +++ b/app/views/projects/_visibility_level.html.haml @@ -0,0 +1,23 @@ +.control-group.project-visibility-level-holder + = f.label :visibility_level, "Visibility Level" + - if can_change_visibility_level + - Gitlab::VisibilityLevel.values.each do |level| + - restricted = restricted_visibility_levels.include?(level) + .controls + = f.radio_button :visibility_level, level, checked: (visibility_level == level), disabled: restricted + %span.descr{:class => ("restricted" if restricted)} + = visibility_level_icon(level) + %strong + = visibility_level_label(level) + = visibility_level_description(level) + - unless restricted_visibility_levels.empty? + .controls + %span.info + Some visibility level settings have been restricted by the administrator. + - else + .controls + %span.info + = visibility_level_icon(visibility_level) + %strong + = visibility_level_label(visibility_level) + = visibility_level_description(visibility_level)
\ No newline at end of file diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 3afc607d90f..d282fc626e1 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -29,22 +29,7 @@ .controls= f.select(:default_branch, @repository.branch_names, {}, {class: 'chosen'}) - - if can?(current_user, :change_public_mode, @project) - %fieldset.public-mode - %legend - Public mode: - .control-group - = f.label :public, class: 'control-label' do - %span Public access - .controls - = f.check_box :public - %span.descr - If checked, this project can be cloned - %em without any - authentication. - It will also be listed on the #{link_to "public access directory", public_root_path}. - %em Any - user will have #{link_to "Guest", help_permissions_path} permissions on the repository. + = render "visibility_level", f: f, visibility_level: @project.visibility_level, can_change_visibility_level: can?(current_user, :change_visibility_level, @project) %fieldset.features %legend diff --git a/app/views/projects/empty.html.haml b/app/views/projects/empty.html.haml index 04fc0c31733..3ed22015c0b 100644 --- a/app/views/projects/empty.html.haml +++ b/app/views/projects/empty.html.haml @@ -1,7 +1,4 @@ -%h3.page-title - = @project.name_with_namespace - .form-horizontal.pull-right - = render "shared/clone_panel" += render "home_panel" - if @project.import? && !@project.imported .save-project-loader diff --git a/app/views/projects/new.html.haml b/app/views/projects/new.html.haml index 466a63dee83..ee6c42b6ea8 100644 --- a/app/views/projects/new.html.haml +++ b/app/views/projects/new.html.haml @@ -47,12 +47,7 @@ %span.light (optional) .controls = f.text_area :description, placeholder: "Awesome project", class: "input-xlarge", rows: 3, maxlength: 250, tabindex: 3 - .control-group.project-public-holder - = f.label :public do - %span Public project - .controls - = f.check_box :public, { checked: gitlab_config.default_projects_features.public }, true, false - %span.help-inline Make project visible to everyone + = render "visibility_level", f: f, visibility_level: gitlab_config.default_projects_features.visibility_level, can_change_visibility_level: true .form-actions = f.submit 'Create project', class: "btn btn-create project-submit", tabindex: 4 diff --git a/app/views/projects/show.html.haml b/app/views/projects/show.html.haml index 1f4b5839175..41035d91756 100644 --- a/app/views/projects/show.html.haml +++ b/app/views/projects/show.html.haml @@ -1,32 +1,4 @@ -.project-home-panel - .row - .span5 - %h4.project-home-title - = @project.name_with_namespace - - if @project.public - %span.public-label Public - - else - %span.public-label Private - - .span7 - .project-home-dropdown - = render "dropdown" - .form-horizontal - = render "shared/clone_panel" - - .project-home-extra.clearfix - .project-home-desc - - if @project.description.present? - = @project.description - - if can?(current_user, :admin_project, @project) - – - %strong= link_to 'Edit', edit_project_path - - .project-home-links - = link_to pluralize(@repository.round_commit_count, 'commit'), project_commits_path(@project, @ref || @repository.root_ref) - = link_to pluralize(@repository.branch_names.count, 'branch'), project_branches_path(@project) - = link_to pluralize(@repository.tag_names.count, 'tag'), project_tags_path(@project) - %span.light.prepend-left-20= repository_size += render "home_panel" .row .span9 diff --git a/app/views/public/projects/index.html.haml b/app/views/public/projects/index.html.haml index 21aee644579..b88169add3c 100644 --- a/app/views/public/projects/index.html.haml +++ b/app/views/public/projects/index.html.haml @@ -19,6 +19,10 @@ %h4 = link_to project_path(project) do = project.name_with_namespace + - if project.internal? + %small.access-icon + = internal_icon + Internal .pull-right %pre.public-clone git clone #{project.http_url_to_repo} diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 759d7434230..6b527cf0212 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -55,6 +55,10 @@ production: &base # default: false - Account passwords are not sent via the email if signup is enabled. # signup_enabled: true + # Restrict setting visibility levels for non-admin users. + # The default is to allow all levels. + #restricted_visibility_levels: [ "public" ] + ## Automatic issue closing # If a commit message matches this regular expression, all issues referenced from the matched text will be closed. # This happens when the commit is pushed or merged into the default branch of a project. @@ -68,7 +72,7 @@ production: &base wiki: true wall: false snippets: false - public: false + visibility_level: "private" # can be "private" | "internal" | "public" ## External issues trackers issues_tracker: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 942b77ffd2e..06e05714fdf 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -30,6 +30,29 @@ class Settings < Settingslogic gitlab.relative_url_root ].join('') end + + # check that values in `current` (string or integer) is a contant in `modul`. + def verify_constant_array(modul, current, default) + values = default || [] + if !current.nil? + values = [] + current.each do |constant| + values.push(verify_constant(modul, constant, nil)) + end + values.delete_if { |value| value.nil? } + end + values + end + + # check that `current` (string or integer) is a contant in `modul`. + def verify_constant(modul, current, default) + constant = modul.constants.find{ |name| modul.const_get(name) == current } + value = constant.nil? ? default : modul.const_get(constant) + if current.is_a? String + value = modul.const_get(current.upcase) rescue default + end + value + end end end @@ -68,6 +91,7 @@ rescue ArgumentError # no user configured '/home/' + Settings.gitlab['user'] end Settings.gitlab['signup_enabled'] ||= false +Settings.gitlab['restricted_visibility_levels'] = Settings.send(:verify_constant_array, Gitlab::VisibilityLevel, Settings.gitlab['restricted_visibility_levels'], []) Settings.gitlab['username_changing_enabled'] = true if Settings.gitlab['username_changing_enabled'].nil? Settings.gitlab['issue_closing_pattern'] = '([Cc]loses|[Ff]ixes) #(\d+)' if Settings.gitlab['issue_closing_pattern'].nil? Settings.gitlab['default_projects_features'] ||= {} @@ -76,7 +100,7 @@ Settings.gitlab.default_projects_features['merge_requests'] = true if Settings.g Settings.gitlab.default_projects_features['wiki'] = true if Settings.gitlab.default_projects_features['wiki'].nil? Settings.gitlab.default_projects_features['wall'] = false if Settings.gitlab.default_projects_features['wall'].nil? Settings.gitlab.default_projects_features['snippets'] = false if Settings.gitlab.default_projects_features['snippets'].nil? -Settings.gitlab.default_projects_features['public'] = false if Settings.gitlab.default_projects_features['public'].nil? +Settings.gitlab.default_projects_features['visibility_level'] = Settings.send(:verify_constant, Gitlab::VisibilityLevel, Settings.gitlab.default_projects_features['visibility_level'], Gitlab::VisibilityLevel::PRIVATE) # # Gravatar diff --git a/db/migrate/20131112220935_add_visibility_level_to_projects.rb b/db/migrate/20131112220935_add_visibility_level_to_projects.rb new file mode 100644 index 00000000000..cf1e9f912a0 --- /dev/null +++ b/db/migrate/20131112220935_add_visibility_level_to_projects.rb @@ -0,0 +1,13 @@ +class AddVisibilityLevelToProjects < ActiveRecord::Migration + def self.up + add_column :projects, :visibility_level, :integer, :default => 0, :null => false + Project.where(public: true).update_all(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + remove_column :projects, :public + end + + def self.down + add_column :projects, :public, :boolean, :default => false, :null => false + Project.where(visibility_level: Gitlab::VisibilityLevel::PUBLIC).update_all(public: true) + remove_column :projects, :visibility_level + end +end diff --git a/db/schema.rb b/db/schema.rb index a03e4713188..24d2fef4a19 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -11,7 +11,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20131112114325) do +ActiveRecord::Schema.define(:version => 20131112220935) do create_table "broadcast_messages", :force => true do |t| t.text "message", :null => false @@ -185,13 +185,13 @@ ActiveRecord::Schema.define(:version => 20131112114325) do t.boolean "merge_requests_enabled", :default => true, :null => false t.boolean "wiki_enabled", :default => true, :null => false t.integer "namespace_id" - t.boolean "public", :default => false, :null => false t.string "issues_tracker", :default => "gitlab", :null => false t.string "issues_tracker_id" t.boolean "snippets_enabled", :default => true, :null => false t.datetime "last_activity_at" t.boolean "imported", :default => false, :null => false t.string "import_url" + t.integer "visibility_level", :default => 0, :null => false end add_index "projects", ["creator_id"], :name => "index_projects_on_owner_id" diff --git a/doc/api/projects.md b/doc/api/projects.md index 9b5d62ac832..5ec4c4a74e5 100644 --- a/doc/api/projects.md +++ b/doc/api/projects.md @@ -15,6 +15,7 @@ GET /projects "description": null, "default_branch": "master", "public": false, + "visibility_level": 0, "ssh_url_to_repo": "git@example.com:diaspora/diaspora-client.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-client.git", "web_url": "http://example.com/diaspora/diaspora-client", @@ -49,6 +50,7 @@ GET /projects "description": null, "default_branch": "master", "public": false, + "visibility_level": 0, "ssh_url_to_repo": "git@example.com:brightbox/puppet.git", "http_url_to_repo": "http://example.com/brightbox/puppet.git", "web_url": "http://example.com/brightbox/puppet", @@ -117,6 +119,7 @@ Parameters: "description": null, "default_branch": "master", "public": false, + "visibility_level": 0, "ssh_url_to_repo": "git@example.com:diaspora/diaspora-project-site.git", "http_url_to_repo": "http://example.com/diaspora/diaspora-project-site.git", "web_url": "http://example.com/diaspora/diaspora-project-site", @@ -234,7 +237,8 @@ Parameters: + `merge_requests_enabled` (optional) + `wiki_enabled` (optional) + `snippets_enabled` (optional) -+ `public` (optional) ++ `public` (optional) - if `true` same as setting visibility_level = 20 ++ `visibility_level` (optional) ### Create project for user @@ -256,7 +260,8 @@ Parameters: + `merge_requests_enabled` (optional) + `wiki_enabled` (optional) + `snippets_enabled` (optional) -+ `public` (optional) ++ `public` (optional) - if `true` same as setting visibility_level = 20 ++ `visibility_level` (optional) ## Remove project diff --git a/features/public/public_projects.feature b/features/public/public_projects.feature index 178a769194c..86bb888fdb6 100644 --- a/features/public/public_projects.feature +++ b/features/public/public_projects.feature @@ -1,18 +1,40 @@ Feature: Public Projects Feature Background: Given public project "Community" + And internal project "Internal" And private project "Enterprise" Scenario: I visit public area When I visit the public projects area Then I should see project "Community" + And I should not see project "Internal" And I should not see project "Enterprise" Scenario: I visit public project page When I visit project "Community" page Then I should see project "Community" home page + Scenario: I visit internal project page + When I visit project "Internal" page + Then page status code should be 404 + + Scenario: I visit private project page + When I visit project "Enterprise" page + Then page status code should be 404 + Scenario: I visit an empty public project page Given public empty project "Empty Public Project" When I visit empty project page Then I should see empty public project details + + Scenario: I visit public area as user + Given I sign in as a user + When I visit the public projects area + Then I should see project "Community" + And I should see project "Internal" + And I should not see project "Enterprise" + + Scenario: I visit internal project page as user + Given I sign in as a user + When I visit project "Internal" page + Then I should see project "Internal" home page diff --git a/features/steps/public/projects_feature.rb b/features/steps/public/projects_feature.rb index e5292380c55..8b61eba3ffb 100644 --- a/features/steps/public/projects_feature.rb +++ b/features/steps/public/projects_feature.rb @@ -1,5 +1,7 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps + include SharedAuthentication include SharedPaths + include SharedProject step 'I should see project "Community"' do page.should have_content "Community" @@ -23,11 +25,11 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps end step 'public project "Community"' do - create :project_with_code, name: 'Community', public: true + create :project_with_code, name: 'Community', visibility_level: Gitlab::VisibilityLevel::PUBLIC end step 'public empty project "Empty Public Project"' do - create :project, name: 'Empty Public Project', public: true + create :project, name: 'Empty Public Project', visibility_level: Gitlab::VisibilityLevel::PUBLIC end step 'I visit empty project page' do @@ -48,16 +50,38 @@ class Spinach::Features::PublicProjectsFeature < Spinach::FeatureSteps create :project, name: 'Enterprise' end + step 'I visit project "Enterprise" page' do + project = Project.find_by_name('Enterprise') + visit project_path(project) + end + step 'I should see project "Community" home page' do within '.project-home-title' do page.should have_content 'Community' end end - private + step 'internal project "Internal"' do + create :project_with_code, name: 'Internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL + end - def project - @project ||= Project.find_by_name("Community") + step 'I should see project "Internal"' do + page.should have_content "Internal" + end + + step 'I should not see project "Internal"' do + page.should_not have_content "Internal" + end + + step 'I visit project "Internal" page' do + project = Project.find_by_name('Internal') + visit project_path(project) + end + + step 'I should see project "Internal" home page' do + within '.project-home-title' do + page.should have_content 'Internal' + end end end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 2bdcbdc8c7f..90cb69760a9 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -31,11 +31,13 @@ module API end class Project < Grape::Entity - expose :id, :description, :default_branch, :public, :ssh_url_to_repo, :http_url_to_repo, :web_url + expose :id, :description, :default_branch + expose :public?, as: :public + expose :visibility_level, :ssh_url_to_repo, :http_url_to_repo, :web_url expose :owner, using: Entities::UserBasic expose :name, :name_with_namespace expose :path, :path_with_namespace - expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at, :public + expose :issues_enabled, :merge_requests_enabled, :wall_enabled, :wiki_enabled, :snippets_enabled, :created_at, :last_activity_at expose :namespace expose :forked_from_project, using: Entities::ForkedFromProject, :if => lambda{ | project, options | project.forked? } end diff --git a/lib/api/projects.rb b/lib/api/projects.rb index b927e63f4a4..003533fb59a 100644 --- a/lib/api/projects.rb +++ b/lib/api/projects.rb @@ -11,6 +11,13 @@ module API end not_found! end + + def map_public_to_visibility_level(attrs) + publik = attrs.delete(:public) + publik = [ true, 1, '1', 't', 'T', 'true', 'TRUE', 'on', 'ON' ].include?(publik) + attrs[:visibility_level] = Gitlab::VisibilityLevel::PUBLIC if !attrs[:visibility_level].present? && publik == true + attrs + end end # Get a projects list for authenticated user @@ -76,7 +83,8 @@ module API # wiki_enabled (optional) # snippets_enabled (optional) # namespace_id (optional) - defaults to user namespace - # public (optional) - false by default + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) - 0 by default # Example Request # POST /projects post do @@ -90,7 +98,9 @@ module API :wiki_enabled, :snippets_enabled, :namespace_id, - :public] + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateContext.new(current_user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -114,7 +124,8 @@ module API # merge_requests_enabled (optional) # wiki_enabled (optional) # snippets_enabled (optional) - # public (optional) + # public (optional) - if true same as setting visibility_level = 20 + # visibility_level (optional) # Example Request # POST /projects/user/:user_id post "user/:user_id" do @@ -128,7 +139,9 @@ module API :merge_requests_enabled, :wiki_enabled, :snippets_enabled, - :public] + :public, + :visibility_level] + attrs = map_public_to_visibility_level(attrs) @project = ::Projects::CreateContext.new(user, attrs).execute if @project.saved? present @project, with: Entities::Project @@ -290,7 +303,8 @@ module API # GET /projects/search/:query get "/search/:query" do ids = current_user.authorized_projects.map(&:id) - projects = Project.where("(id in (?) OR public = true) AND (name LIKE (?))", ids, "%#{params[:query]}%") + visibility_levels = [ Gitlab::VisibilityLevel::INTERNAL, Gitlab::VisibilityLevel::PUBLIC ] + projects = Project.where("(id in (?) OR visibility_level in (?)) AND (name LIKE (?))", ids, visibility_levels, "%#{params[:query]}%") present paginate(projects), with: Entities::Project end end diff --git a/lib/gitlab/backend/grack_auth.rb b/lib/gitlab/backend/grack_auth.rb index e2349495b57..c629144118c 100644 --- a/lib/gitlab/backend/grack_auth.rb +++ b/lib/gitlab/backend/grack_auth.rb @@ -58,7 +58,7 @@ module Grack end else - return unauthorized unless project.public + return unauthorized unless project.public? end if authorized_git_request? @@ -80,7 +80,7 @@ module Grack def authorize_request(service) case service when 'git-upload-pack' - project.public || can?(user, :download_code, project) + can?(user, :download_code, project) when'git-receive-pack' refs.each do |ref| action = if project.protected_branch?(ref) diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb new file mode 100644 index 00000000000..eada9bcddf5 --- /dev/null +++ b/lib/gitlab/visibility_level.rb @@ -0,0 +1,42 @@ +# Gitlab::VisibilityLevel module +# +# Define allowed public modes that can be used for +# GitLab projects to determine project public mode +# +module Gitlab + module VisibilityLevel + PRIVATE = 0 + INTERNAL = 10 + PUBLIC = 20 + + class << self + def values + options.values + end + + def options + { + 'Private' => PRIVATE, + 'Internal' => INTERNAL, + 'Public' => PUBLIC + } + end + + def allowed_for?(user, level) + user.is_admin? || !Gitlab.config.gitlab.restricted_visibility_levels.include?(level) + end + end + + def private? + visibility_level_field == PRIVATE + end + + def internal? + visibility_level_field == INTERNAL + end + + def public? + visibility_level_field == PUBLIC + end + end +end diff --git a/spec/contexts/projects_create_context_spec.rb b/spec/contexts/projects_create_context_spec.rb index 8b2a49dbee5..d5b1cb83510 100644 --- a/spec/contexts/projects_create_context_spec.rb +++ b/spec/contexts/projects_create_context_spec.rb @@ -7,6 +7,7 @@ describe Projects::CreateContext do describe :create_by_user do before do @user = create :user + @admin = create :user, admin: true @opts = { name: "GitLab", namespace: @user.namespace @@ -37,7 +38,7 @@ describe Projects::CreateContext do it { @project.namespace.should == @group } end - context 'respect configured public setting' do + context 'respect configured visibility setting' do before(:each) do @settings = double("settings") @settings.stub(:issues) { true } @@ -46,25 +47,90 @@ describe Projects::CreateContext do @settings.stub(:wall) { true } @settings.stub(:snippets) { true } stub_const("Settings", Class.new) + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [] } + Settings.stub_chain(:gitlab).and_return(@restrictions) Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) end context 'should be public when setting is public' do before do - @settings.stub(:public) { true } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PUBLIC } @project = create_project(@user, @opts) end - it { @project.public.should be_true } + it { @project.public?.should be_true } end - context 'should be private when setting is not public' do + context 'should be private when setting is private' do before do - @settings.stub(:public) { false } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } @project = create_project(@user, @opts) end - it { @project.public.should be_false } + it { @project.private?.should be_true } + end + + context 'should be internal when setting is internal' do + before do + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::INTERNAL } + @project = create_project(@user, @opts) + end + + it { @project.internal?.should be_true } + end + end + + context 'respect configured visibility restrictions setting' do + before(:each) do + @settings = double("settings") + @settings.stub(:issues) { true } + @settings.stub(:merge_requests) { true } + @settings.stub(:wiki) { true } + @settings.stub(:wall) { true } + @settings.stub(:snippets) { true } + @settings.stub(:visibility_level) { Gitlab::VisibilityLevel::PRIVATE } + stub_const("Settings", Class.new) + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } + Settings.stub_chain(:gitlab).and_return(@restrictions) + Settings.stub_chain(:gitlab, :default_projects_features).and_return(@settings) + end + + context 'should be private when option is public' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + @project = create_project(@user, @opts) + end + + it { @project.private?.should be_true } + end + + context 'should be public when option is public for admin' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + @project = create_project(@admin, @opts) + end + + it { @project.public?.should be_true } + end + + context 'should be private when option is private' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + @project = create_project(@user, @opts) + end + + it { @project.private?.should be_true } + end + + context 'should be internal when option is internal' do + before do + @opts.merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + @project = create_project(@user, @opts) + end + + it { @project.internal?.should be_true } end end end @@ -73,3 +139,4 @@ describe Projects::CreateContext do Projects::CreateContext.new(user, opts).execute end end + diff --git a/spec/contexts/projects_update_context_spec.rb b/spec/contexts/projects_update_context_spec.rb new file mode 100644 index 00000000000..edcaf844e5d --- /dev/null +++ b/spec/contexts/projects_update_context_spec.rb @@ -0,0 +1,111 @@ +require 'spec_helper' + +describe Projects::UpdateContext do + before(:each) { ActiveRecord::Base.observers.enable(:user_observer) } + after(:each) { ActiveRecord::Base.observers.disable(:user_observer) } + + describe :update_by_user do + before do + @user = create :user + @admin = create :user, admin: true + @project = create :project, creator_id: @user.id, namespace: @user.namespace + @opts = { project: {} } + end + + context 'should be private when updated to private' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be internal when updated to internal' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.internal?.should be_true } + end + + context 'should be public when updated to public' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.public?.should be_true } + end + + context 'respect configured visibility restrictions setting' do + before(:each) do + @restrictions = double("restrictions") + @restrictions.stub(:restricted_visibility_levels) { [ Gitlab::VisibilityLevel::PUBLIC ] } + Settings.stub_chain(:gitlab).and_return(@restrictions) + end + + context 'should be private when updated to private' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be internal when updated to internal' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::INTERNAL) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.internal?.should be_true } + end + + context 'should be private when updated to public' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @user, @opts) + end + + it { @created_private.should be_true } + it { @project.private?.should be_true } + end + + context 'should be public when updated to public by admin' do + before do + @created_private = @project.private? + + @opts[:project].merge!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + update_project(@project, @admin, @opts) + end + + it { @created_private.should be_true } + it { @project.public?.should be_true } + end + end + end + + def update_project(project, user, opts) + Projects::UpdateContext.new(project, user, opts).execute + end +end
\ No newline at end of file diff --git a/spec/contexts/search_context_spec.rb b/spec/contexts/search_context_spec.rb index 58f747e8725..c25743e0032 100644 --- a/spec/contexts/search_context_spec.rb +++ b/spec/contexts/search_context_spec.rb @@ -3,23 +3,39 @@ require 'spec_helper' describe SearchContext do let(:found_namespace) { create(:namespace, name: 'searchable namespace', path:'another_thing') } let(:user) { create(:user, namespace: found_namespace) } - let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, public: false) } + let!(:found_project) { create(:project, name: 'searchable_project', creator_id: user.id, namespace: found_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } let(:unfound_namespace) { create(:namespace, name: 'unfound namespace', path: 'yet_something_else') } - let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, public: false) } - let(:public_namespace) { create(:namespace, path: 'something_else',name: 'searchable public namespace') } - let(:other_user) { create(:user, namespace: public_namespace) } - let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: other_user.id, namespace: public_namespace, public: true) } + let!(:unfound_project) { create(:project, name: 'unfound_project', creator_id: user.id, namespace: unfound_namespace, visibility_level: Gitlab::VisibilityLevel::PRIVATE) } + + let(:internal_namespace) { create(:namespace, path: 'something_internal',name: 'searchable internal namespace') } + let(:internal_user) { create(:user, namespace: internal_namespace) } + let!(:internal_project) { create(:project, name: 'searchable_internal_project', creator_id: internal_user.id, namespace: internal_namespace, visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + + let(:public_namespace) { create(:namespace, path: 'something_public',name: 'searchable public namespace') } + let(:public_user) { create(:user, namespace: public_namespace) } + let!(:public_project) { create(:project, name: 'searchable_public_project', creator_id: public_user.id, namespace: public_namespace, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } describe '#execute' do it 'public projects should be searchable' do - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable"}) + context = SearchContext.new([found_project.id], nil, {search_code: false, search: "searchable"}) results = context.execute results[:projects].should == [found_project, public_project] end + it 'internal projects should be searchable' do + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable"}) + results = context.execute + # can't seem to rely on the return order, so check this way + #subject { results[:projects] } + results[:projects].should have(3).items + results[:projects].should include(found_project) + results[:projects].should include(internal_project) + results[:projects].should include(public_project) + end + it 'namespace name should be searchable' do - context = SearchContext.new([found_project.id], {search_code: false, search: "searchable namespace"}) + context = SearchContext.new([found_project.id], user, {search_code: false, search: "searchable namespace"}) results = context.execute results[:projects].should == [found_project] end diff --git a/spec/features/security/project/internal_access_spec.rb b/spec/features/security/project/internal_access_spec.rb new file mode 100644 index 00000000000..5abccd259d4 --- /dev/null +++ b/spec/features/security/project/internal_access_spec.rb @@ -0,0 +1,251 @@ +require 'spec_helper' + +describe "Internal Project Access" do + let(:project) { create(:project_with_code) } + + let(:master) { create(:user) } + let(:guest) { create(:user) } + let(:reporter) { create(:user) } + + before do + # internal project + project.visibility_level = Gitlab::VisibilityLevel::INTERNAL + project.save! + + # full access + project.team << [master, :master] + + # readonly + project.team << [reporter, :reporter] + + end + + describe "Project should be internal" do + subject { project } + + its(:internal?) { should be_true } + end + + describe "GET /:project_path" do + subject { project_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tree/master" do + subject { project_tree_path(project, project.repository.root_ref) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commits/master" do + subject { project_commits_path(project, project.repository.root_ref, limit: 1) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/commit/:sha" do + subject { project_commit_path(project, project.repository.commit) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/compare" do + subject { project_compare_index_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/team" do + subject { project_team_index_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/wall" do + subject { project_wall_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/blob" do + before do + commit = project.repository.commit + path = commit.tree.contents.select { |i| i.is_a?(Grit::Blob) }.first.name + @blob_path = project_blob_path(project, File.join(commit.id, path)) + end + + it { @blob_path.should be_allowed_for master } + it { @blob_path.should be_allowed_for reporter } + it { @blob_path.should be_allowed_for :admin } + it { @blob_path.should be_allowed_for guest } + it { @blob_path.should be_allowed_for :user } + it { @blob_path.should be_denied_for :visitor } + end + + describe "GET /:project_path/edit" do + subject { edit_project_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/deploy_keys" do + subject { project_deploy_keys_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/issues" do + subject { project_issues_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets" do + subject { project_snippets_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/snippets/new" do + subject { new_project_snippet_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests" do + subject { project_merge_requests_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/merge_requests/new" do + subject { new_project_merge_request_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches/recent" do + subject { recent_project_branches_path(project) } + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/branches" do + subject { project_branches_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:branches).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/tags" do + subject { project_tags_path(project) } + + before do + # Speed increase + Project.any_instance.stub(:tags).and_return([]) + end + + it { should be_allowed_for master } + it { should be_allowed_for reporter } + it { should be_allowed_for :admin } + it { should be_allowed_for guest } + it { should be_allowed_for :user } + it { should be_denied_for :visitor } + end + + describe "GET /:project_path/hooks" do + subject { project_hooks_path(project) } + + it { should be_allowed_for master } + it { should be_denied_for reporter } + it { should be_allowed_for :admin } + it { should be_denied_for guest } + it { should be_denied_for :user } + it { should be_denied_for :visitor } + end +end diff --git a/spec/features/security/project/private_access_spec.rb b/spec/features/security/project/private_access_spec.rb index 7f3f8c50f02..481d8cec416 100644 --- a/spec/features/security/project/private_access_spec.rb +++ b/spec/features/security/project/private_access_spec.rb @@ -15,6 +15,12 @@ describe "Private Project Access" do project.team << [reporter, :reporter] end + describe "Project should be private" do + subject { project } + + its(:private?) { should be_true } + end + describe "GET /:project_path" do subject { project_path(project) } diff --git a/spec/features/security/project/public_access_spec.rb b/spec/features/security/project/public_access_spec.rb index 267643fd8ef..3f1016473f5 100644 --- a/spec/features/security/project/public_access_spec.rb +++ b/spec/features/security/project/public_access_spec.rb @@ -9,7 +9,7 @@ describe "Public Project Access" do before do # public project - project.public = true + project.visibility_level = Gitlab::VisibilityLevel::PUBLIC project.save! # full access diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index d5803d8cec3..0167d51dd39 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -14,13 +14,13 @@ # merge_requests_enabled :boolean default(TRUE), not null # wiki_enabled :boolean default(TRUE), not null # namespace_id :integer -# public :boolean default(FALSE), not null # issues_tracker :string(255) default("gitlab"), not null # issues_tracker_id :string(255) # snippets_enabled :boolean default(TRUE), not null # last_activity_at :datetime # imported :boolean default(FALSE), not null # import_url :string(255) +# visibility_level :integer default(0), not null # require 'spec_helper' diff --git a/spec/requests/api/projects_spec.rb b/spec/requests/api/projects_spec.rb index e4cef6c587c..7322d793c95 100644 --- a/spec/requests/api/projects_spec.rb +++ b/spec/requests/api/projects_spec.rb @@ -132,15 +132,45 @@ describe API::API do end it "should set a project as public" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + post api("/projects", user), project + json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + + it "should set a project as public using :public" do project = attributes_for(:project, { public: true }) post api("/projects", user), project json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + + it "should set a project as internal" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + end + + it "should set a project as internal overriding :public" do + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end it "should set a project as private" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + post api("/projects", user), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + end + + it "should set a project as private using :public" do project = attributes_for(:project, { public: false }) post api("/projects", user), project json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end end @@ -183,19 +213,46 @@ describe API::API do end it "should set a project as public" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PUBLIC }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + + it "should set a project as public using :public" do project = attributes_for(:project, { public: true }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_true + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PUBLIC + end + it "should set a project as internal" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::INTERNAL }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL end - it "should set a project as private" do - project = attributes_for(:project, { public: false }) + it "should set a project as internal overriding :public" do + project = attributes_for(:project, { public: true, visibility_level: Gitlab::VisibilityLevel::INTERNAL }) post api("/projects/user/#{user.id}", admin), project json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::INTERNAL + end + it "should set a project as private" do + project = attributes_for(:project, { visibility_level: Gitlab::VisibilityLevel::PRIVATE }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE end + it "should set a project as private using :public" do + project = attributes_for(:project, { public: false }) + post api("/projects/user/#{user.id}", admin), project + json_response['public'].should be_false + json_response['visibility_level'].should == Gitlab::VisibilityLevel::PRIVATE + end end describe "GET /projects/:id" do @@ -649,10 +706,10 @@ describe API::API do describe :fork_admin do let(:project_fork_target) { create(:project) } - let(:project_fork_source) { create(:project, public: true) } + let(:project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } describe "POST /projects/:id/fork/:forked_from_id" do - let(:new_project_fork_source) { create(:project, public: true) } + let(:new_project_fork_source) { create(:project, visibility_level: Gitlab::VisibilityLevel::PUBLIC) } it "shouldn't available for non admin users" do post api("/projects/#{project_fork_target.id}/fork/#{project_fork_source.id}", user) @@ -721,8 +778,10 @@ describe API::API do let!(:post) { create(:project, name: "#{query}_post", creator_id: user.id, namespace: user.namespace) } let!(:pre_post) { create(:project, name: "pre_#{query}_post", creator_id: user.id, namespace: user.namespace) } let!(:unfound) { create(:project, name: 'unfound', creator_id: user.id, namespace: user.namespace) } - let!(:public) { create(:project, name: "another #{query}",public: true) } - let!(:unfound_public) { create(:project, name: 'unfound public', public: true) } + let!(:internal) { create(:project, name: "internal #{query}", visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:unfound_internal) { create(:project, name: 'unfound internal', visibility_level: Gitlab::VisibilityLevel::INTERNAL) } + let!(:public) { create(:project, name: "public #{query}", visibility_level: Gitlab::VisibilityLevel::PUBLIC) } + let!(:unfound_public) { create(:project, name: 'unfound public', visibility_level: Gitlab::VisibilityLevel::PUBLIC) } context "when unauthenticated" do it "should return authentication error" do @@ -736,7 +795,7 @@ describe API::API do get api("/projects/search/#{query}",user) response.status.should == 200 json_response.should be_an Array - json_response.size.should == 5 + json_response.size.should == 6 json_response.each {|project| project['name'].should =~ /.*query.*/} end end @@ -746,8 +805,8 @@ describe API::API do get api("/projects/search/#{query}", user2) response.status.should == 200 json_response.should be_an Array - json_response.size.should == 1 - json_response.first['name'].should == "another #{query}" + json_response.size.should == 2 + json_response.each {|project| project['name'].should =~ /(internal|public) query/} end end end |