Completed
Branch v2.4-alpha (b4736b)
by Ahmad
05:54
created

User   F

Complexity

Total Complexity 63

Size/Duplication

Total Lines 306
Duplicated Lines 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
dl 0
loc 306
rs 3.36
c 3
b 0
f 0
wmc 63

17 Methods

Rating   Name   Duplication   Size   Complexity  
B add_role() 0 17 6
A remove_role() 0 8 3
A new_token() 0 3 1
A with_role() 0 3 1
A check_if_email_can_be_blank() 0 9 5
A all_users_with_roles() 0 4 1
A admins_order() 0 4 1
A admin_of? 0 12 3
A activate() 0 5 1
A digest() 0 4 2
A assign_default_role() 0 7 4
A auth_roles() 0 11 3
A from_omniauth() 0 13 2
A authenticated? 0 5 2
A admins_search() 0 28 3
A without_role() 0 3 1
A highest_priority_role() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like User often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# frozen_string_literal: true
2
3
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/.
4
#
5
# Copyright (c) 2018 BigBlueButton Inc. and by respective authors (see below).
6
#
7
# This program is free software; you can redistribute it and/or modify it under the
8
# terms of the GNU Lesser General Public License as published by the Free Software
9
# Foundation; either version 3.0 of the License, or (at your option) any later
10
# version.
11
#
12
# BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
13
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
14
# PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
15
#
16
# You should have received a copy of the GNU Lesser General Public License along
17
# with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
18
19
require 'bbb_api'
20
21
class User < ApplicationRecord
22
  attr_accessor :reset_token
23
  after_create :assign_default_role
24
  after_create :initialize_main_room
25
26
  before_save { email.try(:downcase!) }
27
28
  before_destroy :destroy_rooms
29
30
  has_many :rooms
31
  belongs_to :main_room, class_name: 'Room', foreign_key: :room_id, required: false
32
33
  has_and_belongs_to_many :roles, join_table: :users_roles
34
35
  validates :name, length: { maximum: 256 }, presence: true
36
  validates :provider, presence: true
37
  validate :check_if_email_can_be_blank
38
  validates :email, length: { maximum: 256 }, allow_blank: true,
39
                    uniqueness: { case_sensitive: false, scope: :provider },
40
                    format: { with: /\A[\w+\-.]+@[a-z\d\-.]+\.[a-z]+\z/i }
41
42
  validates :password, length: { minimum: 6 }, confirmation: true, if: :greenlight_account?, on: :create
43
44
  # Bypass validation if omniauth
45
  validates :accepted_terms, acceptance: true,
46
                             unless: -> { !greenlight_account? || !Rails.configuration.terms }
47
48
  # We don't want to require password validations on all accounts.
49
  has_secure_password(validations: false)
50
51
  class << self
52
    # Generates a user from omniauth.
53
    def from_omniauth(auth)
54
      # Provider is the customer name if in loadbalanced config mode
55
      provider = auth['provider'] == "bn_launcher" ? auth['info']['customer'] : auth['provider']
56
      find_or_initialize_by(social_uid: auth['uid'], provider: provider).tap do |u|
57
        u.name = auth_name(auth) unless u.name
58
        u.username = auth_username(auth) unless u.username
59
        u.email = auth_email(auth)
60
        u.image = auth_image(auth) unless u.image
61
        auth_roles(u, auth)
62
        u.email_verified = true
63
        u.save!
64
      end
65
    end
66
67
    private
68
69
    # Provider attributes.
70
    def auth_name(auth)
71
      case auth['provider']
72
      when :office365
73
        auth['info']['display_name']
74
      else
75
        auth['info']['name']
76
      end
77
    end
78
79
    def auth_username(auth)
80
      case auth['provider']
81
      when :google
82
        auth['info']['email'].split('@').first
83
      when :bn_launcher
84
        auth['info']['username']
85
      else
86
        auth['info']['nickname']
87
      end
88
    end
89
90
    def auth_email(auth)
91
      auth['info']['email']
92
    end
