summaryrefslogtreecommitdiff
path: root/users/app/models
diff options
context:
space:
mode:
authorjessib <jessib@riseup.net>2013-08-27 12:18:35 -0700
committerjessib <jessib@riseup.net>2013-08-27 12:18:35 -0700
commitdc41ae0a3fb0a137e716d8ec63084b0ec3a7299b (patch)
treec09fef161f105e7c03c35d1edcb2d257144cb97d /users/app/models
parenta87c750d1f12f15272beb117f8ee12ab711cc6d1 (diff)
parente481b8cbc05a858674a59ef36d695973622f6b3a (diff)
Merge branch 'master' into billing_with_tests
Diffstat (limited to 'users/app/models')
-rw-r--r--users/app/models/account_settings.rb36
-rw-r--r--users/app/models/email.rb35
-rw-r--r--users/app/models/identity.rb82
-rw-r--r--users/app/models/local_email.rb68
-rw-r--r--users/app/models/login_format_validation.rb19
-rw-r--r--users/app/models/remote_email.rb14
-rw-r--r--users/app/models/session.rb6
-rw-r--r--users/app/models/signup_service.rb9
-rw-r--r--users/app/models/token.rb4
-rw-r--r--users/app/models/user.rb71
10 files changed, 210 insertions, 134 deletions
diff --git a/users/app/models/account_settings.rb b/users/app/models/account_settings.rb
new file mode 100644
index 0000000..27fa227
--- /dev/null
+++ b/users/app/models/account_settings.rb
@@ -0,0 +1,36 @@
+class AccountSettings
+
+ def initialize(user)
+ @user = user
+ end
+
+ def update(attrs)
+ if attrs[:password_verifier].present?
+ update_login(attrs[:login])
+ @user.update_attributes attrs.slice(:password_verifier, :password_salt)
+ end
+ # TODO: move into identity controller
+ update_pgp_key(attrs[:public_key]) if attrs.has_key? :public_key
+ @user.save && save_identities
+ 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)
+ @new_identity ||= Identity.for(@user)
+ @new_identity.set_key(:pgp, key)
+ end
+
+ def save_identities
+ @new_identity.try(:save) && @old_identity.try(:save)
+ end
+
+end
diff --git a/users/app/models/email.rb b/users/app/models/email.rb
index 6d82f2a..1bcff1c 100644
--- a/users/app/models/email.rb
+++ b/users/app/models/email.rb
@@ -1,33 +1,22 @@
-module Email
- extend ActiveSupport::Concern
+class Email < String
+ include ActiveModel::Validations
- included do
- validates :email,
- :format => {
- :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/,
- :message => "needs to be a valid email address"
- }
- end
-
- def initialize(attributes = nil, &block)
- attributes = {:email => attributes} if attributes.is_a? String
- super(attributes, &block)
- end
-
- def to_s
- email
- end
-
- def ==(other)
- other.is_a?(Email) ? self.email == other.email : self.email == other
- end
+ validates :email,
+ :format => {
+ :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/,
+ :message => "needs to be a valid email address"
+ }
def to_partial_path
"emails/email"
end
def to_param
- email
+ to_s
+ end
+
+ def email
+ self
end
end
diff --git a/users/app/models/identity.rb b/users/app/models/identity.rb
new file mode 100644
index 0000000..355f67a
--- /dev/null
+++ b/users/app/models/identity.rb
@@ -0,0 +1,82 @@
+class Identity < CouchRest::Model::Base
+
+ use_database :identities
+
+ belongs_to :user
+
+ property :address, LocalEmail
+ property :destination, Email
+ property :keys, HashWithIndifferentAccess
+
+ validate :unique_forward
+ validate :alias_available
+
+ design do
+ view :by_user_id
+ view :by_address_and_destination
+ view :by_address
+ view :pgp_key_by_email,
+ map: <<-EOJS
+ function(doc) {
+ if (doc.type != 'Identity') {
+ return;
+ }
+ emit(doc.address, doc.keys["pgp"]);
+ }
+ EOJS
+
+ 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)
+ find_by_address_and_destination [attributes[:address], attributes[:destination]]
+ 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.attributes_from_user(user)
+ { user_id: user.id,
+ address: user.email_address,
+ destination: user.email_address
+ }
+ end
+
+ def keys
+ read_attribute('keys') || HashWithIndifferentAccess.new
+ end
+
+ def set_key(type, value)
+ return if keys[type] == value
+ write_attribute('keys', keys.merge(type => value))
+ end
+
+ protected
+
+ def unique_forward
+ same = Identity.find_by_address_and_destination([address, destination])
+ if same && same != self
+ errors.add :base, "This alias already exists"
+ end
+ end
+
+ def alias_available
+ same = Identity.find_by_address(address)
+ if same && same.user != self.user
+ errors.add :base, "This email has already been taken"
+ end
+ end
+
+end
diff --git a/users/app/models/local_email.rb b/users/app/models/local_email.rb
index 69cba01..c1f7c11 100644
--- a/users/app/models/local_email.rb
+++ b/users/app/models/local_email.rb
@@ -1,63 +1,39 @@
-class LocalEmail
- include CouchRest::Model::Embeddable
- include Email
+class LocalEmail < Email
- property :username, String
- before_validation :strip_domain_if_needed
-
- validates :username,
- :presence => true,
- :format => { :with => /\A([^@\s]+)(@#{APP_CONFIG[:domain]})?\Z/i, :message => "needs to be a valid login or email address @#{APP_CONFIG[:domain]}"}
-
- validate :unique_on_server
- validate :unique_alias_for_user
- validate :differs_from_login
-
- validates :casted_by, :presence => true
-
- def email
- return '' if username.nil?
- username + '@' + APP_CONFIG[:domain]
+ def self.domain
+ APP_CONFIG[:domain]
end
- def email=(value)
- return if value.blank?
- self.username = value
- strip_domain_if_needed
+ validates :email,
+ :format => {
+ :with => /@#{domain}\Z/i,
+ :message => "needs to end in @#{domain}"
+ }
+
+ def initialize(s)
+ super
+ append_domain_if_needed
end
def to_key
- [username]
+ [handle]
end
- protected
-
- def unique_on_server
- has_email = User.find_by_login_or_alias(username)
- if has_email && has_email != self.casted_by
- errors.add :username, "has already been taken"
- end
+ def handle
+ gsub(/@#{domain}/i, '')
end
- def unique_alias_for_user
- aliases = self.casted_by.email_aliases
- if aliases.select{|a|a.username == self.username}.count > 1
- errors.add :username, "is already your alias"
- end
+ def domain
+ LocalEmail.domain
end
- def differs_from_login
- # If this has not changed but the email let's mark the email invalid instead.
- return if self.persisted?
- user = self.casted_by
- if user.login == self.username
- errors.add :username, "may not be the same as your email address"
- end
- end
+ protected
- def strip_domain_if_needed
- self.username.gsub! /@#{APP_CONFIG[:domain]}/i, ''
+ def append_domain_if_needed
+ unless self.index('@')
+ self << '@' + domain
+ end
end
end
diff --git a/users/app/models/login_format_validation.rb b/users/app/models/login_format_validation.rb
new file mode 100644
index 0000000..1d02bd1
--- /dev/null
+++ b/users/app/models/login_format_validation.rb
@@ -0,0 +1,19 @@
+module LoginFormatValidation
+ extend ActiveSupport::Concern
+
+ included do
+ # Have multiple regular expression validations so we can get specific error messages:
+ validates :login,
+ :format => { :with => /\A.{2,}\z/,
+ :message => "Login must have at least two characters"}
+ validates :login,
+ :format => { :with => /\A[a-z\d_\.-]+\z/,
+ :message => "Only lowercase letters, digits, . - and _ allowed."}
+ validates :login,
+ :format => { :with => /\A[a-z].*\z/,
+ :message => "Login must begin with a lowercase letter"}
+ validates :login,
+ :format => { :with => /\A.*[a-z\d]\z/,
+ :message => "Login must end with a letter or digit"}
+ end
+end
diff --git a/users/app/models/remote_email.rb b/users/app/models/remote_email.rb
deleted file mode 100644
index 4fe7425..0000000
--- a/users/app/models/remote_email.rb
+++ /dev/null
@@ -1,14 +0,0 @@
-class RemoteEmail
- include CouchRest::Model::Embeddable
- include Email
-
- property :email, String
-
- def username
- email.spilt('@').first
- end
-
- def domain
- email.split('@').last
- end
-end
diff --git a/users/app/models/session.rb b/users/app/models/session.rb
index a9fdb1b..0d7e10e 100644
--- a/users/app/models/session.rb
+++ b/users/app/models/session.rb
@@ -1,12 +1,10 @@
class Session < SRP::Session
include ActiveModel::Validations
+ include LoginFormatValidation
attr_accessor :login
- validates :login,
- :presence => true,
- :format => { :with => /\A[A-Za-z\d_]+\z/,
- :message => "Only letters, digits and _ allowed" }
+ validates :login, :presence => true
def initialize(user = nil, aa = nil)
super(user, aa) if user
diff --git a/users/app/models/signup_service.rb b/users/app/models/signup_service.rb
new file mode 100644
index 0000000..f316ca9
--- /dev/null
+++ b/users/app/models/signup_service.rb
@@ -0,0 +1,9 @@
+class SignupService
+
+ def register(attrs)
+ User.create(attrs).tap do |user|
+ Identity.create_for user
+ end
+ end
+
+end
diff --git a/users/app/models/token.rb b/users/app/models/token.rb
index cc62778..514b97f 100644
--- a/users/app/models/token.rb
+++ b/users/app/models/token.rb
@@ -6,6 +6,10 @@ class Token < CouchRest::Model::Base
validates :user_id, presence: true
+ def user
+ User.find(self.user_id)
+ end
+
def initialize(*args)
super
self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
index 413b4ac..c1988f3 100644
--- a/users/app/models/user.rb
+++ b/users/app/models/user.rb
@@ -1,4 +1,5 @@
class User < CouchRest::Model::Base
+ include LoginFormatValidation
use_database :users
@@ -6,11 +7,6 @@ class User < CouchRest::Model::Base
property :password_verifier, String, :accessible => true
property :password_salt, String, :accessible => true
- property :email_forward, String, :accessible => true
- property :email_aliases, [LocalEmail]
-
- property :public_key, :accessible => true
-
property :enabled, TrueClass, :default => true
validates :login, :password_salt, :password_verifier,
@@ -20,20 +16,6 @@ class User < CouchRest::Model::Base
:uniqueness => true,
:if => :serverside?
- # Have multiple regular expression validations so we can get specific error messages:
- validates :login,
- :format => { :with => /\A.{2,}\z/,
- :message => "Login must have at least two characters"}
- validates :login,
- :format => { :with => /\A[a-z\d_\.-]+\z/,
- :message => "Only lowercase letters, digits, . - and _ allowed."}
- validates :login,
- :format => { :with => /\A[a-z].*\z/,
- :message => "Login must begin with a lowercase letter"}
- validates :login,
- :format => { :with => /\A.*[a-z\d]\z/,
- :message => "Login must end with a letter or digit"}
-
validate :login_is_unique_alias
validates :password_salt, :password_verifier,
@@ -43,10 +25,6 @@ class User < CouchRest::Model::Base
:confirmation => true,
:format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" }
- validates :email_forward,
- :allow_blank => true,
- :format => { :with => /\A(([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,}))?\Z/, :message => "needs to be a valid email address"}
-
timestamps!
design do
@@ -54,19 +32,6 @@ class User < CouchRest::Model::Base
load_views(own_path.join('..', 'designs', 'user'))
view :by_login
view :by_created_at
- view :pgp_key_by_handle,
- map: <<-EOJS
- function(doc) {
- if (doc.type != 'User') {
- return;
- }
- emit(doc.login, doc.public_key);
- doc.email_aliases.forEach(function(alias){
- emit(alias.username, doc.public_key);
- });
- }
- EOJS
-
end # end of design
class << self
@@ -105,16 +70,30 @@ class User < CouchRest::Model::Base
APP_CONFIG['admins'].include? self.login
end
- # this currently only adds the first email address submitted.
- # All the ui needs for now.
- def email_aliases_attributes=(attrs)
- email_aliases.build(attrs.values.first) if attrs
- end
-
def most_recent_tickets(count=3)
Ticket.for_user(self).limit(count).all #defaults to having most recent updated first
end
+ # DEPRECATED
+ #
+ # Please set the key on the identity directly
+ # WARNING: This will not be serialized with the user record!
+ # It is only a workaround for the key form.
+ def public_key=(value)
+ identity.set_key(:pgp, value)
+ end
+
+ # DEPRECATED
+ #
+ # Please access identity.keys[:pgp] directly
+ def public_key
+ identity.keys[:pgp]
+ end
+
+ def identity
+ @identity ||= Identity.for(self)
+ end
+
protected
##
@@ -122,12 +101,10 @@ class User < CouchRest::Model::Base
##
def login_is_unique_alias
- has_alias = User.find_by_login_or_alias(username)
- return if has_alias.nil?
- if has_alias != self
+ alias_identity = Identity.find_by_address(self.email_address)
+ return if alias_identity.blank?
+ if alias_identity.user != self
errors.add(:login, "has already been taken")
- elsif has_alias.login != self.login
- errors.add(:login, "may not be the same as one of your aliases")
end
end