Commit da07c2e4 authored by Felipe's avatar Felipe

Add visibility level to project repository

parent c49e1526
Pipeline #4640834 failed with stages
in 58 minutes and 11 seconds
......@@ -61,6 +61,7 @@ Please view this file on the master branch, on stable branches it's out of date.
- Fix inconsistent highlighting of already selected activity nav-links (ClemMakesApps)
- Remove redundant mixins (ClemMakesApps)
- Added 'Download' button to the Snippets page (Justin DiPierro)
- Add visibility level to project repository
- Fix robots.txt disallowing access to groups starting with "s" (Matt Harrison)
- Close open merge request without source project (Katarzyna Kobierska Ula Budziszewska)
- Fix that manual jobs would no longer block jobs in the next stage. !6604
......
......@@ -4,9 +4,8 @@
this.ProjectNew = (function() {
function ProjectNew() {
this.toggleSettings = bind(this.toggleSettings, this);
this.$selects = $('.features select').filter(function () {
return $(this).data('field');
});
this.$selects = $('.features select');
this.$repoSelects = this.$selects.filter('.js-repo-select');
$('.project-edit-container').on('ajax:before', (function(_this) {
return function() {
......@@ -16,6 +15,7 @@
})(this));
this.toggleSettings();
this.toggleSettingsOnclick();
this.toggleRepoVisibility();
}
ProjectNew.prototype.toggleSettings = function() {
......@@ -43,6 +43,38 @@
}
};
ProjectNew.prototype.toggleRepoVisibility = function () {
var $repoAccessLevel = $('.js-repo-access-level select');
this.$repoSelects.find("option[value='" + $repoAccessLevel.val() + "']")
.nextAll()
.hide();
$repoAccessLevel.off('change')
.on('change', function () {
var selectedVal = parseInt($repoAccessLevel.val());
this.$repoSelects.each(function () {
var $this = $(this),
repoSelectVal = parseInt($this.val());
$this.find('option').show();
if (selectedVal < repoSelectVal) {
$this.val(selectedVal);
}
$this.find("option[value='" + selectedVal + "']").nextAll().hide();
});
if (selectedVal) {
this.$repoSelects.removeClass('disabled');
} else {
this.$repoSelects.addClass('disabled');
}
}.bind(this));
};
return ProjectNew;
})();
......
......@@ -761,62 +761,6 @@ pre.light-well {
.dropdown-menu {
width: 300px;
}
&.from .compare-dropdown-toggle {
width: 237px;
}
&.to .compare-dropdown-toggle {
width: 254px;
}
.dropdown-toggle-text {
display: block;
height: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
width: 100%;
}
}
.compare-ellipsis {
display: inline;
}
@media (max-width: $screen-xs-max) {
.compare-form-group {
.input-group {
width: 100%;
& > .compare-dropdown-toggle {
width: 100%;
}
}
.dropdown-menu {
width: 100%;
}
}
.compare-switch-container {
text-align: center;
padding: 0 0 $gl-padding;
.commits-compare-switch {
float: none;
}
}
.compare-ellipsis {
display: block;
text-align: center;
padding: 0 0 $gl-padding;
}
.commits-compare-btn {
width: 100%;
}
}
.clearable-input {
......@@ -855,3 +799,30 @@ pre.light-well {
border-bottom-right-radius: 0;
}
}
.project-home-empty {
border-top: 0;
.container-fluid {
background: none;
}
p {
margin-left: auto;
margin-right: auto;
max-width: 650px;
}
}
.project-feature-nested {
@media (min-width: $screen-sm-min) {
padding-left: 45px;
}
}
.project-repo-select {
&.disabled {
opacity: 0.5;
pointer-events: none;
}
}
class ProjectsController < Projects::ApplicationController
include IssuableCollections
include ExtractsPath
before_action :authenticate_user!, except: [:show, :activity, :refs]
......@@ -103,16 +104,7 @@ class ProjectsController < Projects::ApplicationController
respond_to do |format|
format.html do
@notification_setting = current_user.notification_settings_for(@project) if current_user
if @project.repository_exists?
if @project.empty_repo?
render 'projects/empty'
else
render :show
end
else
render 'projects/no_repo'
end
render_landing_page
end
format.atom do
......@@ -285,6 +277,26 @@ class ProjectsController < Projects::ApplicationController
private
# Render project landing depending of which features are available
# So if page is not availble in the list it renders the next page
#
# pages list order: repository readme, wiki home, issues list, customize workflow
def render_landing_page
if @project.feature_available?(:repository, current_user)
return render 'projects/no_repo' unless @project.repository_exists?
render 'projects/empty' if @project.empty_repo?
else
if @project.wiki_enabled?
@wiki_home = @project.wiki.find_page('home', params[:version_id])
elsif @project.feature_available?(:issues, current_user)
@issues = issues_collection
@issues = @issues.page(params[:page])
end
render :show
end
end
def determine_layout
if [:new, :create].include?(action_name.to_sym)
'application'
......@@ -308,7 +320,8 @@ class ProjectsController < Projects::ApplicationController
project_feature_attributes:
[
:issues_access_level, :builds_access_level,
:wiki_access_level, :merge_requests_access_level, :snippets_access_level
:wiki_access_level, :merge_requests_access_level,
:snippets_access_level, :repository_access_level
]
}
......
......@@ -50,6 +50,20 @@ module PreferencesHelper
end
def default_project_view
current_user ? current_user.project_view : 'readme'
return 'readme' unless current_user
user_view = current_user.project_view
if @project.feature_available?(:repository, current_user)
user_view
elsif user_view == "activity"
"activity"
elsif @project.wiki_enabled?
"wiki"
elsif @project.feature_available?(:issues, current_user)
"projects/issues/issues"
else
"customize_workflow"
end
end
end
......@@ -134,16 +134,35 @@ module ProjectsHelper
options = project_feature_options
if @project.private?
level = @project.project_feature.send(field)
options.delete('Everyone with access')
highest_available_option = options.values.max if @project.project_feature.send(field) == ProjectFeature::ENABLED
highest_available_option = options.values.max if level == ProjectFeature::ENABLED
end
options = options_for_select(options, selected: highest_available_option || @project.project_feature.public_send(field))
content_tag(:select, options, name: "project[project_feature_attributes][#{field}]", id: "project_project_feature_attributes_#{field}", class: "pull-right form-control", data: { field: field }).html_safe
content_tag(
:select,
options,
name: "project[project_feature_attributes][#{field}]",
id: "project_project_feature_attributes_#{field}",
class: "pull-right form-control #{repo_children_classes(field)}",
data: { field: field }
).html_safe
end
private
def repo_children_classes(field)
needs_repo_check = [:merge_requests_access_level, :builds_access_level]
return unless needs_repo_check.include?(field)
classes = "project-repo-select js-repo-select"
classes << " disabled" unless @project.feature_available?(:repository, current_user)
classes
end
def get_project_nav_tabs(project, current_user)
nav_tabs = [:home]
......@@ -155,12 +174,8 @@ module ProjectsHelper
nav_tabs << :merge_requests
end
if can?(current_user, :read_pipeline, project)
  • @felipefac

    Is there a reason why :builds got removed there?

  • @ayufan Yes. We want to hide pipelines whole tab when builds or repository are not available because
    it could leak information about repository, like commit messages, links etc. Builds and pipelines are enabled for the same levels reporter access or anonymous when builds are public.

    Did it cause a problem?

  • Hmm, I still do not understand why this was done this way or why :builds nav tab got removed from that list.

    Can you describe it in more detail?

  • I did check that builds was a subtab of pipelines, just realized that removing it here hides the subtab too. I will send a MR and mention you.