93
94
    def auth_image(auth)
95
      case auth['provider']
96
      when :twitter
97
        auth['info']['image'].gsub("http", "https").gsub("_normal", "")
98
      else
99
        auth['info']['image']
100
      end
101
    end
102
103
    def auth_roles(user, auth)
104
      unless auth['info']['roles'].nil?
105
        roles = auth['info']['roles'].split(',')
106
107
        role_provider = auth['provider'] == "bn_launcher" ? auth['info']['customer'] : "greenlight"
108
        roles.each do |role_name|
109
          role = Role.where(provider: role_provider, name: role_name).first
110
          user.roles << role unless role.nil?
111
        end
112
      end
113
    end
114
  end
115
116
  def self.admins_search(string, role)
117
    active_database = Rails.configuration.database_configuration[Rails.env]["adapter"]
118
    # Postgres requires created_at to be cast to a string
119
    created_at_query = if active_database == "postgresql"
120
      "created_at::text"
121
    else
122
      "created_at"
123
    end
124
125
    search_query = ""
126
    role_search_param = ""
127
    if role.nil?
128
      search_query = "users.name LIKE :search OR email LIKE :search OR username LIKE :search" \
129
                    " OR users.#{created_at_query} LIKE :search OR users.provider LIKE :search" \
130
                    " OR roles.name LIKE :roles_search"
131
      role_search_param = "%#{string}%"
132
    else
133
      search_query = "(users.name LIKE :search OR email LIKE :search OR username LIKE :search" \
134
                    " OR users.#{created_at_query} LIKE :search OR users.provider LIKE :search)" \
135
                    " AND roles.name = :roles_search"
136
      role_search_param = role.name
137
    end
138
139
    search_param = "%#{string}%"
140
    joins("LEFT OUTER JOIN users_roles ON users_roles.user_id = users.id LEFT OUTER JOIN roles " \
141
      "ON roles.id = users_roles.role_id").distinct
142
      .where(search_query, search: search_param, roles_search: role_search_param)
143
  end
144
145
  def self.admins_order(column, direction)
146
    # Arel.sql to avoid sql injection
147
    order(Arel.sql("#{column} #{direction}"))
148
  end
149
150
  # Activates an account and initialize a users main room
151
  def activate
152
    update_attribute(:email_verified, true)
153
    update_attribute(:activated_at, Time.zone.now)
154
    save
155
  end
156
157
  def activated?
158
    return true unless Rails.configuration.enable_email_verification
159
    email_verified
160
  end
161
162
  # Sets the password reset attributes.
163
  def create_reset_digest
164
    self.reset_token = User.new_token
165
    update_attribute(:reset_digest,  User.digest(reset_token))
166
    update_attribute(:reset_sent_at, Time.zone.now)
167
  end
168
169
  # Returns true if the given token matches the digest.
170
  def authenticated?(attribute, token)
171
    digest = send("#{attribute}_digest")
172
    return false if digest.nil?
173
    BCrypt::Password.new(digest).is_password?(token)
174
  end
175
176
  # Return true if password reset link expires
177
  def password_reset_expired?
178
    reset_sent_at < 2.hours.ago
179
  end
180
181
  # Retrives a list of all a users rooms that are not the main room, sorted by last session date.
182
  def secondary_rooms
183
    secondary = (rooms - [main_room])
184
    no_session, session = secondary.partition { |r| r.last_session.nil? }
185
    sorted = session.sort_by(&:last_session)
186
    sorted + no_session
187
  end
188
189
  def name_chunk
190
    charset = ("a".."z").to_a - %w(b i l o s) + ("2".."9").to_a - %w(5 8)
191
    chunk = name.parameterize[0...3]
192
    if chunk.empty?
193
      chunk + (0...3).map { charset.to_a[rand(charset.size)] }.join
194
    elsif chunk.length == 1
195
      chunk + (0...2).map { charset.to_a[rand(charset.size)] }.join
196
    elsif chunk.length == 2
197
      chunk + (0...1).map { charset.to_a[rand(charset.size)] }.join
