class Vines::Storage

Attributes

ldap[RW]

Public Class Methods

defer(method) click to toggle source

Wrap a blocking IO method in a new method that pushes the original method onto EventMachine's thread pool using EM#defer. Storage classes implemented with blocking IO don't need to worry about threading or blocking the EventMachine reactor thread if they wrap their methods with this one.

For example: def find_user(jid)

some_blocking_lookup(jid)

end defer :find_user

Storage classes that use asynchronous IO (through an EventMachine enabled library like em-http-request or em-redis) don't need any special consideration and must not use this method.

# File lib/vines/storage.rb, line 37
def self.defer(method)
  old = instance_method(method)
  define_method method do |*args|
    fiber = Fiber.current
    op = operation { old.bind(self).call(*args) }
    cb = proc {|result| fiber.resume(result) }
    EM.defer(op, cb)
    Fiber.yield
  end
end
fiber(method) click to toggle source

Wrap a method with Fiber yield and resume logic. The method must yield its result to a block. This makes it easier to write asynchronous implementations of authenticate, find_user, and save_user that block and return a result rather than yielding.

For example: def find_user(jid)

http = EM::HttpRequest.new(url).get
http.callback { yield build_user_from_http_response(http) }

end fiber :find_user

Because find_user has been wrapped in Fiber logic, we can call it synchronously even though it uses asynchronous EventMachine calls.

user = storage.find_user('alice@wonderland.lit') puts user.nil?

# File lib/vines/storage.rb, line 82
def self.fiber(method)
  old = instance_method(method)
  define_method method do |*args|
    fiber, yielding = Fiber.current, true
    old.bind(self).call(*args) do |user|
      fiber.resume(user) rescue yielding = false
    end
    Fiber.yield if yielding
  end
end
from_name(name, &block) click to toggle source
# File lib/vines/storage.rb, line 17
def self.from_name(name, &block)
  klass = @@nicks[name.to_sym]
  raise "#{name} storage class not found" unless klass
  klass.new(&block)
end
register(name) click to toggle source

Register a nickname that can be used in the config file to specify this storage implementation.

# File lib/vines/storage.rb, line 13
def self.register(name)
  @@nicks[name.to_sym] = self
end
wrap_ldap(method) click to toggle source

Wrap an authenticate method with a new method that uses LDAP if it's enabled in the config file. If LDAP is not enabled, invoke the original authenticate method as usual. This allows storage classes to implement their native authentication logic and not worry about handling LDAP.

For example: def authenticate(username, password)

some_user_lookup_by_password(username, password)

end wrap_ldap :authenticate

# File lib/vines/storage.rb, line 58
def self.wrap_ldap(method)
  old = instance_method(method)
  define_method method do |*args|
    ldap? ? authenticate_with_ldap(*args) : old.bind(self).call(*args)
  end
end

Public Instance Methods

authenticate(username, password) click to toggle source

Validate the username and password pair and return a Vines::User object on success. Return nil on failure.

For example: user = storage.authenticate('alice@wonderland.lit', 'secr3t') puts user.nil?

This default implementation validates the password against a bcrypt hash of the password stored in the database. Sub-classes not using bcrypt passwords must override this method.

# File lib/vines/storage.rb, line 108
def authenticate(username, password)
  user = find_user(username)
  hash = BCrypt::Password.new(user.password) rescue nil
  (hash && hash == password) ? user : nil
end
find_fragment(jid, node) click to toggle source

Return the Nokogiri::XML::Node for the XML fragment stored for this JID. Return nil if the fragment could not be found. JID may be nil, a String, or a Vines::JID object. It may be a bare JID or a full JID. Implementations of this method must convert the JID to a bare JID before searching for the fragment in the database.

Private XML storage uniquely identifies fragments by JID, root element name, and root element namespace.

root = Nokogiri::XML('<custom xmlns=“urn:custom:ns”/>').root fragment = storage.find_fragment('alice@wonderland.lit', root) puts fragment.nil?

# File lib/vines/storage.rb, line 173
def find_fragment(jid, node)
  raise 'subclass must implement'
end
find_user(jid) click to toggle source

