Skip to content
Snippets Groups Projects
Commit 892dea67 authored by Felipe Artur's avatar Felipe Artur
Browse files

Project tools visibility level

parent e71cd7a3
No related branches found
No related tags found
No related merge requests found
Showing
with 174 additions and 35 deletions
Loading
Loading
@@ -34,6 +34,7 @@ v 8.12.0 (unreleased)
- Added project specific enable/disable setting for LFS !5997
- Don't expose a user's token in the `/api/v3/user` API (!6047)
- Remove redundant js-timeago-pending from user activity log (ClemMakesApps)
- Ability to manage project issues, snippets, wiki, merge requests and builds access level
- Added tests for diff notes
- Add a button to download latest successful artifacts for branches and tags !5142
- Remove redundant pipeline tooltips (ClemMakesApps)
Loading
Loading
Loading
Loading
@@ -37,7 +37,7 @@ class JwtController < ApplicationController
 
def authenticate_project(login, password)
if login == 'gitlab-ci-token'
Project.find_by(builds_enabled: true, runners_token: password)
Project.with_builds_enabled.find_by(runners_token: password)
end
end
 
Loading
Loading
Loading
Loading
@@ -88,6 +88,6 @@ class Projects::ApplicationController < ApplicationController
end
 
def builds_enabled
return render_404 unless @project.builds_enabled?
return render_404 unless @project.feature_available?(:builds, current_user)
end
end
Loading
Loading
@@ -38,6 +38,6 @@ class Projects::DiscussionsController < Projects::ApplicationController
end
 
def module_enabled
render_404 unless @project.merge_requests_enabled
render_404 unless @project.feature_available?(:merge_requests, current_user)
end
end
Loading
Loading
@@ -201,7 +201,7 @@ class Projects::IssuesController < Projects::ApplicationController
end
 
def module_enabled
return render_404 unless @project.issues_enabled && @project.default_issues_tracker?
return render_404 unless @project.feature_available?(:issues, current_user) && @project.default_issues_tracker?
end
 
def redirect_to_external_issue_tracker
Loading
Loading
Loading
Loading
@@ -99,7 +99,7 @@ class Projects::LabelsController < Projects::ApplicationController
protected
 
def module_enabled
unless @project.issues_enabled || @project.merge_requests_enabled
unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404
end
end
Loading
Loading
Loading
Loading
@@ -413,7 +413,7 @@ class Projects::MergeRequestsController < Projects::ApplicationController
end
 
def module_enabled
return render_404 unless @project.merge_requests_enabled
return render_404 unless @project.feature_available?(:merge_requests, current_user)
end
 
def validates_merge_request
Loading
Loading
Loading
Loading
@@ -106,7 +106,7 @@ class Projects::MilestonesController < Projects::ApplicationController
end
 
def module_enabled
unless @project.issues_enabled || @project.merge_requests_enabled
unless @project.feature_available?(:issues, current_user) || @project.feature_available?(:merge_requests, current_user)
return render_404
end
end
Loading
Loading
Loading
Loading
@@ -94,7 +94,7 @@ class Projects::SnippetsController < Projects::ApplicationController
end
 
def module_enabled
return render_404 unless @project.snippets_enabled
return render_404 unless @project.feature_available?(:snippets, current_user)
end
 
def snippet_params
Loading
Loading
Loading
Loading
@@ -303,13 +303,23 @@ class ProjectsController < Projects::ApplicationController
end
 
def project_params
project_feature_attributes =
{
project_feature_attributes:
[
:issues_access_level, :builds_access_level,
:wiki_access_level, :merge_requests_access_level, :snippets_access_level
]
}
params.require(:project).permit(
:name, :path, :description, :issues_tracker, :tag_list, :runners_token,
:issues_enabled, :merge_requests_enabled, :snippets_enabled, :container_registry_enabled,
:container_registry_enabled,
:issues_tracker_id, :default_branch,
:wiki_enabled, :visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:builds_enabled, :build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled, :lfs_enabled
:visibility_level, :import_url, :last_activity_at, :namespace_id, :avatar,
:build_allow_git_fetch, :build_timeout_in_minutes, :build_coverage_regex,
:public_builds, :only_allow_merge_if_build_succeeds, :request_access_enabled,
:lfs_enabled, project_feature_attributes
)
end
 
Loading
Loading
Loading
Loading
@@ -110,7 +110,7 @@ module ApplicationHelper
project = event.project
 
# Skip if project repo is empty or MR disabled
return false unless project && !project.empty_repo? && project.merge_requests_enabled
return false unless project && !project.empty_repo? && project.feature_available?(:merge_requests, current_user)
 
# Skip if user already created appropriate MR
return false if project.merge_requests.where(source_branch: event.branch_name).opened.any?
Loading
Loading
Loading
Loading
@@ -3,7 +3,7 @@ module CompareHelper
from.present? &&
to.present? &&
from != to &&
project.merge_requests_enabled &&
project.feature_available?(:merge_requests, current_user) &&
project.repository.branch_names.include?(from) &&
project.repository.branch_names.include?(to)
end
Loading
Loading
Loading
Loading
@@ -412,4 +412,23 @@ module ProjectsHelper
 
