Class: Keepassx::Database

Inherits:
Object
  • Object
show all
Includes:
Utilities
Defined in:
lib/keepassx/database.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(opts) ⇒ Database

Returns a new instance of Database.



21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
# File 'lib/keepassx/database.rb', line 21

def initialize opts

  raw_db, @groups, @entries, @locked = '', [], [], true

  if opts.is_a? File
    self.path = opts.path
    raw_db = read opts
    initialize_database raw_db

  elsif opts.is_a? String
    self.path = opts
    raw_db = read opts if File.exist? opts
    initialize_database raw_db

  elsif opts.is_a? REXML::Document
    # TODO: Implement import method
    fail NotImplementedError

  elsif opts.is_a? Array
    initialize_database '' # Pass empty data to get header initialized
    # Avoid opts change by parse_data_array method
    opts.each { |item| parse_data_array item }
    # initialize_payload # Make sure payaload is available for checksum

  else
    fail TypeError, "Expected one of the File, String, " \
        "REXML::Document or Hast, got #{opts.class}"
  end

end

Instance Attribute Details

#headerObject (readonly)

BACKUP_GROUP_OPTIONS = { :title => :Backup, :icon => 4, :id => 0 }



8
9
10
# File 'lib/keepassx/database.rb', line 8

def header
  @header
end

#key_fileObject

Returns the value of attribute key_file.



9
10
11
# File 'lib/keepassx/database.rb', line 9

def key_file
  @key_file
end

#passwordObject

Returns the value of attribute password.



9
10
11
# File 'lib/keepassx/database.rb', line 9

def password
  @password
end

#pathObject

Returns the value of attribute path.



9
10
11
# File 'lib/keepassx/database.rb', line 9

def path
  @path
end

Class Method Details

.open(opts) {|db| ... } ⇒ Object

Yields:

  • (db)


12
13
14
15
16
17
18
# File 'lib/keepassx/database.rb', line 12

def self.open opts
  path = opts.to_s
  fail "File #{path} does not exist." unless File.exist? path
  db = self.new path
  return db unless block_given?
  yield db
end

Instance Method Details

#add(item, opts = {}) ⇒ Keepassx::Group, Keepassx::Entry

Add new item to database.

Parameters:

Returns:



204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/keepassx/database.rb', line 204

def add item, opts = {}
  if item.is_a? Symbol

    if item.eql? :group
      return add_group opts
    elsif item.eql? :entry
      return add_entry opts
    else
      fail "Unknown item type '#{item.to_s}'"
    end

  elsif item.is_a? Keepassx::Group
    return add_group item

  elsif item.is_a? Keepassx::Entry
    return add_entry item

  else
    fail "Could not add '#{item.inspect}'"
  end
end

#add_entry(opts) ⇒ Keepassx::Entry

Add new entry to database.

Parameters:

  • opts (Hash)

    Options that will be passed to Keepassx::Entry#new.

Returns:



270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
# File 'lib/keepassx/database.rb', line 270

def add_entry opts
  # FIXME: Add warnings and detailed description
  if opts.is_a? Hash
    opts = deep_copy opts

    # FIXME: Remove this feature as it has unpredictable behavior when groups with duplicate title are present
    if opts[:group].is_a? Symbol
      group = self.group opts[:group]
      fail "Group #{opts[:group].inspect} does not exist" if group.nil?
      opts[:group] = group
    end

    entry = Keepassx::Entry.new opts
    @entries << entry
    header.entry_number += 1
    entry

  elsif opts.is_a? Keepassx::Entry
    @entries << opts
    header.entry_number += 1
    opts
  else
    fail TypeError, "Expected Hash or Keepassx::Entry, got #{opts.class}"
  end
end

#add_group(opts) ⇒ Keepassx::Group

Add new group to database.

Parameters:

  • opts (Hash)

    Options that will be passed to Keepassx::Group#new.

Returns:



231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/keepassx/database.rb', line 231

def add_group opts

  if opts.is_a? Hash
    opts = deep_copy opts
    opts[:id] = next_group_id unless opts.has_key? :id

    if opts[:parent].is_a? Symbol
      group = self.group opts[:parent]
      fail "Group #{opts[:parent].inspect} does not exist" if group.nil?
      opts[:parent] = group
    end

    group = Keepassx::Group.new(opts)
    if group.parent.nil?
      @groups << group
    else
      @groups.insert last_sibling_index(group.parent) + 1, group
    end
    header.group_number += 1

    group
  elsif opts.is_a? Keepassx::Group
    # Assign parent group
    parent = opts.parent || nil
    @groups.insert last_sibling_index(parent) + 1, item
    header.group_number += 1
    opts

  else
    fail TypeError, "Expected Hash or Keepassx::Group, got #{opts.class}"
  end

