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
|