summaryrefslogtreecommitdiff
path: root/app/models/concerns/featurable.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/models/concerns/featurable.rb')
-rw-r--r--app/models/concerns/featurable.rb99
1 files changed, 99 insertions, 0 deletions
diff --git a/app/models/concerns/featurable.rb b/app/models/concerns/featurable.rb
new file mode 100644
index 00000000000..60aa46ce04c
--- /dev/null
+++ b/app/models/concerns/featurable.rb
@@ -0,0 +1,99 @@
+# 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
+
+ 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 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
+
+ def access_level(feature)
+ public_send(self.class.access_level_attribute(feature)) # rubocop:disable GitlabSecurity/PublicSend
+ end
+
+ def feature_available?(feature, user)
+ # This feature might not be behind a feature flag at all, so default to true
+ return false unless ::Feature.enabled?(feature, user, default_enabled: true)
+
+ get_permission(user, feature)
+ end
+
+ def string_access_level(feature)
+ self.class.str_from_access_level(access_level(feature))
+ end
+end