Return the Vines::User associated with the JID. Return nil if the user could not be found. JID may be nil, a String, or a Vines::JID object. It may be a bare JID or a full JID. Implementations of this method must convert the JID to a bare JID before searching for the user in the database.

user = storage.find_user('alice@wonderland.lit') puts user.nil?

# File lib/vines/storage.rb, line 123
def find_user(jid)
  raise 'subclass must implement'
end
find_vcard(jid) click to toggle source

Return the Nokogiri::XML::Node for the vcard stored for this JID. Return nil if the vcard could not be found. JID may be nil, a String, or a Vines::JID object. It may be a bare JID or a full JID. Implementations of this method must convert the JID to a bare JID before searching for the vcard in the database.

card = storage.find_vcard('alice@wonderland.lit') puts card.nil?

# File lib/vines/storage.rb, line 145
def find_vcard(jid)
  raise 'subclass must implement'
end
ldap?() click to toggle source

Return true if users are authenticated against an LDAP directory.

# File lib/vines/storage.rb, line 94
def ldap?
  !!ldap
end
save_fragment(jid, fragment) click to toggle source

Save the XML fragment to the database and return when the save is complete. JID may be a String or a Vines::JID object. It may be a bare JID or a full JID. Implementations of this method must convert the JID to a bare JID before saving the fragment. Fragment is a Nokogiri::XML::Node object.

fragment = Nokogiri::XML('<custom xmlns=“urn:custom:ns”>some data</custom>').root storage.save_fragment('alice@wonderland.lit', fragment) puts 'saved'

# File lib/vines/storage.rb, line 185
def save_fragment(jid, fragment)
  raise 'subclass must implement'
end
save_user(user) click to toggle source

Persist the Vines::User object to the database and return when the save is complete.

alice = Vines::User.new(:jid => 'alice@wonderland.lit') storage.save_user(alice) puts 'saved'

# File lib/vines/storage.rb, line 133
def save_user(user)
  raise 'subclass must implement'
end
save_vcard(jid, card) click to toggle source

Save the vcard to the database and return when the save is complete. JID may be a String or a Vines::JID object. It may be a bare JID or a full JID. Implementations of this method must convert the JID to a bare JID before saving the vcard. Card is a Nokogiri::XML::Node object.

card = Nokogiri::XML('<vCard>…</vCard>').root storage.save_vcard('alice@wonderland.lit', card) puts 'saved'

# File lib/vines/storage.rb, line 157
def save_vcard(jid, card)
  raise 'subclass must implement'
end

Private Instance Methods

authenticate_with_ldap(username, password, &block) click to toggle source

Return a Vines::User object if we are able to bind to the LDAP server using the username and password. Return nil if authentication failed. If authentication succeeds, but the user is not yet stored in our database, save the user to the database.

# File lib/vines/storage.rb, line 216
def authenticate_with_ldap(username, password, &block)
  op = operation { ldap.authenticate(username, password) }
  cb = proc {|user| save_ldap_user(user, &block) }
  EM.defer(op, cb)
end
empty?(*args) click to toggle source

Return true if any of the arguments are nil or empty strings. For example: username, password = 'alice@wonderland.lit', '' empty?(username, password) #=> true

# File lib/vines/storage.rb, line 195
def empty?(*args)
  args.flatten.any? {|arg| (arg || '').strip.empty? }
end
operation() { || ... } click to toggle source

Return a proc suitable for running on the EM.defer thread pool that traps and logs any errors thrown by the provided block.

# File lib/vines/storage.rb, line 201
def operation
  proc do
    begin
      yield
    rescue => e
      log.error("Thread pool operation failed: #{e.message}")
      nil
    end
  end
end
save_ldap_user(user, &block) click to toggle source

Save missing users to the storage database after they're authenticated with LDAP. This allows admins to define users once in LDAP and have them sync to the chat database the first time they successfully sign in.

# File lib/vines/storage.rb, line 226
def save_ldap_user(user, &block)
  Fiber.new do
    if user.nil?
      block.call
    elsif found = find_user(user.jid)
      block.call(found)
    else
      save_user(user)
      block.call(user)
    end
  end.resume
end