OSDN Git Service

Merge branch 'feature/refactoring_scopes_pr' of https://github.com/Undev/gitlabhq...
[wvm/gitlab.git] / app / models / user.rb
1 # == Schema Information
2 #
3 # Table name: users
4 #
5 #  id                     :integer          not null, primary key
6 #  email                  :string(255)      default(""), not null
7 #  encrypted_password     :string(255)      default(""), not null
8 #  reset_password_token   :string(255)
9 #  reset_password_sent_at :datetime
10 #  remember_created_at    :datetime
11 #  sign_in_count          :integer          default(0)
12 #  current_sign_in_at     :datetime
13 #  last_sign_in_at        :datetime
14 #  current_sign_in_ip     :string(255)
15 #  last_sign_in_ip        :string(255)
16 #  created_at             :datetime         not null
17 #  updated_at             :datetime         not null
18 #  name                   :string(255)
19 #  admin                  :boolean          default(FALSE), not null
20 #  projects_limit         :integer          default(10)
21 #  skype                  :string(255)      default(""), not null
22 #  linkedin               :string(255)      default(""), not null
23 #  twitter                :string(255)      default(""), not null
24 #  authentication_token   :string(255)
25 #  theme_id               :integer          default(1), not null
26 #  bio                    :string(255)
27 #  failed_attempts        :integer          default(0)
28 #  locked_at              :datetime
29 #  extern_uid             :string(255)
30 #  provider               :string(255)
31 #  username               :string(255)
32 #  can_create_group       :boolean          default(TRUE), not null
33 #  can_create_team        :boolean          default(TRUE), not null
34 #  state                  :string(255)
35 #  color_scheme_id        :integer          default(1), not null
36 #  notification_level     :integer          default(1), not null
37 #
38
39 class User < ActiveRecord::Base
40   devise :database_authenticatable, :token_authenticatable, :lockable,
41          :recoverable, :rememberable, :trackable, :validatable, :omniauthable, :registerable
42
43   attr_accessible :email, :password, :password_confirmation, :remember_me, :bio, :name, :username,
44                   :skype, :linkedin, :twitter, :color_scheme_id, :theme_id, :force_random_password,
45                   :extern_uid, :provider, as: [:default, :admin]
46   attr_accessible :projects_limit, :can_create_team, :can_create_group, as: :admin
47
48   attr_accessor :force_random_password
49
50   # Virtual attribute for authenticating by either username or email
51   attr_accessor :login
52
53   # Add login to attr_accessible
54   attr_accessible :login
55
56
57   #
58   # Relations
59   #
60
61   # Namespace for personal projects
62   has_one :namespace, dependent: :destroy, foreign_key: :owner_id, class_name: "Namespace", conditions: 'type IS NULL'
63
64   # Namespaces (owned groups and own namespace)
65   has_many :namespaces, foreign_key: :owner_id
66
67   # Profile
68   has_many :keys, dependent: :destroy
69
70   # Groups
71   has_many :groups, class_name: "Group", foreign_key: :owner_id
72
73   # Teams
74   has_many :own_teams,                       dependent: :destroy, class_name: "UserTeam", foreign_key: :owner_id
75   has_many :user_team_user_relationships,    dependent: :destroy
76   has_many :user_teams,                      through: :user_team_user_relationships
77   has_many :user_team_project_relationships, through: :user_teams
78   has_many :team_projects,                   through: :user_team_project_relationships
79
80   # Projects
81   has_many :users_projects,           dependent: :destroy
82   has_many :issues,                   dependent: :destroy, foreign_key: :author_id
83   has_many :notes,                    dependent: :destroy, foreign_key: :author_id
84   has_many :merge_requests,           dependent: :destroy, foreign_key: :author_id
85   has_many :events,                   dependent: :destroy, foreign_key: :author_id,   class_name: "Event"
86   has_many :recent_events,                                 foreign_key: :author_id,   class_name: "Event", order: "id DESC"
87   has_many :assigned_issues,          dependent: :destroy, foreign_key: :assignee_id, class_name: "Issue"
88   has_many :assigned_merge_requests,  dependent: :destroy, foreign_key: :assignee_id, class_name: "MergeRequest"
89
90   has_many :personal_projects,        through: :namespace, source: :projects
91   has_many :projects,                 through: :users_projects
92   has_many :own_projects,             foreign_key: :creator_id
93   has_many :owned_projects,           through: :namespaces, source: :projects
94
95   #
96   # Validations
97   #
98   validates :name, presence: true
99   validates :email, presence: true, format: { with: /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/ }
100   validates :bio, length: { within: 0..255 }
101   validates :extern_uid, allow_blank: true, uniqueness: {scope: :provider}
102   validates :projects_limit, presence: true, numericality: {greater_than_or_equal_to: 0}
103   validates :username, presence: true, uniqueness: true,
104             format: { with: Gitlab::Regex.username_regex,
105                       message: "only letters, digits & '_' '-' '.' allowed. Letter should be first" }
106
107   validates :notification_level, inclusion: { in: Notification.notification_levels }, presence: true
108
109   validate :namespace_uniq, if: ->(user) { user.username_changed? }
110
111   before_validation :generate_password, on: :create
112   before_save :ensure_authentication_token
113   alias_attribute :private_token, :authentication_token
114
115   delegate :path, to: :namespace, allow_nil: true, prefix: true
116
117   state_machine :state, initial: :active do
118     after_transition any => :blocked do |user, transition|
119       # Remove user from all projects and
120       user.users_projects.find_each do |membership|
121         return false unless membership.destroy
122       end
123     end
124
125     event :block do
126       transition active: :blocked
127     end
128
129     event :activate do
130       transition blocked: :active
131     end
132   end
133
134   # Scopes
135   scope :admins, -> { where(admin:  true) }
136   scope :blocked, -> { with_state(:blocked) }
137   scope :active, -> { with_state(:active) }
138   scope :alphabetically, -> { order('name ASC') }
139   scope :in_team, ->(team){ where(id: team.member_ids) }
140   scope :not_in_team, ->(team){ where('users.id NOT IN (:ids)', ids: team.member_ids) }
141   scope :not_in_project, ->(project) { project.users.present? ? where("id not in (:ids)", ids: project.users.map(&:id) ) : scoped }
142   scope :without_projects, -> { where('id NOT IN (SELECT DISTINCT(user_id) FROM users_projects)') }
143
144   scope :potential_team_members, ->(team) { team.members.any? ? active.not_in_team(team) : active  }
145
146   #
147   # Class methods
148   #
149   class << self
150     # Devise method overriden to allow sing in with email or username
151     def find_for_database_authentication(warden_conditions)
152       conditions = warden_conditions.dup
153       if login = conditions.delete(:login)
154         where(conditions).where(["lower(username) = :value OR lower(email) = :value", { value: login.downcase }]).first
155       else
156         where(conditions).first
157       end
158     end
159
160     def filter filter_name
161       case filter_name
162       when "admins"; self.admins
163       when "blocked"; self.blocked
164       when "wop"; self.without_projects
165       else
166         self.active
167       end
168     end
169
170     def create_from_omniauth(auth, ldap = false)
171       gitlab_auth.create_from_omniauth(auth, ldap)
172     end
173
174     def find_or_new_for_omniauth(auth)
175       gitlab_auth.find_or_new_for_omniauth(auth)
176     end
177
178     def find_for_ldap_auth(auth, signed_in_resource = nil)
179       gitlab_auth.find_for_ldap_auth(auth, signed_in_resource)
180     end
181
182     def gitlab_auth
183       Gitlab::Auth.new
184     end
185
186     def search query
187       where("name LIKE :query OR email LIKE :query OR username LIKE :query", query: "%#{query}%")
188     end
189   end
190
191   #
192   # Instance methods
193   #
194
195   def to_param
196     username
197   end
198
199   def notification
200     @notification ||= Notification.new(self)
201   end
202
203   def generate_password
204     if self.force_random_password
205       self.password = self.password_confirmation = Devise.friendly_token.first(8)
206     end
207   end
208
209   def namespace_uniq
210     namespace_name = self.username
211     if Namespace.find_by_path(namespace_name)
212       self.errors.add :username, "already exist"
213     end
214   end
215
216   # Groups where user is an owner
217   def owned_groups
218     groups
219   end
220
221   def owned_teams
222     own_teams
223   end
224
225   # Groups user has access to
226   def authorized_groups
227     @group_ids ||= (groups.pluck(:id) + authorized_projects.pluck(:namespace_id))
228     Group.where(id: @group_ids)
229   end
230
231
232   # Projects user has access to
233   def authorized_projects
234     @project_ids ||= (owned_projects.pluck(:id) + projects.pluck(:id)).uniq
235     Project.where(id: @project_ids)
236   end
237
238   def authorized_teams
239     @team_ids ||= (user_teams.pluck(:id) + own_teams.pluck(:id)).uniq
240     UserTeam.where(id: @team_ids)
241   end
242
243   # Team membership in authorized projects
244   def tm_in_authorized_projects
245     UsersProject.where(project_id: authorized_projects.map(&:id), user_id: self.id)
246   end
247
248   def is_admin?
249     admin
250   end
251
252   def require_ssh_key?
253     keys.count == 0
254   end
255
256   def can_change_username?
257     Gitlab.config.gitlab.username_changing_enabled
258   end
259
260   def can_create_project?
261     projects_limit > owned_projects.count
262   end
263
264   def can_create_group?
265     can?(:create_group, nil)
266   end
267
268   def abilities
269     @abilities ||= begin
270                      abilities = Six.new
271                      abilities << Ability
272                      abilities
273                    end
274   end
275
276   def can_select_namespace?
277     several_namespaces? || admin
278   end
279
280   def can? action, subject
281     abilities.allowed?(self, action, subject)
282   end
283
284   def first_name
285     name.split.first unless name.blank?
286   end
287
288   def cared_merge_requests
289     MergeRequest.cared(self)
290   end
291
292   def projects_limit_left
293     projects_limit - owned_projects.count
294   end
295
296   def projects_limit_percent
297     return 100 if projects_limit.zero?
298     (owned_projects.count.to_f / projects_limit) * 100
299   end
300
301   def recent_push project_id = nil
302     # Get push events not earlier than 2 hours ago
303     events = recent_events.code_push.where("created_at > ?", Time.now - 2.hours)
304     events = events.where(project_id: project_id) if project_id
305
306     # Take only latest one
307     events = events.recent.limit(1).first
308   end
309
310   def projects_sorted_by_activity
311     authorized_projects.sorted_by_activity
312   end
313
314   def several_namespaces?
315     namespaces.many?
316   end
317
318   def namespace_id
319     namespace.try :id
320   end
321
322   def name_with_username
323     "#{name} (#{username})"
324   end
325
326   def tm_of(project)
327     project.team_member_by_id(self.id)
328   end
329 end