2020-05-26 05:08:06 -04:00
|
|
|
# frozen_string_literal: true
|
|
|
|
|
|
|
|
# == Featurable concern
|
|
|
|
#
|
|
|
|
# This concern adds features (tools) functionality to Project and Group
|
|
|
|
# To enable features you need to call `set_available_features`
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# class ProjectFeature
|
|
|
|
# include Featurable
|
|
|
|
# set_available_features %i(wiki merge_request)
|
|
|
|
|
|
|
|
module Featurable
|
|
|
|
extend ActiveSupport::Concern
|
|
|
|
|
|
|
|
# Can be enabled only for members, everyone or disabled
|
|
|
|
# Access control is made only for non private containers.
|
|
|
|
#
|
|
|
|
# Permission levels:
|
|
|
|
#
|
|
|
|
# Disabled: not enabled for anyone
|
|
|
|
# Private: enabled only for team members
|
|
|
|
# Enabled: enabled for everyone able to access the project
|
|
|
|
# Public: enabled for everyone (only allowed for pages)
|
|
|
|
DISABLED = 0
|
|
|
|
PRIVATE = 10
|
|
|
|
ENABLED = 20
|
|
|
|
PUBLIC = 30
|
|
|
|
|
|
|
|
STRING_OPTIONS = HashWithIndifferentAccess.new({
|
|
|
|
'disabled' => DISABLED,
|
|
|
|
'private' => PRIVATE,
|
|
|
|
'enabled' => ENABLED,
|
|
|
|
'public' => PUBLIC
|
|
|
|
}).freeze
|
|
|
|
|
|
|
|
class_methods do
|
|
|
|
def set_available_features(available_features = [])
|
2020-11-05 07:09:05 -05:00
|
|
|
@available_features ||= []
|
|
|
|
@available_features += available_features
|
2020-05-26 05:08:06 -04:00
|
|
|
|
|
|
|
class_eval do
|
|
|
|
available_features.each do |feature|
|
|
|
|
define_method("#{feature}_enabled?") do
|
|
|
|
public_send("#{feature}_access_level") > DISABLED # rubocop:disable GitlabSecurity/PublicSend
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def available_features
|
2022-04-07 08:10:21 -04:00
|
|
|
@available_features || []
|
2020-05-26 05:08:06 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def access_level_attribute(feature)
|
|
|
|
feature = ensure_feature!(feature)
|
|
|
|
|
|
|
|
"#{feature}_access_level".to_sym
|
|
|
|
end
|
|
|
|
|
|
|
|
def quoted_access_level_column(feature)
|
|
|
|
attribute = connection.quote_column_name(access_level_attribute(feature))
|
|
|
|
table = connection.quote_table_name(table_name)
|
|
|
|
|
|
|
|
"#{table}.#{attribute}"
|
|
|
|
end
|
|
|
|
|
|
|
|
def access_level_from_str(level)
|
|
|
|
STRING_OPTIONS.fetch(level)
|
|
|
|
end
|
|
|
|
|
|
|
|
def str_from_access_level(level)
|
|
|
|
STRING_OPTIONS.key(level)
|
|
|
|
end
|
|
|
|
|
2022-04-07 08:10:21 -04:00
|
|
|
def required_minimum_access_level(feature)
|
|
|
|
ensure_feature!(feature)
|
|
|
|
|
|
|
|
Gitlab::Access::GUEST
|
|
|
|
end
|
|
|
|
|
2020-05-26 05:08:06 -04:00
|
|
|
def ensure_feature!(feature)
|
|
|
|
feature = feature.model_name.plural if feature.respond_to?(:model_name)
|
|
|
|
feature = feature.to_sym
|
|
|
|
raise ArgumentError, "invalid feature: #{feature}" unless available_features.include?(feature)
|
|
|
|
|
|
|
|
feature
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2021-08-24 05:10:45 -04:00
|
|
|
included do
|
|
|
|
validate :allowed_access_levels
|
|
|
|
end
|
|
|
|
|
2020-05-26 05:08:06 -04:00
|
|
|
def access_level(feature)
|
|
|
|
public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
|
|
|
|
end
|
|
|
|
|
2022-04-07 08:10:21 -04:00
|
|
|
def feature_available?(feature, user = nil)
|
|
|
|
has_permission?(user, feature)
|
2020-05-26 05:08:06 -04:00
|
|
|
end
|
|
|
|
|
|
|
|
def string_access_level(feature)
|
|
|
|
self.class.str_from_access_level(access_level(feature))
|
|
|
|
end
|
2021-08-24 05:10:45 -04:00
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def allowed_access_levels
|
|
|
|
validator = lambda do |field|
|
|
|
|
level = public_send(field) || ENABLED # rubocop:disable GitlabSecurity/PublicSend
|
|
|
|
not_allowed = level > ENABLED
|
|
|
|
self.errors.add(field, "cannot have public visibility level") if not_allowed
|
|
|
|
end
|
|
|
|
|
|
|
|
(self.class.available_features - feature_validation_exclusion).each {|f| validator.call("#{f}_access_level")}
|
|
|
|
end
|
|
|
|
|
|
|
|
# Features that we should exclude from the validation
|
|
|
|
def feature_validation_exclusion
|
|
|
|
[]
|
|
|
|
end
|
2022-04-07 08:10:21 -04:00
|
|
|
|
|
|
|
def has_permission?(user, feature)
|
|
|
|
case access_level(feature)
|
|
|
|
when DISABLED
|
|
|
|
false
|
|
|
|
when PRIVATE
|
|
|
|
member?(user, feature)
|
|
|
|
when ENABLED
|
|
|
|
true
|
|
|
|
when PUBLIC
|
|
|
|
true
|
|
|
|
else
|
|
|
|
true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def member?(user, feature)
|
|
|
|
return false unless user
|
|
|
|
return true if user.can_read_all_resources?
|
|
|
|
|
|
|
|
resource_member?(user, feature)
|
|
|
|
end
|
|
|
|
|
|
|
|
def resource_member?(user, feature)
|
|
|
|
raise NotImplementedError
|
|
|
|
end
|
2020-05-26 05:08:06 -04:00
|
|
|
end
|