message.strip.gsub(project.repository_storage_path.chomp('/'), "[REPOS PATH]")
end
def project_feature_options
{
'Disabled' => ProjectFeature::DISABLED,
'Only team members' => ProjectFeature::PRIVATE,
'Everyone with access' => ProjectFeature::ENABLED
}
end
def project_feature_access_select(field)
# Don't show option "everyone with access" if project is private
options = project_feature_options
level = @project.project_feature.public_send(field)
options.delete('Everyone with access') if @project.private? && level != ProjectFeature::ENABLED
options = options_for_select(options, selected: @project.project_feature.public_send(field) || ProjectFeature::ENABLED)
content_tag(:select, options, name: "project[project_feature_attributes][#{field.to_s}]", id: "project_project_feature_attributes_#{field.to_s}", class: "pull-right form-control").html_safe
end
end
# Makes api V3 compatible with old project features permissions methods
#
# After migrating issues_enabled merge_requests_enabled builds_enabled snippets_enabled and wiki_enabled
# fields to a new table "project_features", support for the old fields is still needed in the API.
module ProjectFeaturesCompatibility
extend ActiveSupport::Concern
def wiki_enabled=(value)
write_feature_attribute(:wiki_access_level, value)
end
def builds_enabled=(value)
write_feature_attribute(:builds_access_level, value)
end
def merge_requests_enabled=(value)
write_feature_attribute(:merge_requests_access_level, value)
end
def issues_enabled=(value)
write_feature_attribute(:issues_access_level, value)
end
def snippets_enabled=(value)
write_feature_attribute(:snippets_access_level, value)
end
private
def write_feature_attribute(field, value)
build_project_feature unless project_feature
access_level = value == "true" ? ProjectFeature::ENABLED : ProjectFeature::DISABLED
project_feature.update_attribute(field, access_level)
end
end
Loading
Loading
@@ -11,24 +11,23 @@ class Project < ActiveRecord::Base
include AfterCommitQueue
include CaseSensitivity
include TokenAuthenticatable
include ProjectFeaturesCompatibility
 
extend Gitlab::ConfigHelper
 
UNKNOWN_IMPORT_URL = 'http://unknown.git'
 
delegate :feature_available?, :builds_enabled?, :wiki_enabled?, :merge_requests_enabled?, to: :project_feature, allow_nil: true
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :issues_enabled, gitlab_config_features.issues
default_value_for :merge_requests_enabled, gitlab_config_features.merge_requests
default_value_for :builds_enabled, gitlab_config_features.builds
default_value_for :wiki_enabled, gitlab_config_features.wiki
default_value_for :snippets_enabled, gitlab_config_features.snippets
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
default_value_for(:repository_storage) { current_application_settings.repository_storage }
default_value_for(:shared_runners_enabled) { current_application_settings.shared_runners_enabled }
 
after_create :ensure_dir_exist
after_save :ensure_dir_exist, if: :namespace_id_changed?
after_initialize :setup_project_feature
 
# set last_activity_at to the same as created_at
after_create :set_last_activity_at
Loading
Loading
@@ -62,10 +61,10 @@ class Project < ActiveRecord::Base
belongs_to :group, -> { where(type: Group) }, foreign_key: 'namespace_id'
belongs_to :namespace
 
has_one :board, dependent: :destroy
has_one :last_event, -> {order 'events.created_at DESC'}, class_name: 'Event', foreign_key: 'project_id'
 
has_one :board, dependent: :destroy
# Project services
has_many :services
has_one :campfire_service, dependent: :destroy
Loading
Loading
@@ -130,6 +129,7 @@ class Project < ActiveRecord::Base
has_many :notification_settings, dependent: :destroy, as: :source
 
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy
 
has_many :commit_statuses, dependent: :destroy, class_name: 'CommitStatus', foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
Loading
Loading
@@ -142,6 +142,7 @@ class Project < ActiveRecord::Base
has_many :deployments, dependent: :destroy
 
accepts_nested_attributes_for :variables, allow_destroy: true
accepts_nested_attributes_for :project_feature
 
delegate :name, to: :owner, allow_nil: true, prefix: true
delegate :members, to: :team, prefix: true
Loading
Loading
@@ -159,8 +160,6 @@ class Project < ActiveRecord::Base
length: { within: 0..255 },
format: { with: Gitlab::Regex.project_path_regex,
message: Gitlab::Regex.project_path_regex_message }
validates :issues_enabled, :merge_requests_enabled,
:wiki_enabled, inclusion: { in: [true, false] }
validates :namespace, presence: true
validates_uniqueness_of :name, scope: :namespace_id
validates_uniqueness_of :path, scope: :namespace_id
Loading
Loading
@@ -196,6 +195,9 @@ class Project < ActiveRecord::Base
scope :for_milestones, ->(ids) { joins(:milestones).where('milestones.id' => ids).distinct }
scope :with_push, -> { joins(:events).where('events.action = ?', Event::PUSHED) }
 