end

#checksumString

Get actual payload checksum.

Returns:

  • (String)


415
416
417
# File 'lib/keepassx/database.rb', line 415

def checksum
  Digest::SHA256.digest payload
end

#delete(item, opts = {}) ⇒ Object

Delete item from database.

Parameters:

  • item (Keepassx::Group, Keepassx::Entry, Symbol)

    Item to delete.

  • opts (Hash) (defaults to: {})

    If first parameter is a Symbol, then this will be used to determine which item to delete.



302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
# File 'lib/keepassx/database.rb', line 302

def delete item, opts = {}
  if item.is_a? Keepassx::Group
    delete_group item

  elsif item.is_a? Keepassx::Entry
    delete_entry item

  elsif item.is_a? Symbol
    if item.eql? :group
      delete_group group(opts)

    elsif item.eql? :entry
      delete_entry entry(opts)

    else
      fail "Unknown item type '#{item.to_s}'"
    end
  end

end

#dump(password = nil, key_file = nil) ⇒ String

Get raw encoded database.

Parameters:

  • password (String) (defaults to: nil)

    Password the database will be encoded with.

  • key_file (String) (defaults to: nil)

    Path to key file.

Returns:

  • (String)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/keepassx/database.rb', line 58

def dump password = nil, key_file = nil
  # FIXME: Figure out what this is needed for
  # my $e = ($self->find_entries({title => 'Meta-Info', username => 'SYSTEM', comment => 'KPX_GROUP_TREE_STATE', url => '$'}))[0] || $self->add_entry({
  #     comment  => 'KPX_GROUP_TREE_STATE',
  #     title    => 'Meta-Info',
  #     username => 'SYSTEM',
  #     url      => '$',
  #     id       => '0000000000000000',
  #     group    => $g[0],
  #     binary   => {'bin-stream' => $bin},
  # });

  self.password = password unless password.nil?
  self.key_file = key_file unless key_file.nil?

  initialize_payload
  header.contents_hash = checksum
  encrypt

  header.encode << @encrypted_payload.to_s
end

#entries(opts = {}) ⇒ Array<Keepassx::Entry>

Get all matching entries.

Returns:



172
173
174
# File 'lib/keepassx/database.rb', line 172

def entries opts = {}
  get :entry, opts
end

#entry(opts = {}) ⇒ Keepassx::Entry

Get first matching entry.

Returns:



158
159
160
161
162
163
164
165
166
# File 'lib/keepassx/database.rb', line 158

def entry opts = {}
  entries = get :entry, opts
  if entries.empty?
    nil
  else
    entries.first
  end

end

#get(item_type, opts = {}) ⇒ Keepassx::Group, Keepassx::Entry

Search for items, using AND statement for the search conditions

Parameters:

  • item_type (Symbol)

    Can be :entry or :group.

  • opts (Hash) (defaults to: {})

    Search options.

Returns:



106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
# File 'lib/keepassx/database.rb', line 106

def get item_type, opts = {}

  case item_type
    when :entry
      item_list = @entries
    when :group
      item_list = @groups
    else
      fail "Unknown item type '#{item_type}'"
  end

  if opts.empty?
    # Return all items if no selection condition was provided
    items = item_list

  else
    opts = {:title => opts.to_s} if opts.is_a? String or opts.is_a? Symbol

    match_number = opts.length
    items = []
    opts.each do |k, v|
      items += Array(item_list.select { |e| e.send(k).eql?(v) })
    end

    buffer = Hash.new 0
    items.each do |e|
      buffer[e] += 1
    end


    # Select only items which matches all conditions
    items = []
    buffer.each do |k, v|
      items << k if v.eql? match_number
    end
  end

  if block_given?
    items.each do |i|
      yield i
    end

  else
    items
  end

end

#group(opts = {}) ⇒ Keepassx::Group

Get first matching group.

Returns:



180
181
182
183
184
185
186
187
188
# File 'lib/keepassx/database.rb', line 180

def group opts = {}
  groups = get :group, opts
  if groups.empty?
    nil
  else
    groups.first
  end

end

#groups(opts = {}) ⇒ Array<Keepassx::Group>

Get all matching groups.

Parameters:

  • opts (Hash) (defaults to: {})

Returns:



