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
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
181
182
183
184
185
186
187
188
|
#
# NOTE: there is some confusing terminology between User and Identity:
# If a user is disabled, the user still exists but has been marked as disabled
# and this condition can be easily reversed. If an identity is disabled, then
# it loses any association with the user and exists only to reserve that username
# and prevent anyone else from registering it.
#
class Identity < CouchRest::Model::Base
include LoginFormatValidation
use_database :identities
belongs_to :user
property :address, LocalEmail
property :destination, Email
property :keys, HashWithIndifferentAccess
property :cert_fingerprints, Hash
validates :address, presence: true
validate :address_available
validates :destination, presence: true, if: :enabled?
validates :destination, uniqueness: {scope: :address}
validate :address_local_email
validate :destination_email
design do
own_path = Pathname.new(File.dirname(__FILE__))
load_views(own_path.join('..', 'designs', 'identity'), nil)
view :by_user_id
view :by_address_and_destination
view :by_address
end
def self.address_starts_with(query)
self.by_address.startkey(query).endkey(query + "\ufff0")
end
def self.for(user, attributes = {})
find_for(user, attributes) || build_for(user, attributes)
end
def self.find_for(user, attributes = {})
attributes.reverse_merge! attributes_from_user(user)
id = find_by_address_and_destination attributes.values_at(:address, :destination)
return id if id && id.user == user
end
def self.build_for(user, attributes = {})
attributes.reverse_merge! attributes_from_user(user)
Identity.new(attributes)
end
def self.create_for(user, attributes = {})
identity = build_for(user, attributes)
identity.save
identity
end
def self.disable_all_for(user)
Identity.by_user_id.key(user.id).each do |identity|
identity.disable
# if the identity is not unique anymore because the destination
# was reset to nil we destroy it.
identity.save || identity.destroy
end
end
# if an identity is disabled, it loses contact
# with its former user. but sometimes we want to keep the association
# and remove the fingerprints that allow the user to send email.
def self.remove_cert_fingerprints_for(user)
Identity.by_user_id.key(user.id).each do |identity|
identity.write_attribute(:cert_fingerprints, {})
identity.save
end
end
def self.destroy_all_for(user)
Identity.by_user_id.key(user.id).each do |identity|
identity.destroy
end
end
def self.destroy_all_disabled
Identity.disabled.each do |identity|
identity.destroy
end
end
def self.attributes_from_user(user)
{ user_id: user.id,
address: user.email_address,
destination: user.email_address
}
end
def status
return :blocked if disabled?
case destination
when address
:main_email
when /@#{APP_CONFIG[:domain]}\Z/i,
:alias
else
:forward
end
end
def enabled?
self.user_id
end
def disabled?
!enabled?
end
def actions
if enabled?
[] # [:show, :edit]
else
[:destroy]
end
end
def disable
self.destination = nil
self.user_id = nil
end
def keys
read_attribute('keys') || HashWithIndifferentAccess.new
end
def set_key(type, key)
return if keys[type] == key.to_s
write_attribute('keys', keys.merge(type => key.to_s))
end
def cert_fingerprints
read_attribute('cert_fingerprints') || Hash.new
end
def register_cert(cert)
expiry = cert.expiry.to_date.to_s
write_attribute 'cert_fingerprints',
cert_fingerprints.merge(cert.fingerprint => expiry)
end
# for LoginFormatValidation
def login
address.handle if address.present?
end
protected
def address_available
blocking_identities = Identity.by_address.key(address).all
blocking_identities.delete self
if self.user
blocking_identities.reject! { |other| other.user == self.user }
end
if blocking_identities.any?
errors.add :address, :taken
end
end
def address_local_email
# caught by presence validation
return if address.blank?
return if address.valid?
address.errors.each do |attribute, error|
self.errors.add(:address, error)
end
end
def destination_email
# caught by presence validation or this identity is disabled
return if destination.blank?
return if destination.valid?
destination.errors.each do |attribute, error|
self.errors.add(:destination, error)
end
end
ActiveSupport.run_load_hooks(:identity, self)
end
|