summaryrefslogtreecommitdiff
path: root/users/app/models
diff options
context:
space:
mode:
authorazul <azul@riseup.net>2014-04-17 10:12:05 +0200
committerazul <azul@riseup.net>2014-04-17 10:12:05 +0200
commit3513ad74f950b113af1ba1e3d06bc6a55c48fde5 (patch)
treedb49ebd4428053d5c8d720275b77594a531a1ad1 /users/app/models
parentcb6442c344d6bdaf52c3878b2de2fcf4d85f2648 (diff)
parent3d3688647fab7049e5b531c45b85c1e46a1d528f (diff)
Merge pull request #146 from azul/refactor/engines
Refactor/engines
Diffstat (limited to 'users/app/models')
-rw-r--r--users/app/models/.gitkeep0
-rw-r--r--users/app/models/account.rb68
-rw-r--r--users/app/models/email.rb26
-rw-r--r--users/app/models/identity.rb136
-rw-r--r--users/app/models/local_email.rb68
-rw-r--r--users/app/models/login_format_validation.rb21
-rw-r--r--users/app/models/message.rb29
-rw-r--r--users/app/models/pgp_key.rb48
-rw-r--r--users/app/models/service_level.rb19
-rw-r--r--users/app/models/session.rb32
-rw-r--r--users/app/models/token.rb69
-rw-r--r--users/app/models/unauthenticated_user.rb6
-rw-r--r--users/app/models/user.rb179
13 files changed, 0 insertions, 701 deletions
diff --git a/users/app/models/.gitkeep b/users/app/models/.gitkeep
deleted file mode 100644
index e69de29..0000000
--- a/users/app/models/.gitkeep
+++ /dev/null
diff --git a/users/app/models/account.rb b/users/app/models/account.rb
deleted file mode 100644
index cf998e4..0000000
--- a/users/app/models/account.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-#
-# The Account model takes care of the livecycle of a user.
-# It composes a User record and it's identity records.
-# It also allows for other engines to hook into the livecycle 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
-
- # Returns the user record so it can be used in views.
- def self.create(attrs)
- @user = User.create(attrs).tap do |user|
- Identity.create_for user
- end
- 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
- 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
- return unless @user
- Identity.disable_all_for(@user)
- @user.destroy
- 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
-
- # You can hook into the account lifecycle from different engines using
- # ActiveSupport.on_load(:account) do ...
- ActiveSupport.run_load_hooks(:account, self)
-end
diff --git a/users/app/models/email.rb b/users/app/models/email.rb
deleted file mode 100644
index a9a503f..0000000
--- a/users/app/models/email.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-class Email < String
- include ActiveModel::Validations
-
- validates :email,
- :format => {
- :with => /\A([^@\s]+)@((?:[-a-z0-9]+\.)+[a-z]{2,})\Z/, #local part of email is case-sensitive, so allow uppercase letter.
- :message => "needs to be a valid email address"
- }
-
- def to_partial_path
- "emails/email"
- end
-
- def to_param
- to_s
- end
-
- def email
- self
- end
-
- def handle
- self.split('@').first
- end
-
-end
diff --git a/users/app/models/identity.rb b/users/app/models/identity.rb
deleted file mode 100644
index 9b97b51..0000000
--- a/users/app/models/identity.rb
+++ /dev/null
@@ -1,136 +0,0 @@
-class Identity < CouchRest::Model::Base
- include LoginFormatValidation
-
- use_database :identities
-
- belongs_to :user
-
- property :address, LocalEmail
- property :destination, Email
- property :keys, HashWithIndifferentAccess
-
- validate :unique_forward
- validate :alias_available
- validate :address_local_email
- validate :destination_email
-
- 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;
- }
- if (typeof doc.keys === "object") {
- emit(doc.address, doc.keys["pgp"]);
- }
- }
- EOJS
- view :disabled,
- map: <<-EOJS
- function(doc) {
- if (doc.type != 'Identity') {
- return;
- }
- if (typeof doc.user_id === "undefined") {
- emit(doc._id, 1);
- }
- }
- 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.disable_all_for(user)
- Identity.by_user_id.key(user.id).each do |identity|
- identity.disable
- identity.save
- 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 enabled?
- self.destination && self.user_id
- 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
-
- # for LoginFormatValidation
- def login
- self.address.handle
- 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
-
- def address_local_email
- return if address.valid? #this ensures it is LocalEmail
- self.errors.add(:address, address.errors.messages[:email].first) #assumes only one error
- end
-
- def destination_email
- return if destination.nil? # this identity is disabled
- return if destination.valid? # this ensures it is Email
- self.errors.add(:destination, destination.errors.messages[:email].first) #assumes only one error #TODO
- end
-
-end
diff --git a/users/app/models/local_email.rb b/users/app/models/local_email.rb
deleted file mode 100644
index 2b4c65e..0000000
--- a/users/app/models/local_email.rb
+++ /dev/null
@@ -1,68 +0,0 @@
-class LocalEmail < Email
-
- BLACKLIST_FROM_RFC2142 = [
- 'postmaster', 'hostmaster', 'domainadmin', 'webmaster', 'www',
- 'abuse', 'noc', 'security', 'usenet', 'news', 'uucp',
- 'ftp', 'sales', 'marketing', 'support', 'info'
- ]
-
- def self.domain
- APP_CONFIG[:domain]
- end
-
- validates :email,
- :format => {
- :with => /@#{domain}\Z/i,
- :message => "needs to end in @#{domain}"
- }
-
- validate :handle_allowed
-
- def initialize(s)
- super
- append_domain_if_needed
- end
-
- def to_key
- [handle]
- end
-
- def domain
- LocalEmail.domain
- end
-
- protected
-
- def append_domain_if_needed
- unless self.index('@')
- self << '@' + domain
- end
- end
-
- def handle_allowed
- errors.add(:handle, "is reserved.") if handle_reserved?
- end
-
- def handle_reserved?
- # *ARRAY in a case statement tests if ARRAY includes the handle.
- case handle
- when *APP_CONFIG[:handle_blacklist]
- true
- when *APP_CONFIG[:handle_whitelist]
- false
- when *BLACKLIST_FROM_RFC2142
- true
- else
- handle_in_passwd?
- end
- end
-
- def handle_in_passwd?
- begin
- !!Etc.getpwnam(handle)
- rescue ArgumentError
- # handle was not found
- return false
- end
- end
-end
diff --git a/users/app/models/login_format_validation.rb b/users/app/models/login_format_validation.rb
deleted file mode 100644
index c1fcf70..0000000
--- a/users/app/models/login_format_validation.rb
+++ /dev/null
@@ -1,21 +0,0 @@
-module LoginFormatValidation
- extend ActiveSupport::Concern
-
- #TODO: Probably will replace this. Playing with using it for aliases too, but won't want it connected to login field.
-
- included do
- # Have multiple regular expression validations so we can get specific error messages:
- validates :login,
- :format => { :with => /\A.{2,}\z/,
- :message => "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 => "Must begin with a lowercase letter"}
- validates :login,
- :format => { :with => /\A.*[a-z\d]\z/,
- :message => "Must end with a letter or digit"}
- end
-end
diff --git a/users/app/models/message.rb b/users/app/models/message.rb
deleted file mode 100644
index 424f094..0000000
--- a/users/app/models/message.rb
+++ /dev/null
@@ -1,29 +0,0 @@
-class Message < CouchRest::Model::Base
-
- use_database :messages
-
- property :text, String
- property :user_ids_to_show, [String]
- property :user_ids_have_shown, [String] # is this necessary to store?
-
- timestamps!
-
- design do
- own_path = Pathname.new(File.dirname(__FILE__))
- load_views(own_path.join('..', 'designs', 'message'))
- end
-
- def mark_as_read_by(user)
- user_ids_to_show.delete(user.id)
- # is it necessary to keep track of what users have already seen it?
- user_ids_have_shown << user.id unless read_by?(user)
- end
-
- def read_by?(user)
- user_ids_have_shown.include?(user.id)
- end
-
- def unread_by?(user)
- user_ids_to_show.include?(user.id)
- end
-end
diff --git a/users/app/models/pgp_key.rb b/users/app/models/pgp_key.rb
deleted file mode 100644
index 66f8660..0000000
--- a/users/app/models/pgp_key.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-class PgpKey
- include ActiveModel::Validations
-
- KEYBLOCK_IDENTIFIERS = [
- '-----BEGIN PGP PUBLIC KEY BLOCK-----',
- '-----END PGP PUBLIC KEY BLOCK-----',
- ]
-
- # mostly for testing.
- attr_accessor :keyblock
-
- validate :validate_keyblock_format
-
- def initialize(keyblock = nil)
- @keyblock = keyblock
- end
-
- def to_s
- @keyblock
- end
-
- def present?
- @keyblock.present?
- end
-
- # allow comparison with plain keyblock strings.
- def ==(other)
- self.equal?(other) or
- # relax the comparison on line ends.
- self.to_s.tr_s("\n\r", '') == other.tr_s("\r\n", '')
- end
-
- protected
-
- def validate_keyblock_format
- if keyblock_identifier_missing?
- errors.add :public_key_block,
- "does not look like an armored pgp public key block"
- end
- end
-
- def keyblock_identifier_missing?
- KEYBLOCK_IDENTIFIERS.find do |identify|
- !@keyblock.include?(identify)
- end
- end
-
-end
diff --git a/users/app/models/service_level.rb b/users/app/models/service_level.rb
deleted file mode 100644
index 299aaf1..0000000
--- a/users/app/models/service_level.rb
+++ /dev/null
@@ -1,19 +0,0 @@
-class ServiceLevel
-
- def initialize(attributes = {})
- @id = attributes[:id] || APP_CONFIG[:default_service_level]
- end
-
- def self.authenticated_select_options
- APP_CONFIG[:service_levels].map { |id,config_hash| [config_hash[:description], id] if config_hash[:name] != 'anonymous'}.compact
- end
-
- def id
- @id
- end
-
- def config_hash
- APP_CONFIG[:service_levels][@id]
- end
-
-end
diff --git a/users/app/models/session.rb b/users/app/models/session.rb
deleted file mode 100644
index 0d7e10e..0000000
--- a/users/app/models/session.rb
+++ /dev/null
@@ -1,32 +0,0 @@
-class Session < SRP::Session
- include ActiveModel::Validations
- include LoginFormatValidation
-
- attr_accessor :login
-
- validates :login, :presence => true
-
- def initialize(user = nil, aa = nil)
- super(user, aa) if user
- end
-
- def persisted?
- false
- end
-
- def new_record?
- true
- end
-
- def to_model
- self
- end
-
- def to_key
- [object_id]
- end
-
- def to_param
- nil
- end
-end
diff --git a/users/app/models/token.rb b/users/app/models/token.rb
deleted file mode 100644
index 4856c31..0000000
--- a/users/app/models/token.rb
+++ /dev/null
@@ -1,69 +0,0 @@
-class Token < CouchRest::Model::Base
-
- use_database :tokens
-
- belongs_to :user
-
- # timestamps! does not create setters and only sets updated_at
- # if the object has changed and been saved. Instead of triggering
- # that we rather use our own property we have control over:
- property :last_seen_at, Time, accessible: false
-
- validates :user_id, presence: true
-
- design do
- view :by_last_seen_at
- end
-
- def self.expires_after
- APP_CONFIG[:auth] && APP_CONFIG[:auth][:token_expires_after]
- end
-
- def self.expired
- return [] unless expires_after
- by_last_seen_at.endkey(expires_after.minutes.ago)
- end
-
- def self.destroy_all_expired
- self.expired.each do |token|
- token.destroy
- end
- end
-
- def authenticate
- if expired?
- destroy
- return nil
- else
- touch
- return user
- end
- end
-
- # Tokens can be cleaned up in different ways.
- # So let's make sure we don't crash if they disappeared
- def destroy_with_rescue
- destroy_without_rescue
- rescue RestClient::ResourceNotFound
- end
- alias_method_chain :destroy, :rescue
-
- def touch
- self.last_seen_at = Time.now
- save
- end
-
- def expired?
- Token.expires_after and
- last_seen_at < Token.expires_after.minutes.ago
- end
-
- def initialize(*args)
- super
- if new_record?
- self.id = SecureRandom.urlsafe_base64(32).gsub(/^_*/, '')
- self.last_seen_at = Time.now
- end
- end
-end
-
diff --git a/users/app/models/unauthenticated_user.rb b/users/app/models/unauthenticated_user.rb
deleted file mode 100644
index 0fc17d2..0000000
--- a/users/app/models/unauthenticated_user.rb
+++ /dev/null
@@ -1,6 +0,0 @@
-# The nil object for the user class
-class UnauthenticatedUser < Object
-
- # will probably want something here to return service level as APP_CONFIG[:service_levels][0] but not sure how will be accessing.
-
-end
diff --git a/users/app/models/user.rb b/users/app/models/user.rb
deleted file mode 100644
index c297ac8..0000000
--- a/users/app/models/user.rb
+++ /dev/null
@@ -1,179 +0,0 @@
-class User < CouchRest::Model::Base
- include LoginFormatValidation
-
- use_database :users
-
- property :login, String, :accessible => true
- property :password_verifier, String, :accessible => true
- property :password_salt, String, :accessible => true
-
- property :enabled, TrueClass, :default => true
-
- # these will be null by default but we shouldn't ever pull them directly, but only via the methods that will return the full ServiceLevel
- property :desired_service_level_code, Integer, :accessible => true
- property :effective_service_level_code, Integer, :accessible => true
-
- property :one_month_warning_sent, TrueClass
-
- before_save :update_effective_service_level
-
- validates :login, :password_salt, :password_verifier,
- :presence => true
-
- validates :login,
- :uniqueness => true,
- :if => :serverside?
-
- validate :login_is_unique_alias
-
- validates :password_salt, :password_verifier,
- :format => { :with => /\A[\dA-Fa-f]+\z/, :message => "Only hex numbers allowed" }
-
- validates :password, :presence => true,
- :confirmation => true,
- :format => { :with => /.{8}.*/, :message => "needs to be at least 8 characters long" }
-
- timestamps!
-
- design do
- own_path = Pathname.new(File.dirname(__FILE__))
- load_views(own_path.join('..', 'designs', 'user'))
- view :by_login
- view :by_created_at
- end # end of design
-
- def to_json(options={})
- {
- :login => login,
- :ok => valid?
- }.to_json(options)
- end
-
- def salt
- password_salt.hex
- end
-
- def verifier
- password_verifier.hex
- end
-
- def username
- login
- end
-
- def email_address
- LocalEmail.new(login)
- end
-
- # Since we are storing admins by login, we cannot allow admins to change their login.
- def is_admin?
- APP_CONFIG['admins'].include? self.login
- end
-
- def most_recent_tickets(count=3)
- Ticket.for_user(self).limit(count).all #defaults to having most recent updated first
- end
-
- def messages(unseen = true)
- #TODO for now this only shows unseen messages. Will we ever want seen ones? Is it necessary to store?
-
- # we don't want to emit all the userids associated with a message, so only emit id and text.
- Message.by_user_ids_to_show.key(self.id).map { |message| [message.id, message.text] }
-
- 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 account
- Account.new(self)
- end
-
- def identity
- @identity ||= Identity.for(self)
- end
-
- def refresh_identity
- @identity = Identity.for(self)
- end
-
- def desired_service_level
- code = self.desired_service_level_code || APP_CONFIG[:default_service_level]
- ServiceLevel.new({id: code})
- end
-
- def effective_service_level
- code = self.effective_service_level_code || self.desired_service_level.id
- ServiceLevel.new({id: code})
- end
-
-
- def self.send_one_month_warnings
-
- # To determine warnings to send, need to get all users where one_month_warning_sent is not set, and where it was created greater than or equal to 1 month ago.
- # TODO: might want to further limit to enabled accounts, and, based on provider's service level configuration, for particular service levels.
- users_to_warn = User.by_created_at_and_one_month_warning_not_sent.endkey(Time.now-1.month)
-
- users_to_warn.each do |user|
- # instead of loop could use something like:
- # message.user_ids_to_show = users_to_warn.map(&:id)
- # but would still need to loop through users to store one_month_warning_sent
-
- if !@message
- # create a message for today's date
- # only want to create once, and only if it will be used.
- @message = Message.new(:text => I18n.t(:payment_one_month_warning, :date_in_one_month => (Time.now+1.month).strftime("%Y-%d-%m")))
- end
-
- @message.user_ids_to_show << user.id
- user.one_month_warning_sent = true
- user.save
- end
- @message.save if @message
-
- end
-
- protected
-
- ##
- # Validation Functions
- ##
-
- def login_is_unique_alias
- 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")
- end
- end
-
- def password
- password_verifier
- end
-
- # used as a condition for validations that are server side only
- def serverside?
- true
- end
-
- def update_effective_service_level
- # TODO: Is this always the case? Might there be a situation where the admin has set the effective service level and we don't want it changed to match the desired one?
- if self.desired_service_level_code_changed?
- self.effective_service_level_code = self.desired_service_level_code
- end
- end
-
-end