class Pwl::Locker

Constants

DEFAULT_PASSWORD_POLICY

Public Class Methods

load(file, master_password, options = {})
Alias for: new
new(file, master_password, options = {}) click to toggle source

Constructs a new locker (not only the object, but also the file behind it).

# File lib/pwl/locker.rb, line 59
def new(file, master_password, options = {})
  if File.exists?(file) && !options[:force] # don't allow accedidential override of existing file
    raise FileAlreadyExistsError.new(file)
  else
    password_policy.validate!(master_password)
    locker = load(file, master_password)
    locker.reset!
  end

  locker
end
Also aliased as: load
new(file, master_password) click to toggle source

Create a new locker object by loading an existing file.

Beware: New is overridden; it performs additional actions before and after initialize

# File lib/pwl/locker.rb, line 95
def initialize(file, master_password)
  @backend = PStore.new(file, true)
  @backend.ultra_safe = true
  @master_password = master_password
end
open(file, master_password) click to toggle source

Opens an existing locker. Throws if the backing file does not exist or isn't initialized.

# File lib/pwl/locker.rb, line 74
def open(file, master_password)
  raise FileNotFoundError.new(file) unless File.exists?(file)
  locker = load(file, master_password)
  locker.authenticate # do not allow openeing without successful authentication
  locker
end
password_policy() click to toggle source
# File lib/pwl/locker.rb, line 81
def password_policy
  @password_policy || DEFAULT_PASSWORD_POLICY
end
password_policy=(policy) click to toggle source
# File lib/pwl/locker.rb, line 85
def password_policy=(policy)
  @password_policy = policy
end

Public Instance Methods

add(entry_or_key, value = nil) click to toggle source

Store entry or value under key

# File lib/pwl/locker.rb, line 143
def add(entry_or_key, value = nil)
  if value.nil? and entry_or_key.is_a?(Entry) # treat as entry
    entry = entry_or_key
  else
    entry = Entry.new(entry_or_key)
    entry.password = value
  end

  entry.validate!

  @backend.transaction{
    timestamp!(:last_modified)
    @backend[:user][encrypt(entry.name)] = encrypt(EntryMapper.to_json(entry))
  }
end
all() click to toggle source

Return all entries as array

# File lib/pwl/locker.rb, line 190
def all
  result = []
  @backend.transaction(true){
    @backend[:user].each{|k,v| result << EntryMapper.from_json(decrypt(v))}
  }
  result
end
authenticate() click to toggle source

Check that the master password is correct. This is done to prevent opening an existing but blank locker with the wrong password.

# File lib/pwl/locker.rb, line 116
def authenticate
  begin
    @backend.transaction(true){
      raise NotInitializedError.new(@backend.path.path) unless @backend[:user] && @backend[:system] && @backend[:system][:created]
      check_salt!
    }
  rescue OpenSSL::Cipher::CipherError
    raise WrongMasterPasswordError
  end
end
change_password!(new_master_password) click to toggle source

Change the master password to new_master_password. Note that we don't take a password confirmation here. This is up to a UI layer.

# File lib/pwl/locker.rb, line 202
def change_password!(new_master_password)
  self.class.password_policy.validate!(new_master_password)

  @backend.transaction{
    # Decrypt each key and value with the old master password and encrypt them with the new master password
    copy = {}
    @backend[:user].each{|k,v|
      # No need to (de)serialize - the value comes in as JSON and goes out as JSON
      new_key = Encryptor.encrypt(decrypt(k), :key => new_master_password)
      new_val = Encryptor.encrypt(decrypt(v), :key => new_master_password)
      copy[new_key] = new_val
    }

    # re-write user branch with newly encrypted keys and values
    @backend[:user] = copy

    # from now on, use the new master password as long as the object lives
    @master_password = new_master_password

    timestamp!(:last_modified)
    @backend[:system][:salt] = encrypt(Random.rand.to_s)
  }
end
created() click to toggle source

Return the date when the locker was created

# File lib/pwl/locker.rb, line 229
def created
  @backend.transaction(true){@backend[:system][:created]}
end
delete(key) click to toggle source

Delete the value that is stored under key and return it

# File lib/pwl/locker.rb, line 162
def delete(key)
  raise BlankKeyError if key.blank?
  @backend.transaction{
    timestamp!(:last_modified)
    old_value = @backend[:user].delete(encrypt(key))
    raise KeyNotFoundError.new(key) unless old_value
    EntryMapper.from_json(decrypt(old_value))
  }
end
get(key) click to toggle source

Return the value stored under key

# File lib/pwl/locker.rb, line 130
def get(key)
  raise BlankKeyError if key.blank?
  @backend.transaction{
    timestamp!(:last_accessed)
    value = @backend[:user][encrypt(key)]
    raise KeyNotFoundError.new(key) unless value
    EntryMapper.from_json(decrypt(value))
  }
end
last_accessed() click to toggle source

Return the date when the locker was last accessed

# File lib/pwl/locker.rb, line 236
def last_accessed
  @backend.transaction(true){@backend[:system][:last_accessed]}
end
last_modified() click to toggle source

Return the date when the locker was last modified

# File lib/pwl/locker.rb, line 243
def last_modified
  @backend.transaction(true){@backend[:system][:last_modified]}
end
list(filter = nil) click to toggle source

Return all keys, optionally filtered by filter

# File lib/pwl/locker.rb, line 175
def list(filter = nil)
  @backend.transaction(true){
    result = @backend[:user].keys.collect{|k| decrypt(k)}

    if filter.blank?
      result
    else
      result.select{|k,v| k =~ /#{filter}/}
    end
  }
end
path() click to toggle source

Return the path to the file backing this locker

# File lib/pwl/locker.rb, line 250
def path
  @backend.path
end
reset!() click to toggle source

(Re-) Initialize the database

# File lib/pwl/locker.rb, line 104
def reset!
  @backend.transaction{
    @backend[:user] = {}
    @backend[:system] = {}
    @backend[:system][:created] = DateTime.now
    @backend[:system][:salt] = encrypt(Random.rand.to_s)
  }
end

Private Instance Methods

check_salt!() click to toggle source

Attempts to decrypt the system salt. Throws if the master password is incorrect.

This method must run within a PStore transaction (may be read-only).

# File lib/pwl/locker.rb, line 284
def check_salt!
  raise NotInitializedError.new(@backend.path.path) if @backend[:system][:salt].blank?
  decrypt(@backend[:system][:salt])
  nil
end
decrypt(value) click to toggle source

Return the decrypted value (uses the current master password)

# File lib/pwl/locker.rb, line 275
def decrypt(value)
  Encryptor.decrypt(value, :key => @master_password)
end
encrypt(value) click to toggle source

Return the encrypted value (uses the current master password)

# File lib/pwl/locker.rb, line 268
def encrypt(value)
  Encryptor.encrypt(value, :key => @master_password)
end
timestamp!(sym) click to toggle source

Adds or updates the time-stamp stored under symbol

This method must run within a PStore read/write transaction.

# File lib/pwl/locker.rb, line 261
def timestamp!(sym)
  @backend[:system][sym] = DateTime.now
end