195
196
197
# File 'lib/keepassx/database.rb', line 195

def groups opts = {}
  get :group, opts
end

#index(v) ⇒ Fixnum

Get Group/Entry index in storage.

Returns:

  • (Fixnum)


383
384
385
386
387
388
389
390
391
392
393
394
# File 'lib/keepassx/database.rb', line 383

def index v
  if v.is_a? Keepassx::Group
    groups.find_index v

  elsif v.is_a? Keepassx::Entry
    entries.find_index v

  else
    fail "Cannot get index for #{v.class}"

  end
end

#lengthFixnum

Get Enries and Groups total number.

Returns:

  • (Fixnum)


400
401
402
403
404
405
406
407
408
409
# File 'lib/keepassx/database.rb', line 400

def length
  length = 0
  [@groups, @entries].each do |items|
    items.each do |item|
      length += item.length
    end
  end

  length
end

#locked?Boolean

Get lock state

Returns:

  • (Boolean)


375
376
377
# File 'lib/keepassx/database.rb', line 375

def locked?
  @locked
end

#next_group_idFixnum

Get next group ID number.

Returns:

  • (Fixnum)


423
424
425
426
427
428
429
430
431
432
433
434
435
# File 'lib/keepassx/database.rb', line 423

def next_group_id
  if groups.empty?
    # Start each time from 1 to make sure groups get the same id's for the
    # same input data
    1
  else
    id = groups.last.id
    loop do
      id += 1
      break id if groups.detect { |g| g.id.eql? id }.nil?
    end
  end
end

#save(password = nil, key_file = nil) ⇒ Fixnum

Save database to file storage

Parameters:

  • password (String) (defaults to: nil)

    Password the database will be encoded with.

  • key_file (String) (defaults to: nil)

    Path to key file.

Returns:

  • (Fixnum)


86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/keepassx/database.rb', line 86

def save password = nil, key_file = nil
  # TODO: Switch to rails style, i.e. save(:password => 'pass')
  fail TypeError, 'File path is not set' if path.nil?
  File.write path, dump(password, key_file)

# FIXME: Implement exceptions
rescue IOError => e
  warn ">>>> IOError in database.rb"
  fail
rescue SystemCallError => e
  warn ">>>> SystemCallError in database.rb"
  fail
end

#search(pattern) ⇒ Keepassx::Entry

Search entry by title.

Parameters:

  • pattern (String)

    Entry’s title to search for.

Returns:



354
355
356
357
358
359
360
361
# File 'lib/keepassx/database.rb', line 354

def search pattern
  # FIXME: Seqrch by any atribute by pattern
  backup = group 'Backup'

  entries.select do |e|
    e.group != backup && e.title =~ /#{pattern}/i
  end
end

#to_aArray

Dump Array representation of database.

Returns:

  • (Array)


461
462
463
464
465
# File 'lib/keepassx/database.rb', line 461

def to_a
  result = []
  groups(:level => 0).each { |group| result << build_branch(group) }
  result
end

#to_xmlREXML::Document

Dump database in XML.

Returns:

  • (REXML::Document)

    XML database representation.



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/keepassx/database.rb', line 441

def to_xml

  document = REXML::Document.new '<!DOCTYPE KEEPASSX_DATABASE><database/>'

  parent_element = document.root
  groups.each do |group|
    # xml = group.to_xml
    # parent_element = parent_element.add xml if group.parent.nil?
    section = parent_element.add group.to_xml
    entries(:group => group).each { |e| section.add e.to_xml }
    parent_element.add section
  end

  document
end

#unlock(password, key_file = nil) ⇒ Boolean

Unlock database.

Parameters:

  • password (String)

    Datbase password.

  • key_file (String) (defaults to: nil)

    Key file path.

Returns:

  • (Boolean)

    Whether or not password validation successfull.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
# File 'lib/keepassx/database.rb', line 329

def unlock password, key_file = nil

  return true unless locked?

  self.password = password unless password.nil?
  self.key_file = key_file unless key_file.nil?
  decrypt
  payload_io = StringIO.new payload

  initialize_groups Group.extract_from_payload header, payload_io
  @entries = Entry.extract_from_payload header, groups, payload_io
  @locked = false
  true
rescue OpenSSL::Cipher::CipherError
  false
rescue Keepassx::MalformedDataError
  fail
end

#valid?Boolean

Check database validity.

Returns:

  • (Boolean)


367
368
369
# File 'lib/keepassx/database.rb', line 367

def valid?
  header.valid?
end