member.rb 4.72 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1
class Member < ActiveRecord::Base
2
  include Sortable
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
3 4
  include Gitlab::Access

5 6
  attr_accessor :raw_invite_token

7
  belongs_to :created_by, class_name: "User"
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
8 9 10
  belongs_to :user
  belongs_to :source, polymorphic: true

Douwe Maan's avatar
Douwe Maan committed
11
  validates :user, presence: true, unless: :invite?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
12
  validates :source, presence: true
13
  validates :user_id, uniqueness: { scope: [:source_type, :source_id],
Douwe Maan's avatar
Douwe Maan committed
14 15
                                    message: "already exists in source",
                                    allow_nil: true }
16
  validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
Douwe Maan's avatar
Douwe Maan committed
17 18 19 20
  validates :invite_email,
    presence: {
      if: :invite?
    },
21
    email: {
Douwe Maan's avatar
Douwe Maan committed
22 23 24 25 26 27
      allow_nil: true
    },
    uniqueness: {
      scope: [:source_type, :source_id],
      allow_nil: true
    }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
28

Douwe Maan's avatar
Douwe Maan committed
29 30
  scope :invite, -> { where(user_id: nil) }
  scope :non_invite, -> { where("user_id IS NOT NULL") }
31 32 33 34 35 36
  scope :guests, -> { where(access_level: GUEST) }
  scope :reporters, -> { where(access_level: REPORTER) }
  scope :developers, -> { where(access_level: DEVELOPER) }
  scope :masters,  -> { where(access_level: MASTER) }
  scope :owners,  -> { where(access_level: OWNER) }

Douwe Maan's avatar
Douwe Maan committed
37 38
  before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
  after_create :send_invite, if: :invite?
39
  after_create :create_notification_setting, unless: :invite?
Douwe Maan's avatar
Douwe Maan committed
40 41 42 43
  after_create :post_create_hook, unless: :invite?
  after_update :post_update_hook, unless: :invite?
  after_destroy :post_destroy_hook, unless: :invite?

44
  delegate :name, :username, :email, to: :user, prefix: true
Douwe Maan's avatar
Douwe Maan committed
45

46 47
  default_value_for :notification_level, NotificationSetting.levels[:global]

48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
  class << self
    def find_by_invite_token(invite_token)
      invite_token = Devise.token_generator.digest(self, :invite_token, invite_token)
      find_by(invite_token: invite_token)
    end

    # This method is used to find users that have been entered into the "Add members" field.
    # These can be the User objects directly, their IDs, their emails, or new emails to be invited.
    def user_for_id(user_id)
      return user_id if user_id.is_a?(User)

      user = User.find_by(id: user_id)
      user ||= User.find_by(email: user_id)
      user ||= user_id
      user
    end

    def add_user(members, user_id, access_level, current_user = nil)
      user = user_for_id(user_id)
67

68 69 70 71 72 73 74
      # `user` can be either a User object or an email to be invited
      if user.is_a?(User)
        member = members.find_or_initialize_by(user_id: user.id)
      else
        member = members.build
        member.invite_email = user
      end
75

76
      if can_update_member?(current_user, member) || project_creator?(member, access_level)
77 78
        member.created_by ||= current_user
        member.access_level = access_level
79

80 81
        member.save
      end
82
    end
83 84 85

    private

86
    def can_update_member?(current_user, member)
Douwe Maan's avatar
Douwe Maan committed
87 88 89
      # There is no current user for bulk actions, in which case anything is allowed
      !current_user ||
        current_user.can?(:update_group_member, member) ||
90
        current_user.can?(:update_project_member, member)
91
    end
92 93 94 95 96

    def project_creator?(member, access_level)
      member.new_record? && member.owner? &&
        access_level.to_i == ProjectMember::MASTER
    end
Douwe Maan's avatar
Douwe Maan committed
97 98
  end

Douwe Maan's avatar
Douwe Maan committed
99 100 101 102 103
  def invite?
    self.invite_token.present?
  end

  def accept_invite!(new_user)
Douwe Maan's avatar
Douwe Maan committed
104
    return false unless invite?
105

Douwe Maan's avatar
Douwe Maan committed
106 107 108 109 110 111 112 113 114 115 116 117
    self.invite_token = nil
    self.invite_accepted_at = Time.now.utc

    self.user = new_user

    saved = self.save

    after_accept_invite if saved

    saved
  end

Douwe Maan's avatar
Douwe Maan committed
118 119 120 121 122 123 124 125 126 127
  def decline_invite!
    return false unless invite?

    destroyed = self.destroy

    after_decline_invite if destroyed

    destroyed
  end

Douwe Maan's avatar
Douwe Maan committed
128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145
  def generate_invite_token
    raw, enc = Devise.token_generator.generate(self.class, :invite_token)
    @raw_invite_token = raw
    self.invite_token = enc
  end

  def generate_invite_token!
    generate_invite_token && save(validate: false)
  end

  def resend_invite
    return unless invite?

    generate_invite_token! unless @raw_invite_token

    send_invite
  end

146
  def create_notification_setting
147
    user.notification_settings.find_or_create_for(source)
148 149
  end

150
  def notification_setting
151
    @notification_setting ||= user.notification_settings_for(source)
152 153
  end

Douwe Maan's avatar
Douwe Maan committed
154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175
  private

  def send_invite
    # override in subclass
  end

  def post_create_hook
    system_hook_service.execute_hooks_for(self, :create)
  end

  def post_update_hook
    # override in subclass
  end

  def post_destroy_hook
    system_hook_service.execute_hooks_for(self, :destroy)
  end

  def after_accept_invite
    post_create_hook
  end

Douwe Maan's avatar
Douwe Maan committed
176 177 178 179
  def after_decline_invite
    # override in subclass
  end

Douwe Maan's avatar
Douwe Maan committed
180 181 182 183 184 185 186
  def system_hook_service
    SystemHooksService.new
  end

  def notification_service
    NotificationService.new
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
187
end