summaryrefslogtreecommitdiff
path: root/app/models/account.rb
blob: 4442a6836e5c30e7ea3cda086c59aa9aa0401baf (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
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#
# The Account model takes care of the lifecycle of a user.
# It composes a User record and it's identity records.
# It also allows for other engines to hook into the lifecycle by
# monkeypatching the create, update and destroy methods.
# There's an ActiveSupport load_hook at the end of this file to
# make this more easy.
#
class Account

  attr_reader :user

  def initialize(user = nil)
    @user = user
  end

  #
  # Creates a new user, with matching identity record.
  #
  # Returns the user record so it can be used in views.
  #
  # options:
  #
  #  :invite_required -- if 'false', will overrides app-wide
  #                      configuration by same name.
  #
  def self.create(attrs, options={})
    identity = nil
    user = nil
    user = User.new(attrs)
    if options[:invite_required] == false
      user.ignore_invites!
    end
    user.save

    # this is not very atomic, but we do the best we can:
    if !user.is_tmp? && user.persisted?
      identity = user.identity
      identity.user_id = user.id
      identity.save
      identity.errors.each do |attr, msg|
        user.errors.add(attr, msg)
      end
      if user.invite_required?
        user_invite_code = InviteCode.find_by_invite_code user.invite_code
        if user.is_test? && user_invite_code.max_uses == 1
          user_invite_code.destroy
        else
          user_invite_code.invite_count += 1
          user_invite_code.save
        end
      end
    end
  rescue VALIDATION_FAILED => ex
    user.errors.add(:base, ex.to_s) if user
  ensure
    if creation_problem?(user, identity)
      user.destroy     if user     && user.persisted?
      identity.destroy if identity && identity.persisted?
    end
    return user
  end

  def update(attrs)
    if attrs[:password_verifier].present?
      update_login(attrs[:login])
      @user.update_attributes attrs.slice(:password_verifier, :password_salt)
    end
    if attrs[:recovery_code_verifier].present?
      @user.update_attributes attrs.slice(:recovery_code_verifier, :recovery_code_salt)
    end
    # TODO: move into identity controller
    key = update_pgp_key(attrs[:public_key])
    @user.errors.set :public_key, key.errors.full_messages
    @user.save && save_identities
    @user.refresh_identity
  end

  def destroy(release_handles=false)
    return unless @user
    if !@user.is_tmp?
      @user.identities.each do |id|
        if release_handles == false
          id.orphan!
        else
          id.destroy
        end
      end
    end
    @user.destroy
  end

  # when a user is disable, all their data and associations remain
  # in place, but the user should not be able to send email or
  # create new authentication certificates.
  def disable
    if @user && !@user.is_tmp?
      @user.enabled = false
      @user.save
      @user.identities.each do |id|
        id.disable!
      end
    end
  end

  def enable
    @user.enabled = true
    @user.save
    @user.identities.each do |id|
      id.enable!
    end
  end

  protected

  def update_login(login)
    return unless login.present?
    @old_identity = Identity.for(@user)
    @user.login = login
    @new_identity = Identity.for(@user) # based on the new login
    @old_identity.destination = @user.email_address # alias old -> new
  end

  def update_pgp_key(key)
    PgpKey.new(key).tap do |key|
      if key.present? && key.valid?
        @new_identity ||= Identity.for(@user)
        @new_identity.set_key(:pgp, key)
      end
    end
  end

  def save_identities
    @new_identity.try(:save) && @old_identity.try(:save)
  end

  def self.creation_problem?(user, identity)
    return true if user.nil? || !user.persisted? || user.errors.any?
    if !user.is_tmp?
      return true if identity.nil? || !identity.persisted? || identity.errors.any?
    end
    return false
  end

  # You can hook into the account lifecycle from different engines using
  #   ActiveSupport.on_load(:account) do ...
  ActiveSupport.run_load_hooks(:account, self)
end