scope :with_builds_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0') }
scope :with_issues_enabled, -> { joins('LEFT JOIN project_features ON projects.id = project_features.project_id').where('project_features.issues_access_level IS NULL or project_features.issues_access_level > 0') }
scope :active, -> { joins(:issues, :notes, :merge_requests).order('issues.created_at, notes.created_at, merge_requests.created_at DESC') }
scope :abandoned, -> { where('projects.last_activity_at < ?', 6.months.ago) }
 
Loading
Loading
@@ -1121,7 +1123,7 @@ class Project < ActiveRecord::Base
end
 
def enable_ci
self.builds_enabled = true
project_feature.update_attribute(:builds_access_level, ProjectFeature::ENABLED)
end
 
def any_runners?(&block)
Loading
Loading
@@ -1288,6 +1290,11 @@ class Project < ActiveRecord::Base
 
private
 
# Prevents the creation of project_feature record for every project
def setup_project_feature
build_project_feature unless project_feature
end
def default_branch_protected?
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_FULL ||
current_application_settings.default_branch_protection == Gitlab::Access::PROTECTION_DEV_CAN_MERGE
Loading
Loading
class ProjectFeature < ActiveRecord::Base
# == Project features permissions
#
# Grants access level to project tools
#
# Tools can be enabled only for users, everyone or disabled
# Access control is made only for non private projects
#
# levels:
#
# Disabled: not enabled for anyone
# Private: enabled only for team members
# Enabled: enabled for everyone able to access the project
#
# Permision levels
DISABLED = 0
PRIVATE = 10
ENABLED = 20
FEATURES = %i(issues merge_requests wiki snippets builds)
belongs_to :project
def feature_available?(feature, user)
raise ArgumentError, 'invalid project feature' unless FEATURES.include?(feature)
get_permission(user, public_send("#{feature}_access_level"))
end
def builds_enabled?
return true unless builds_access_level
builds_access_level > DISABLED
end
def wiki_enabled?
return true unless wiki_access_level
wiki_access_level > DISABLED
end
def merge_requests_enabled?
return true unless merge_requests_access_level
merge_requests_access_level > DISABLED
end
private
def get_permission(user, level)
case level
when DISABLED
false
when PRIVATE
user && (project.team.member?(user) || user.admin?)
when ENABLED
true
else
true
end
end
end
Loading
Loading
@@ -433,7 +433,7 @@ class User < ActiveRecord::Base
#
# This logic is duplicated from `Ability#project_abilities` into a SQL form.
def projects_where_can_admin_issues
authorized_projects(Gitlab::Access::REPORTER).non_archived.where.not(issues_enabled: false)
authorized_projects(Gitlab::Access::REPORTER).non_archived.with_issues_enabled
end
 
def is_admin?
Loading
Loading
Loading
Loading
@@ -145,28 +145,28 @@ class ProjectPolicy < BasePolicy
end
 
def disabled_features!
unless project.issues_enabled
unless project.feature_available?(:issues, user)
cannot!(*named_abilities(:issue))
end
 
unless project.merge_requests_enabled
unless project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:merge_request))
end
 
unless project.issues_enabled || project.merge_requests_enabled
unless project.feature_available?(:issues, user) || project.feature_available?(:merge_requests, user)
cannot!(*named_abilities(:label))
cannot!(*named_abilities(:milestone))
end
 
unless project.snippets_enabled
unless project.feature_available?(:snippets, user)
cannot!(*named_abilities(:project_snippet))
end
 
unless project.has_wiki?
unless project.feature_available?(:wiki, user) || project.has_external_wiki?
cannot!(*named_abilities(:wiki))
end
 
unless project.builds_enabled
unless project.feature_available?(:builds, user)
cannot!(*named_abilities(:build))
cannot!(*named_abilities(:pipeline))
cannot!(*named_abilities(:environment))
Loading
Loading
Loading
Loading
@@ -8,16 +8,18 @@ module Ci
builds =
if current_runner.shared?
builds.
# don't run projects which have not enabled shared runners
joins(:project).where(projects: { builds_enabled: true, shared_runners_enabled: true }).
# don't run projects which have not enabled shared runners and builds
joins(:project).where(projects: { shared_runners_enabled: true }).
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
 
# this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all
joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
else
# do run projects which are only assigned to this runner (FIFO)
builds.where(project: current_runner.projects.where(builds_enabled: true)).order('created_at ASC')
builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
end
 
build = builds.find do |build|
Loading
Loading
Loading
Loading
@@ -31,7 +31,7 @@ module MergeRequests
 
def get_branches(changes)
return [] if project.empty_repo?
return [] unless project.merge_requests_enabled
return [] unless project.merge_requests_enabled?
 
changes_list = Gitlab::ChangesList.new(changes)
changes_list.map do |change|
Loading
Loading
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment