member.rb 4.8 KB
Newer Older
Valery Sizov's avatar
Valery Sizov committed
1 2 3 4 5 6 7 8
# == Schema Information
#
# Table name: members
#
#  id                 :integer          not null, primary key
#  access_level       :integer          not null
#  source_id          :integer          not null
#  source_type        :string(255)      not null
Stan Hu's avatar
Stan Hu committed
9
#  user_id            :integer
Valery Sizov's avatar
Valery Sizov committed
10 11 12 13
#  notification_level :integer          not null
#  type               :string(255)
#  created_at         :datetime
#  updated_at         :datetime
14
#  created_by_id      :integer
Stan Hu's avatar
Stan Hu committed
15 16
#  invite_email       :string(255)
#  invite_token       :string(255)
17
#  invite_accepted_at :datetime
Valery Sizov's avatar
Valery Sizov committed
18 19
#

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
20
class Member < ActiveRecord::Base
21
  include Sortable
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
22 23 24
  include Notifiable
  include Gitlab::Access

25 26
  attr_accessor :raw_invite_token

27
  belongs_to :created_by, class_name: "User"
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
28 29 30
  belongs_to :user
  belongs_to :source, polymorphic: true

Douwe Maan's avatar
Douwe Maan committed
31
  validates :user, presence: true, unless: :invite?
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
32
  validates :source, presence: true
33
  validates :user_id, uniqueness: { scope: [:source_type, :source_id],
Douwe Maan's avatar
Douwe Maan committed
34 35
                                    message: "already exists in source",
                                    allow_nil: true }
36
  validates :access_level, inclusion: { in: Gitlab::Access.all_values }, presence: true
Douwe Maan's avatar
Douwe Maan committed
37 38 39 40 41 42 43 44 45 46 47 48
  validates :invite_email,
    presence: {
      if: :invite?
    },
    email: {
      strict_mode: true,
      allow_nil: true
    },
    uniqueness: {
      scope: [:source_type, :source_id],
      allow_nil: true
    }
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
49

Douwe Maan's avatar
Douwe Maan committed
50 51
  scope :invite, -> { where(user_id: nil) }
  scope :non_invite, -> { where("user_id IS NOT NULL") }
52 53 54 55 56 57
  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
58 59 60 61 62 63
  before_validation :generate_invite_token, on: :create, if: -> (member) { member.invite_email.present? }
  after_create :send_invite, if: :invite?
  after_create :post_create_hook, unless: :invite?
  after_update :post_update_hook, unless: :invite?
  after_destroy :post_destroy_hook, unless: :invite?

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

66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84
  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)
85

86 87 88 89 90 91 92
      # `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
93

94
      if can_update_member?(current_user, member)
95 96
        member.created_by ||= current_user
        member.access_level = access_level
97

98 99
        member.save
      end
100
    end
101 102 103

    private

104
    def can_update_member?(current_user, member)
Douwe Maan's avatar
Douwe Maan committed
105 106 107
      # There is no current user for bulk actions, in which case anything is allowed
      !current_user ||
        current_user.can?(:update_group_member, member) ||
108
        current_user.can?(:update_project_member, member)
109
    end
Douwe Maan's avatar
Douwe Maan committed
110 111
  end

Douwe Maan's avatar
Douwe Maan committed
112 113 114 115 116
  def invite?
    self.invite_token.present?
  end

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

Douwe Maan's avatar
Douwe Maan committed
119 120 121 122 123 124 125 126 127 128 129 130
    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
131 132 133 134 135 136 137 138 139 140
  def decline_invite!
    return false unless invite?

    destroyed = self.destroy

    after_decline_invite if destroyed

    destroyed
  end

Douwe Maan's avatar
Douwe Maan committed
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180
  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

  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
181 182 183 184
  def after_decline_invite
    # override in subclass
  end

Douwe Maan's avatar
Douwe Maan committed
185 186 187 188 189 190 191
  def system_hook_service
    SystemHooksService.new
  end

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