Class: Pwl::Locker

Inherits:
Object
  • Object
show all
Defined in:
lib/pwl/locker.rb

Defined Under Namespace

Classes: BlankError, BlankKeyError, BlankValueError, FileAlreadyExistsError, FileNotFoundError, KeyNotFoundError, NotInitializedError, WrongMasterPasswordError

Constant Summary collapse

DEFAULT_PASSWORD_POLICY =
ReasonableComplexityPasswordPolicy.new

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file, master_password) ⇒ Locker

Create a new locker object by loading an existing file.

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



95
96
97
98
99
# 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

Class Method Details

.loadObject



52
# File 'lib/pwl/locker.rb', line 52

alias_method :load, :new

.new(file, master_password, options = {}) ⇒ Object

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



59
60
61
62
63
64
65
66
67
68
69
# 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

.open(file, master_password) ⇒ Object

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

Raises:



74
75
76
77
78
79
# 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_policyObject



81
82
83
# File 'lib/pwl/locker.rb', line 81

def password_policy
  @password_policy || DEFAULT_PASSWORD_POLICY
end

.password_policy=(policy) ⇒ Object



85
86
87
# File 'lib/pwl/locker.rb', line 85

def password_policy=(policy)
  @password_policy = policy
end

Instance Method Details

#add(entry_or_key, value = nil) ⇒ Object

Store entry or value under key



143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# 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

#allObject

Return all entries as array



190
191
192
193
194
195
196
# 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

#authenticateObject

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



116
117
118
119
120
121
122
123
124
125
# 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) ⇒ Object

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.



202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# 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

#createdObject

Return the date when the locker was created



229
230
231
# File 'lib/pwl/locker.rb', line 229

def created
  @backend.transaction(true){@backend[:system][:created]}
end

#delete(key) ⇒ Object

Delete the value that is stored under key and return it

Raises:



162
163
164
165
166
167
168
169
170
# 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) ⇒ Object

Return the value stored under key

Raises:



130
131
132
133
134
135
136
137
138
# 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_accessedObject

Return the date when the locker was last accessed



236
237
238
# File 'lib/pwl/locker.rb', line 236

def last_accessed
  @backend.transaction(true){@backend[:system][:last_accessed]}
end

#last_modifiedObject

Return the date when the locker was last modified



243
244
245
# File 'lib/pwl/locker.rb', line 243

def last_modified
  @backend.transaction(true){@backend[:system][:last_modified]}
end

#list(filter = nil) ⇒ Object

Return all keys, optionally filtered by filter



175
176
177
178
179
180
181
182
183
184
185
# 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

#pathObject

Return the path to the file backing this locker



250
251
252
# File 'lib/pwl/locker.rb', line 250

def path
  @backend.path
end

#reset!Object

(Re-) Initialize the database



104
105
106
107
108
109
110
111
# 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