1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
|
# 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 = [])
@available_features ||= []
@available_features += available_features
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
@available_features || []
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
def required_minimum_access_level(feature)
ensure_feature!(feature)
Gitlab::Access::GUEST
end
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
included do
validate :allowed_access_levels
end
def access_level(feature)
public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
end
def feature_available?(feature, user = nil)
has_permission?(user, feature)
end
def string_access_level(feature)
self.class.str_from_access_level(access_level(feature))
end
private
def allowed_access_levels
validator = ->(field) do
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
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
end
|