Please register or sign in to reply
nav_tabs << :pipelines
end
if can?(current_user, :read_build, project)
nav_tabs << :builds
nav_tabs << :pipelines
end
if Gitlab.config.registry.enabled && can?(current_user, :read_container_image, project)
......@@ -435,4 +450,8 @@ module ProjectsHelper
'Everyone with access' => ProjectFeature::ENABLED
}
end
def project_child_container_class(view_path)
view_path == "projects/issues/issues" ? "prepend-top-default" : "project-show-#{view_path}"
end
end
......@@ -13,23 +13,26 @@ class ProjectFeature < ActiveRecord::Base
# Enabled: enabled for everyone able to access the project
#
# Permision levels
# Permission levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
FEATURES = %i(issues merge_requests wiki snippets builds)
FEATURES = %i(issues merge_requests wiki snippets builds repository)
# Default scopes force us to unscope here since a service may need to check
# permissions for a project in pending_delete
# http://stackoverflow.com/questions/1540645/how-to-disable-default-scope-for-a-belongs-to
belongs_to :project, -> { unscope(where: :pending_delete) }
validate :repository_children_level
default_value_for :builds_access_level, value: ENABLED, allows_nil: false
default_value_for :issues_access_level, value: ENABLED, allows_nil: false
default_value_for :merge_requests_access_level, value: ENABLED, allows_nil: false
default_value_for :snippets_access_level, value: ENABLED, allows_nil: false
default_value_for :wiki_access_level, value: ENABLED, allows_nil: false
default_value_for :repository_access_level, value: ENABLED, allows_nil: false
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
......@@ -57,6 +60,18 @@ class ProjectFeature < ActiveRecord::Base
private
# Validates builds and merge requests access level
# which cannot be higher than repository access level
def repository_children_level
validator = lambda do |field|
level = public_send(field) || ProjectFeature::ENABLED
not_allowed = level > repository_access_level
self.errors.add(field, "cannot have higher visibility level than repository access level") if not_allowed
end
%i(merge_requests_access_level builds_access_level).each(&validator)
end
def get_permission(user, level)
case level
when DISABLED
......
......@@ -162,11 +162,13 @@ class ProjectPolicy < BasePolicy
end
def disabled_features!
repository_enabled = project.feature_available?(:repository, user)
unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue))
end
unless project.feature_available?(:merge_requests, user)
unless project.feature_available?(:merge_requests, user) && repository_enabled
cannot!(*named_abilities(:merge_request))
end
......@@ -183,13 +185,21 @@ class ProjectPolicy < BasePolicy
cannot!(*named_abilities(:wiki))
end
unless project.feature_available?(:builds, user)
unless project.feature_available?(:builds, user) && repository_enabled
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment))
cannot!(*named_abilities(:deployment))
end
unless repository_enabled
cannot! :push_code
cannot! :push_code_to_protected_branches
cannot! :download_code
cannot! :fork_project
cannot! :read_commit_status
end
unless project.container_registry_enabled
cannot!(*named_abilities(:container_image))
end
......
.row-content-block.project-home-empty
%div.text-center{ class: container_class }
%h4
Customize your workflow!
%p
Get started with GitLab by enabling features that work best for your project. From issues and wikis, to merge requests and builds, GitLab can help manage your workflow from idea to production!
- if can?(current_user, :admin_project, @project)
= link_to "Get started", edit_project_path(@project), class: "btn btn-success"
......@@ -22,5 +22,6 @@
= render 'projects/buttons/star'
= render 'projects/buttons/fork'
.project-clone-holder
= render "shared/clone_panel"
- if @project.feature_available?(:repository, current_user)
.project-clone-holder
= render "shared/clone_panel"
- if @wiki_home.present?
%div{ class: container_class }
.wiki-holder.prepend-top-default.append-bottom-default
.wiki
= preserve do
= render_wiki_content(@wiki_home)
- else
- can_create_wiki = can?(current_user, :create_wiki, @project)
.project-home-empty{ class: [('row-content-block' if can_create_wiki), ('content-block' unless can_create_wiki)] }
%div.text-center{ class: container_class }
%h4
This project does not have a wiki homepage yet
- if can_create_wiki
%p
Add a homepage to your wiki that contains information about your project
%p
We recommend you
= link_to "add a homepage", namespace_project_wiki_path(@project.namespace, @project, :home)
to your project's wiki and GitLab will show it here instead of this message.
......@@ -50,24 +50,39 @@
.form_group.prepend-top-20
.row
.col-md-9
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project
.col-md-3
= project_feature_access_select(:issues_access_level)
= feature_fields.label :repository_access_level, "Repository", class: 'label-light'
%span.help-block Push files to be stored in this project
.col-md-3.js-repo-access-level
= project_feature_access_select(:repository_access_level)
.col-sm-12
.row
.col-md-9.project-feature-nested
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
%span.help-block Submit changes to be merged upstream
.col-md-3
= project_feature_access_select(:merge_requests_access_level)
.row
.col-md-9.project-feature-nested
= feature_fields.label :builds_access_level, "Builds", class: 'label-light'
%span.help-block Submit, test and deploy your changes before merge
.col-md-3
= project_feature_access_select(:builds_access_level)
.row
.col-md-9
= feature_fields.label :merge_requests_access_level, "Merge requests", class: 'label-light'
%span.help-block Submit changes to be merged upstream
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
%span.help-block Share code pastes with others out of Git repository
.col-md-3
= project_feature_access_select(:merge_requests_access_level)
= project_feature_access_select(:snippets_access_level)
.row
.col-md-9
= feature_fields.label :builds_access_level, "Builds", class: 'label-light'
%span.help-block Submit Test and deploy your changes before merge
= feature_fields.label :issues_access_level, "Issues", class: 'label-light'
%span.help-block Lightweight issue tracking system for this project
.col-md-3
= project_feature_access_select(:builds_access_level)
= project_feature_access_select(:issues_access_level)
.row
.col-md-9
......@@ -76,24 +91,17 @@
.col-md-3
= project_feature_access_select(:wiki_access_level)
.row
.col-md-9
= feature_fields.label :snippets_access_level, "Snippets", class: 'label-light'
%span.help-block Share code pastes with others out of Git repository
.col-md-3
= project_feature_access_select(:snippets_access_level)
- if Gitlab.config.lfs.enabled && current_user.admin?
.row
.col-md-9
= f.label :lfs_enabled, 'LFS', class: 'label-light'
%span.help-block
.checkbox
= f.label :lfs_enabled do
= f.check_box :lfs_enabled
%strong LFS
%br
%span.descr
Git Large File Storage
= link_to icon('question-circle'), help_page_path('workflow/lfs/manage_large_binaries_with_git_lfs')
.col-md-3
= f.select :lfs_enabled, [%w(Enabled true), %w(Disabled false)], {}, selected: @project.lfs_enabled?, class: 'pull-right form-control', data: { field: 'lfs_enabled' }
- if Gitlab.config.registry.enabled
- if Gitlab.config.lfs.enabled && current_user.admin?
.form-group
.checkbox
= f.label :container_registry_enabled do
......
%ul.content-list.issues-list.issuable-list
= render @issues
= render partial: "projects/issues/issue", collection: @issues
- if @issues.blank?
%li
.nothing-here-block No issues to show
......
......@@ -12,72 +12,74 @@
= render 'projects/last_push'
= render "home_panel"
%nav.project-stats{ class: (container_class) }
%ul.nav
%li
= link_to project_files_path(@project) do
Files (#{repository_size})
%li
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
= link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if default_project_view != 'readme' && @repository.readme
- if @project.feature_available?(:repository, current_user)
%nav.project-stats{ class: container_class }
%ul.nav
%li
= link_to 'Readme', readme_path(@project)
- if @repository.changelog
= link_to project_files_path(@project) do
Files (#{repository_size})
%li
= link_to 'Changelog', changelog_path(@project)
- if @repository.license_blob
= link_to namespace_project_commits_path(@project.namespace, @project, current_ref) do
#{'Commit'.pluralize(@project.commit_count)} (#{number_with_delimiter(@project.commit_count)})
%li
= link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
= link_to namespace_project_branches_path(@project.namespace, @project) do
#{'Branch'.pluralize(@repository.branch_count)} (#{number_with_delimiter(@repository.branch_count)})
%li
= link_to 'Contribution guide', contribution_guide_path(@project)
= link_to namespace_project_tags_path(@project.namespace, @project) do
#{'Tag'.pluralize(@repository.tag_count)} (#{number_with_delimiter(@repository.tag_count)})
- if @repository.gitlab_ci_yml
%li
= link_to 'CI configuration', ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
- if default_project_view != 'readme' && @repository.readme
%li
= link_to 'Readme', readme_path(@project)
- if @repository.changelog
%li
= link_to 'Changelog', changelog_path(@project)
- if @repository.license_blob
%li
= link_to license_short_name(@project), license_path(@project)
- if @repository.contribution_guide
%li
= link_to 'Contribution guide', contribution_guide_path(@project)
- if @repository.gitlab_ci_yml
%li
= link_to 'CI configuration', ci_configuration_path(@project)
- if current_user && can_push_branch?(@project, @project.default_branch)
- unless @repository.changelog
%li.missing
= link_to add_special_file_path(@project, file_name: 'CHANGELOG') do
Add Changelog
- unless @repository.license_blob
%li.missing
= link_to add_special_file_path(@project, file_name: 'LICENSE') do
Add License
- unless @repository.contribution_guide
%li.missing
= link_to add_special_file_path(@project, file_name: 'CONTRIBUTING.md', commit_message: 'Add contribution guide') do
Add Contribution guide
- unless @repository.gitlab_ci_yml
%li.missing
= link_to add_special_file_path(@project, file_name: '.gitlab-ci.yml') do
Set Up CI
%li.project-repo-buttons-right
.project-repo-buttons.project-right-buttons
- if current_user
= render 'shared/members/access_request_buttons', source: @project
= render "projects/buttons/koding"
.btn-group.project-repo-btn-group
= render 'projects/buttons/download', project: @project, ref: @ref
= render 'projects/buttons/dropdown'
= render 'shared/notifications/button', notification_setting: @notification_setting
- if @repository.commit
.project-last-commit{ class: container_class }
= render 'projects/last_commit', commit: @repository.commit, project: @project
%div{ class: container_class }
- if @project.archived?
......@@ -86,5 +88,7 @@
= icon("exclamation-triangle fw")
Archived project! Repository is read-only
%div{class: "project-show-#{default_project_view}"}
= render default_project_view
- view_path = default_project_view
%div{ class: project_child_container_class(view_path) }
= render view_path
class AddRepositoryAccessLevelToProjectFeature < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
disable_ddl_transaction!
DOWNTIME = false
def up
add_column_with_default(:project_features, :repository_access_level, :integer, default: ProjectFeature::ENABLED)
end
def down
remove_column :project_features, :repository_access_level
end
end
......@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20161007133303) do
ActiveRecord::Schema.define(version: 20161012180455) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
......@@ -830,6 +830,7 @@ ActiveRecord::Schema.define(version: 20161007133303) do
t.integer "builds_access_level"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "repository_access_level", default: 20, null: false
end
add_index "project_features", ["project_id"], name: "index_project_features_on_project_id", using: :btree
......
......@@ -41,6 +41,46 @@ describe ProjectsController do
end
end
end
describe "when project repository is disabled" do
render_views
before do
project.team << [user, :developer]