summaryrefslogtreecommitdiff
path: root/app/models/concerns/featurable.rb
blob: ed9bce87da1c1f7751699f5042db2087c7d8063d (plain)
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
# 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 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)
    get_permission(user, feature)
  end

  def string_access_level(feature)
    self.class.str_from_access_level(access_level(feature))
  end
end