198
    else
199
      chunk
200
    end
201
  end
202
203
  def greenlight_account?
204
    social_uid.nil?
205
  end
206
207
  def activation_token
208
    # Create the token.
209
    create_reset_activation_digest(User.new_token)
210
  end
211
212
  def admin_of?(user)
213
    if Rails.configuration.loadbalanced_configuration
214
      if has_role? :super_admin
215
        id != user.id
216
      else
217
        highest_priority_role.can_manage_users && (id != user.id) && (provider == user.provider) &&
218
          (!user.has_role? :super_admin)
219
      end
220
    else
221
      (highest_priority_role.can_manage_users || (has_role? :super_admin)) && (id != user.id)
222
    end
223
  end
224
225
  def self.digest(string)
226
    cost = ActiveModel::SecurePassword.min_cost ? BCrypt::Engine::MIN_COST : BCrypt::Engine.cost
227
    BCrypt::Password.create(string, cost: cost)
228
  end
229
230
  # Returns a random token.
231
  def self.new_token
232
    SecureRandom.urlsafe_base64
233
  end
234
235
  # role functions
236
  def highest_priority_role
237
    roles.by_priority.first
238
  end
239
240
  def add_role(role)
241
    unless has_role?(role)
242
      role_provider = Rails.configuration.loadbalanced_configuration ? provider : "greenlight"
243
244
      new_role = Role.find_by(name: role, provider: role_provider)
245
246
      if new_role.nil?
247
        return if Role.duplicate_name(role, role_provider) || role.strip.empty?
248
249
        new_role = Role.create_new_role(role, role_provider)
250
      end
251
252
      roles << new_role
253
254
      save!
255
    end
256
  end
257
258
  def remove_role(role)
259
    if has_role?(role)
260
      role_provider = Rails.configuration.loadbalanced_configuration ? provider : "greenlight"
261
262
      roles.delete(Role.find_by(name: role, provider: role_provider))
263
      save!
264
    end
265
  end
266
267
  # This rule is disabled as the function name must be has_role?
268
  # rubocop:disable Naming/PredicateName
269
  def has_role?(role)
270
    # rubocop:enable Naming/PredicateName
271
    roles.exists?(name: role)
272
  end
273
274
  def self.with_role(role)
275
    User.all_users_with_roles.where(roles: { name: role })
276
  end
277
278
  def self.without_role(role)
279
    User.where.not(id: with_role(role).pluck(:id))
280
  end
281
282
  def self.all_users_with_roles
283
    User.joins("INNER JOIN users_roles ON users_roles.user_id = users.id INNER JOIN roles " \
284
      "ON roles.id = users_roles.role_id")
285
  end
286
287
  private
288
289
  def create_reset_activation_digest(token)
290
    # Create the digest and persist it.
291
    self.activation_digest = User.digest(token)
292
    save
293
    token
294
  end
295
296
  # Destory a users rooms when they are removed.
297
  def destroy_rooms
298
    rooms.destroy_all
299
  end
300
301
  # Initializes a room for the user and assign a BigBlueButton user id.
302
  def initialize_main_room
303
    self.uid = "gl-#{(0...12).map { rand(65..90).chr }.join.downcase}"
304
    self.main_room = Room.create!(owner: self, name: I18n.t("home_room"))
305
    save
306
  end
307
308
  # Initialize the user to use the default user role
309
  def assign_default_role
310
    role_provider = Rails.configuration.loadbalanced_configuration ? provider : "greenlight"
311
312
    Role.create_default_roles(role_provider) if Role.where(provider: role_provider).count.zero?
313
314
    add_role(:user) if roles.blank?
315
  end
316
317
  def check_if_email_can_be_blank
318
    if email.blank?
319
      if Rails.configuration.loadbalanced_configuration && greenlight_account?
320
        errors.add(:email, I18n.t("errors.messages.blank"))
321
      elsif provider == "greenlight"
322
        errors.add(:email, I18n.t("errors.messages.blank"))
323
      end
324
    end
325
  end
326
end
327