]> Dogcows Code - chaz/p5-File-KDBX/blob - lib/File/KDBX/Entry.pm
2afe50fd155c879ca7338836b95bbe0492ff4063
[chaz/p5-File-KDBX] / lib / File / KDBX / Entry.pm
1 package File::KDBX::Entry;
2 # ABSTRACT: A KDBX database entry
3
4 use warnings;
5 use strict;
6
7 use Crypt::Misc 0.029 qw(decode_b64 encode_b32r);
8 use Devel::GlobalDestruction;
9 use Encode qw(encode);
10 use File::KDBX::Constants qw(:history :icon);
11 use File::KDBX::Error;
12 use File::KDBX::Util qw(:class :coercion :erase :function :uri generate_uuid load_optional);
13 use Hash::Util::FieldHash;
14 use List::Util qw(first sum0);
15 use Ref::Util qw(is_coderef is_hashref is_plain_hashref);
16 use Scalar::Util qw(looks_like_number);
17 use Storable qw(dclone);
18 use Time::Piece;
19 use boolean;
20 use namespace::clean;
21
22 extends 'File::KDBX::Object';
23
24 our $VERSION = '999.999'; # VERSION
25
26 my $PLACEHOLDER_MAX_DEPTH = 10;
27 my %PLACEHOLDERS;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
29
30 sub _parent_container { 'entries' }
31
32 =attr uuid
33
34 128-bit UUID identifying the entry within the database.
35
36 =attr icon_id
37
38 Integer representing a default icon. See L<File::KDBX::Constants/":icon"> for valid values.
39
40 =attr custom_icon_uuid
41
42 128-bit UUID identifying a custom icon within the database.
43
44 =attr foreground_color
45
46 Text color represented as a string of the form C<#000000>.
47
48 =attr background_color
49
50 Background color represented as a string of the form C<#FFFFFF>.
51
52 =attr override_url
53
54 TODO
55
56 =attr tags
57
58 Text string with arbitrary tags which can be used to build a taxonomy.
59
60 =attr auto_type
61
62 Auto-type details.
63
64 {
65 enabled => true,
66 data_transfer_obfuscation => 0,
67 default_sequence => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
68 associations => [
69 {
70 window => 'My Bank - Mozilla Firefox',
71 keystroke_sequence => '{PASSWORD}{ENTER}',
72 },
73 ],
74 }
75
76 =attr auto_type_enabled
77
78 Whether or not the entry is eligible to be matched for auto-typing.
79
80 =attr auto_type_data_transfer_obfuscation
81
82 TODO
83
84 =attr auto_type_default_sequence
85
86 The default auto-type keystroke sequence.
87
88 =attr auto_type_associations
89
90 An array of window title / keystroke sequence associations.
91
92 =attr previous_parent_group
93
94 128-bit UUID identifying a group within the database.
95
96 =attr quality_check
97
98 Boolean indicating whether the entry password should be tested for weakness and show up in reports.
99
100 =attr strings
101
102 Hash with entry strings, including the standard strings as well as any custom ones.
103
104 {
105 # Every entry has these five strings:
106 Title => { value => 'Example Entry' },
107 UserName => { value => 'jdoe' },
108 Password => { value => 's3cr3t', protect => true },
109 URL => { value => 'https://example.com' }
110 Notes => { value => '' },
111 # May also have custom strings:
112 MySystem => { value => 'The mainframe' },
113 }
114
115 =attr binaries
116
117 Files or attachments.
118
119 =attr custom_data
120
121 A set of key-value pairs used to store arbitrary data, usually used by software to keep track of state rather
122 than by end users (who typically work with the strings and binaries).
123
124 =attr history
125
126 Array of historical entries. Historical entries are prior versions of the same entry so they all share the
127 same UUID with the current entry.
128
129 =attr last_modification_time
130
131 Date and time when the entry was last modified.
132
133 =attr creation_time
134
135 Date and time when the entry was created.
136
137 =attr last_access_time
138
139 Date and time when the entry was last accessed.
140
141 =attr expiry_time
142
143 Date and time when the entry expired or will expire.
144
145 =attr expires
146
147 Boolean value indicating whether or not an entry is expired.
148
149 =attr usage_count
150
151 The number of times an entry has been used, which typically means how many times the B<Password> string has
152 been accessed.
153
154 =attr location_changed
155
156 Date and time when the entry was last moved to a different group.
157
158 =attr notes
159
160 Alias for the B<Notes> string value.
161
162 =attr password
163
164 Alias for the B<Password> string value.
165
166 =attr title
167
168 Alias for the B<Title> string value.
169
170 =attr url
171
172 Alias for the B<URL> string value.
173
174 =attr username
175
176 Aliases for the B<UserName> string value.
177
178 =cut
179
180 sub uuid {
181 my $self = shift;
182 if (@_ || !defined $self->{uuid}) {
183 my %args = @_ % 2 == 1 ? (uuid => shift, @_) : @_;
184 my $old_uuid = $self->{uuid};
185 my $uuid = $self->{uuid} = delete $args{uuid} // generate_uuid;
186 for my $entry (@{$self->history}) {
187 $entry->{uuid} = $uuid;
188 }
189 $self->_signal('uuid.changed', $uuid, $old_uuid) if defined $old_uuid && $self->is_current;
190 }
191 $self->{uuid};
192 }
193
194 # has uuid => sub { generate_uuid(printable => 1) };
195 has icon_id => ICON_PASSWORD, coerce => \&to_icon_constant;
196 has custom_icon_uuid => undef, coerce => \&to_uuid;
197 has foreground_color => '', coerce => \&to_string;
198 has background_color => '', coerce => \&to_string;
199 has override_url => '', coerce => \&to_string;
200 has tags => '', coerce => \&to_string;
201 has auto_type => {};
202 has previous_parent_group => undef, coerce => \&to_uuid;
203 has quality_check => true, coerce => \&to_bool;
204 has strings => {};
205 has binaries => {};
206 has times => {};
207 # has custom_data => {};
208 # has history => [];
209
210 has last_modification_time => sub { gmtime }, store => 'times', coerce => \&to_time;
211 has creation_time => sub { gmtime }, store => 'times', coerce => \&to_time;
212 has last_access_time => sub { gmtime }, store => 'times', coerce => \&to_time;
213 has expiry_time => sub { gmtime }, store => 'times', coerce => \&to_time;
214 has expires => false, store => 'times', coerce => \&to_bool;
215 has usage_count => 0, store => 'times', coerce => \&to_number;
216 has location_changed => sub { gmtime }, store => 'times', coerce => \&to_time;
217
218 # has 'auto_type.auto_type_enabled' => true, coerce => \&to_bool;
219 has 'auto_type_data_transfer_obfuscation' => 0, path => 'auto_type.data_transfer_obfuscation',
220 coerce => \&to_number;
221 has 'auto_type_default_sequence' => '{USERNAME}{TAB}{PASSWORD}{ENTER}',
222 path => 'auto_type.default_sequence', coerce => \&to_string;
223 has 'auto_type_associations' => [], path => 'auto_type.associations';
224
225 my %ATTRS_STRINGS = (
226 title => 'Title',
227 username => 'UserName',
228 password => 'Password',
229 url => 'URL',
230 notes => 'Notes',
231 );
232 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
233 no strict 'refs'; ## no critic (ProhibitNoStrict)
234 *{$attr} = sub { shift->string_value($string_key, @_) };
235 *{"expanded_${attr}"} = sub { shift->expanded_string_value($string_key, @_) };
236 }
237
238 my @ATTRS = qw(uuid custom_data history auto_type_enabled);
239 sub _set_nonlazy_attributes {
240 my $self = shift;
241 $self->$_ for @ATTRS, keys %ATTRS_STRINGS, list_attributes(ref $self);
242 }
243
244 sub init {
245 my $self = shift;
246 my %args = @_;
247
248 while (my ($key, $val) = each %args) {
249 if (my $method = $self->can($key)) {
250 $self->$method($val);
251 }
252 else {
253 $self->string($key => $val);
254 }
255 }
256
257 return $self;
258 }
259
260 ##############################################################################
261
262 =method string
263
264 \%string = $entry->string($string_key);
265
266 $entry->string($string_key, \%string);
267 $entry->string($string_key, %attributes);
268 $entry->string($string_key, $value); # same as: value => $value
269
270 Get or set a string. Every string has a unique (to the entry) key and flags and so are returned as a hash
271 structure. For example:
272
273 $string = {
274 value => 'Password',
275 protect => true, # optional
276 };
277
278 Every string should have a value (but might be C<undef> due to memory protection) and these optional flags
279 which might exist:
280
281 =for :list
282 * C<protect> - Whether or not the string value should be memory-protected.
283
284 =cut
285
286 sub string {
287 my $self = shift;
288 my %args = @_ == 2 ? (key => shift, value => shift)
289 : @_ % 2 == 1 ? (key => shift, @_) : @_;
290
291 if (!defined $args{key} && !defined $args{value}) {
292 my %standard = (value => 1, protect => 1);
293 my @other_keys = grep { !$standard{$_} } keys %args;
294 if (@other_keys == 1) {
295 my $key = $args{key} = $other_keys[0];
296 $args{value} = delete $args{$key};
297 }
298 }
299
300 my $key = delete $args{key} or throw 'Must provide a string key to access';
301
302 return $self->{strings}{$key} = $args{value} if is_plain_hashref($args{value});
303
304 while (my ($field, $value) = each %args) {
305 $self->{strings}{$key}{$field} = $value;
306 }
307
308 # Auto-vivify the standard strings.
309 if ($STANDARD_STRINGS{$key}) {
310 return $self->{strings}{$key} //= {value => '', $self->_protect($key) ? (protect => true) : ()};
311 }
312 return $self->{strings}{$key};
313 }
314
315 ### Get whether or not a standard string is configured to be protected
316 sub _protect {
317 my $self = shift;
318 my $key = shift;
319 return false if !$STANDARD_STRINGS{$key};
320 if (my $kdbx = eval { $self->kdbx }) {
321 my $protect = $kdbx->memory_protection($key);
322 return $protect if defined $protect;
323 }
324 return $key eq 'Password';
325 }
326
327 =method string_value
328
329 $string = $entry->string_value($string_key);
330
331 Access a string value directly. The arguments are the same as for L</string>. Returns C<undef> if the string
332 is not set or is currently memory-protected. This is just a shortcut for:
333
334 my $string = do {
335 my $s = $entry->string(...);
336 defined $s ? $s->{value} : undef;
337 };
338
339 =cut
340
341 sub string_value {
342 my $self = shift;
343 my $string = $self->string(@_) // return undef;
344 return $string->{value};
345 }
346
347 =method expanded_string_value
348
349 $string = $entry->expanded_string_value;
350
351 Same as L</string_value> but will substitute placeholders and resolve field references. Any placeholders that
352 do not expand to values are left as-is.
353
354 See L</Placeholders>.
355
356 Some placeholders (notably field references) require the entry be connected to a database and will throw an
357 error if it is not.
358
359 =cut
360
361 sub _expand_placeholder {
362 my $self = shift;
363 my $placeholder = shift;
364 my $arg = shift;
365
366 require File::KDBX;
367
368 my $placeholder_key = $placeholder;
369 if (defined $arg) {
370 $placeholder_key = $File::KDBX::PLACEHOLDERS{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
371 : "${placeholder}:";
372 }
373 return if !defined $File::KDBX::PLACEHOLDERS{$placeholder_key};
374
375 my $local_key = join('/', Hash::Util::FieldHash::id($self), $placeholder_key);
376 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
377 my $handler = $File::KDBX::PLACEHOLDERS{$placeholder_key} or next;
378 memoize recurse_limit($handler, $PLACEHOLDER_MAX_DEPTH, sub {
379 alert "Detected deep recursion while expanding $placeholder placeholder",
380 placeholder => $placeholder;
381 return; # undef
382 });
383 };
384
385 return $handler->($self, $arg, $placeholder);
386 }
387
388 sub _expand_string {
389 my $self = shift;
390 my $str = shift;
391
392 my $expand = memoize $self->can('_expand_placeholder'), $self;
393
394 # placeholders (including field references):
395 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
396
397 # environment variables (alt syntax):
398 my $vars = join('|', map { quotemeta($_) } keys %ENV);
399 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
400
401 return $str;
402 }
403
404 sub expanded_string_value {
405 my $self = shift;
406 my $str = $self->string_peek(@_) // return undef;
407 my $cleanup = erase_scoped $str;
408 return $self->_expand_string($str);
409 }
410
411 =method other_strings
412
413 $other = $entry->other_strings;
414 $other = $entry->other_strings($delimiter);
415
416 Get a concatenation of all non-standard string values. The default delimiter is a newline. This is is useful
417 for executing queries to search for entities based on the contents of these other strings (if any).
418
419 =cut
420
421 sub other_strings {
422 my $self = shift;
423 my $delim = shift // "\n";
424
425 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
426 return join($delim, @strings);
427 }
428
429 =method string_peek
430
431 $string = $entry->string_peek($string_key);
432
433 Same as L</string_value> but can also retrieve the value from protected-memory if the value is currently
434 protected.
435
436 =cut
437
438 sub string_peek {
439 my $self = shift;
440 my $string = $self->string(@_);
441 return defined $string->{value} ? $string->{value} : $self->kdbx->peek($string);
442 }
443
444 ##############################################################################
445
446 sub add_auto_type_association {
447 my $self = shift;
448 my $association = shift;
449 push @{$self->auto_type_associations}, $association;
450 }
451
452 sub expand_keystroke_sequence {
453 my $self = shift;
454 my $association = shift;
455
456 my $keys = is_hashref($association) && exists $association->{keystroke_sequence} ?
457 $association->{keystroke_sequence} : defined $association ? $association : '';
458
459 $keys = $self->auto_type_default_sequence if !$keys;
460 # TODO - Fall back to getting default sequence from parent group, which probably means we shouldn't be
461 # setting a default value in the entry..
462
463 return $self->_expand_string($keys);
464 }
465
466 ##############################################################################
467
468 sub binary {
469 my $self = shift;
470 my $key = shift or throw 'Must provide a binary key to access';
471 if (@_) {
472 my $arg = @_ == 1 ? shift : undef;
473 my %args;
474 @args{keys %$arg} = values %$arg if ref $arg eq 'HASH';
475 $args{value} = $arg if !ref $arg;
476 while (my ($field, $value) = each %args) {
477 $self->{binaries}{$key}{$field} = $value;
478 }
479 }
480 my $binary = $self->{binaries}{$key} //= {value => ''};
481 if (defined (my $ref = $binary->{ref})) {
482 $binary = $self->{binaries}{$key} = dclone($self->kdbx->binaries->{$ref});
483 }
484 return $binary;
485 }
486
487 sub binary_novivify {
488 my $self = shift;
489 my $binary_key = shift;
490 return if !$self->{binaries}{$binary_key} && !@_;
491 return $self->binary($binary_key, @_);
492 }
493
494 sub binary_value {
495 my $self = shift;
496 my $binary = $self->binary_novivify(@_) // return undef;
497 return $binary->{value};
498 }
499
500 sub searching_enabled {
501 my $self = shift;
502 my $parent = $self->group;
503 return $parent->effective_enable_searching if $parent;
504 return true;
505 }
506
507 sub auto_type_enabled {
508 my $self = shift;
509 $self->auto_type->{enabled} = to_bool(shift) if @_;
510 $self->auto_type->{enabled} //= true;
511 return false if !$self->auto_type->{enabled};
512 return true if !$self->is_connected;
513 my $parent = $self->group;
514 return $parent->effective_enable_auto_type if $parent;
515 return true;
516 }
517
518 ##############################################################################
519
520 =method hmac_otp
521
522 $otp = $entry->hmac_otp(%options);
523
524 Generate an HMAC-based one-time password, or C<undef> if HOTP is not configured for the entry. The entry's
525 strings generally must first be unprotected, just like when accessing the password. Valid options are:
526
527 =for :list
528 * C<counter> - Specify the counter value
529
530 To configure HOTP, see L</"One-time Passwords">.
531
532 =cut
533
534 sub hmac_otp {
535 my $self = shift;
536 load_optional('Pass::OTP');
537
538 my %params = ($self->_hotp_params, @_);
539 return if !defined $params{type} || !defined $params{secret};
540
541 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
542 $params{base32} = 1;
543
544 my $otp = eval {Pass::OTP::otp(%params, @_) };
545 if (my $err = $@) {
546 throw 'Unable to generate HOTP', error => $err;
547 }
548
549 $self->_hotp_increment_counter($params{counter});
550
551 return $otp;
552 }
553
554 =method time_otp
555
556 $otp = $entry->time_otp(%options);
557
558 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
559 strings generally must first be unprotected, just like when accessing the password. Valid options are:
560
561 =for :list
562 * C<now> - Specify the value for determining the time-step counter
563
564 To configure TOTP, see L</"One-time Passwords">.
565
566 =cut
567
568 sub time_otp {
569 my $self = shift;
570 load_optional('Pass::OTP');
571
572 my %params = ($self->_totp_params, @_);
573 return if !defined $params{type} || !defined $params{secret};
574
575 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
576 $params{base32} = 1;
577
578 my $otp = eval {Pass::OTP::otp(%params, @_) };
579 if (my $err = $@) {
580 throw 'Unable to generate TOTP', error => $err;
581 }
582
583 return $otp;
584 }
585
586 =method hmac_otp_uri
587
588 =method time_otp_uri
589
590 $uri_string = $entry->hmac_otp_uri;
591 $uri_string = $entry->time_otp_uri;
592
593 Get a HOTP or TOTP otpauth URI for the entry, if available.
594
595 To configure OTP, see L</"One-time Passwords">.
596
597 =cut
598
599 sub hmac_otp_uri { $_[0]->_otp_uri($_[0]->_hotp_params) }
600 sub time_otp_uri { $_[0]->_otp_uri($_[0]->_totp_params) }
601
602 sub _otp_uri {
603 my $self = shift;
604 my %params = @_;
605
606 return if 4 != grep { defined } @params{qw(type secret issuer account)};
607 return if $params{type} !~ /^[ht]otp$/i;
608
609 my $label = delete $params{label};
610 $params{$_} = uri_escape_utf8($params{$_}) for keys %params;
611
612 my $type = lc($params{type});
613 my $issuer = $params{issuer};
614 my $account = $params{account};
615
616 $label //= "$issuer:$account";
617
618 my $secret = $params{secret};
619 $secret = uc(encode_b32r($secret)) if !$params{base32};
620
621 delete $params{algorithm} if defined $params{algorithm} && $params{algorithm} eq 'sha1';
622 delete $params{period} if defined $params{period} && $params{period} == 30;
623 delete $params{digits} if defined $params{digits} && $params{digits} == 6;
624 delete $params{counter} if defined $params{counter} && $params{counter} == 0;
625
626 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
627
628 if (defined $params{encoder}) {
629 $uri .= "&encoder=$params{encoder}";
630 return $uri;
631 }
632 $uri .= '&algorithm=' . uc($params{algorithm}) if defined $params{algorithm};
633 $uri .= "&digits=$params{digits}" if defined $params{digits};
634 $uri .= "&counter=$params{counter}" if defined $params{counter};
635 $uri .= "&period=$params{period}" if defined $params{period};
636
637 return $uri;
638 }
639
640 sub _hotp_params {
641 my $self = shift;
642
643 my %params = (
644 type => 'hotp',
645 issuer => $self->title || 'KDBX',
646 account => $self->username || 'none',
647 digits => 6,
648 counter => $self->string_value('HmacOtp-Counter') // 0,
649 $self->_otp_secret_params('Hmac'),
650 );
651 return %params if $params{secret};
652
653 my %otp_params = $self->_otp_params;
654 return () if !$otp_params{secret} || $otp_params{type} ne 'hotp';
655
656 # $otp_params{counter} = 0
657
658 return (%params, %otp_params);
659 }
660
661 sub _totp_params {
662 my $self = shift;
663
664 my %algorithms = (
665 'HMAC-SHA-1' => 'sha1',
666 'HMAC-SHA-256' => 'sha256',
667 'HMAC-SHA-512' => 'sha512',
668 );
669 my %params = (
670 type => 'totp',
671 issuer => $self->title || 'KDBX',
672 account => $self->username || 'none',
673 digits => $self->string_value('TimeOtp-Length') // 6,
674 algorithm => $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
675 period => $self->string_value('TimeOtp-Period') // 30,
676 $self->_otp_secret_params('Time'),
677 );
678 return %params if $params{secret};
679
680 my %otp_params = $self->_otp_params;
681 return () if !$otp_params{secret} || $otp_params{type} ne 'totp';
682
683 return (%params, %otp_params);
684 }
685
686 # KeePassXC style
687 sub _otp_params {
688 my $self = shift;
689 load_optional('Pass::OTP::URI');
690
691 my $uri = $self->string_value('otp') || '';
692 my %params;
693 %params = Pass::OTP::URI::parse($uri) if $uri =~ m!^otpauth://!;
694 return () if !$params{secret} || !$params{type};
695
696 if (($params{encoder} // '') eq 'steam') {
697 $params{digits} = 5;
698 $params{chars} = '23456789BCDFGHJKMNPQRTVWXY';
699 }
700
701 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
702 my ($issuer, $user) = split(':', $params{label} // ':', 2);
703 $params{issuer} //= uri_unescape_utf8($issuer);
704 $params{account} //= uri_unescape_utf8($user);
705
706 $params{algorithm} = lc($params{algorithm}) if $params{algorithm};
707 $params{counter} = $self->string_value('HmacOtp-Counter') if $params{type} eq 'hotp';
708
709 return %params;
710 }
711
712 sub _otp_secret_params {
713 my $self = shift;
714 my $type = shift // return ();
715
716 my $secret_txt = $self->string_value("${type}Otp-Secret");
717 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
718 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
719 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
720
721 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
722 return () if $count == 0;
723 alert "Found multiple ${type}Otp-Secret strings", count => $count if 1 < $count;
724
725 return (secret => $secret_b32, base32 => 1) if defined $secret_b32;
726 return (secret => decode_b64($secret_b64)) if defined $secret_b64;
727 return (secret => pack('H*', $secret_hex)) if defined $secret_hex;
728 return (secret => encode('UTF-8', $secret_txt));
729 }
730
731 sub _hotp_increment_counter {
732 my $self = shift;
733 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
734
735 looks_like_number($counter) or throw 'HmacOtp-Counter value must be a number', value => $counter;
736 my $next = $counter + 1;
737 $self->string('HmacOtp-Counter', $next);
738 return $next;
739 }
740
741 ##############################################################################
742
743 =method size
744
745 $size = $entry->size;
746
747 Get the size (in bytes) of an entry.
748
749 B<NOTE:> This is not an exact figure because there is no canonical serialization of an entry. This size should
750 only be used as a rough estimate for comparison with other entries or to impose data size limitations.
751
752 =cut
753
754 sub size {
755 my $self = shift;
756
757 my $size = 0;
758
759 # tags
760 $size += length(encode('UTF-8', $self->tags // ''));
761
762 # attributes (strings)
763 while (my ($key, $string) = each %{$self->strings}) {
764 next if !defined $string->{value};
765 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $string->{value} // ''));
766 }
767
768 # custom data
769 while (my ($key, $item) = each %{$self->custom_data}) {
770 next if !defined $item->{value};
771 $size += length(encode('UTF-8', $key)) + length(encode('UTF-8', $item->{value} // ''));
772 }
773
774 # binaries
775 while (my ($key, $binary) = each %{$self->binaries}) {
776 next if !defined $binary->{value};
777 my $value_len = utf8::is_utf8($binary->{value}) ? length(encode('UTF-8', $binary->{value}))
778 : length($binary->{value});
779 $size += length(encode('UTF-8', $key)) + $value_len;
780 }
781
782 # autotype associations
783 for my $association (@{$self->auto_type->{associations} || []}) {
784 $size += length(encode('UTF-8', $association->{window}))
785 + length(encode('UTF-8', $association->{keystroke_sequence} // ''));
786 }
787
788 return $size;
789 }
790
791 ##############################################################################
792
793 sub history {
794 my $self = shift;
795 my $entries = $self->{history} //= [];
796 # FIXME - Looping through entries on each access is too expensive.
797 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
798 return $entries;
799 }
800
801 =method history_size
802
803 $size = $entry->history_size;
804
805 Get the size (in bytes) of all historical entries combined.
806
807 =cut
808
809 sub history_size {
810 my $self = shift;
811 return sum0 map { $_->size } @{$self->history};
812 }
813
814 =method prune_history
815
816 $entry->prune_history(%options);
817
818 Remove as many older historical entries as necessary to get under the database limits. The limits are taken
819 from the connected database (if any) or can be overridden with C<%options>:
820
821 =for :list
822 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
823 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
824
825 =cut
826
827 sub prune_history {
828 my $self = shift;
829 my %args = @_;
830
831 my $max_items = $args{max_items} // eval { $self->kdbx->history_max_items }
832 // HISTORY_DEFAULT_MAX_ITEMS;
833 my $max_size = $args{max_size} // eval { $self->kdbx->history_max_size }
834 // HISTORY_DEFAULT_MAX_SIZE;
835
836 # history is ordered oldest to youngest
837 my $history = $self->history;
838
839 if (0 <= $max_items && $max_items < @$history) {
840 splice @$history, -$max_items;
841 }
842
843 if (0 <= $max_size) {
844 my $current_size = $self->history_size;
845 while ($max_size < $current_size) {
846 my $entry = shift @$history;
847 $current_size -= $entry->size;
848 }
849 }
850 }
851
852 =method add_historical_entry
853
854 $entry->add_historical_entry($entry);
855
856 Add an entry to the history.
857
858 =cut
859
860 sub add_historical_entry {
861 my $self = shift;
862 delete $_->{history} for @_;
863 push @{$self->{history} //= []}, map { $self->_wrap_entry($_) } @_;
864 }
865
866 =method current_entry
867
868 $current_entry = $entry->current_entry;
869
870 Get an entry's current entry. If the entry itself is current (not historical), itself is returned.
871
872 =cut
873
874 sub current_entry {
875 my $self = shift;
876 my $group = $self->group;
877
878 if ($group) {
879 my $id = $self->uuid;
880 my $entry = first { $id eq $_->uuid } @{$group->entries};
881 return $entry if $entry;
882 }
883
884 return $self;
885 }
886
887 =method is_current
888
889 $bool = $entry->is_current;
890
891 Get whether or not an entry is considered current (i.e. not historical). An entry is current if it is directly
892 in the parent group's entry list.
893
894 =cut
895
896 sub is_current {
897 my $self = shift;
898 my $current = $self->current_entry;
899 return Hash::Util::FieldHash::id($self) == Hash::Util::FieldHash::id($current);
900 }
901
902 =method is_historical
903
904 $bool = $entry->is_historical;
905
906 Get whether or not an entry is considered historical (i.e. not current).
907
908 This is just the inverse of L</is_current>.
909
910 =cut
911
912 sub is_historical { !$_[0]->is_current }
913
914 ##############################################################################
915
916 sub _signal {
917 my $self = shift;
918 my $type = shift;
919 return $self->SUPER::_signal("entry.$type", @_);
920 }
921
922 sub _commit {
923 my $self = shift;
924 my $orig = shift;
925 $self->add_historical_entry($orig);
926 my $time = gmtime;
927 $self->last_modification_time($time);
928 $self->last_access_time($time);
929 }
930
931 sub label { shift->expanded_title(@_) }
932
933 1;
934 __END__
935
936 =head1 DESCRIPTION
937
938 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
939 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
940 that every entry has:
941
942 =for :list
943 * B<Title>
944 * B<UserName>
945 * B<Password>
946 * B<URL>
947 * B<Notes>
948
949 Beyond this, you can store any number of other strings and any number of binaries that you can use for
950 whatever purpose you want.
951
952 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
953 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
954 the attributes to see what's available.
955
956 A B<File::KDBX::Entry> is a subclass of L<File::KDBX::Object>.
957
958 =head2 Placeholders
959
960 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
961 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
962 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
963 of the same entry. If the B<UserName> string had a value of "batman", the B<URL> string would expand to
964 C<http://example.com?user=batman>.
965
966 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
967 brace, like C<{PLACEHOLDER:ARGUMENT}>.
968
969 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
970 This software supports many (but not all) of the placeholders documented there.
971
972 =head3 Entry Placeholders
973
974 =for :list
975 * ☑ C<{TITLE}> - B<Title> string
976 * ☑ C<{USERNAME}> - B<UserName> string
977 * ☑ C<{PASSWORD}> - B<Password> string
978 * ☑ C<{NOTES}> - B<Notes> string
979 * ☑ C<{URL}> - B<URL> string
980 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
981 * ☑ C<{URL:USERINFO}>
982 * ☑ C<{URL:USERNAME}>
983 * ☑ C<{URL:PASSWORD}>
984 * ☑ C<{URL:HOST}>
985 * ☑ C<{URL:PORT}>
986 * ☑ C<{URL:PATH}>
987 * ☑ C<{URL:QUERY}>
988 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
989 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
990 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
991 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
992 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
993 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
994 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
995 * ☑ C<{GROUP_PATH}> - Full path of the parent group
996 * ☑ C<{GROUP}> - Name of the parent group
997
998 =head3 Field References
999
1000 =for :list
1001 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
1002
1003 =head3 File path Placeholders
1004
1005 =for :list
1006 * ☑ C<{APPDIR}> - Program directory path
1007 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
1008 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
1009 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
1010 * ☑ C<{OPERA}> - Path to the Opera browser executable
1011 * ☑ C<{SAFARI}> - Path to the Safari browser executable
1012 * ☒ C<{DB_PATH}> - Full file path of the database
1013 * ☒ C<{DB_DIR}> - Directory path of the database
1014 * ☒ C<{DB_NAME}> - File name (including extension) of the database
1015 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
1016 * ☒ C<{DB_EXT}> - File name extension
1017 * ☑ C<{ENV_DIRSEP}> - Directory separator
1018 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
1019
1020 =head3 Date and Time Placeholders
1021
1022 =for :list
1023 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
1024 * ☑ C<{DT_YEAR}> - Year component of the current local date
1025 * ☑ C<{DT_MONTH}> - Month component of the current local date
1026 * ☑ C<{DT_DAY}> - Day component of the current local date
1027 * ☑ C<{DT_HOUR}> - Hour component of the current local time
1028 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
1029 * ☑ C<{DT_SECOND}> - Second component of the current local time
1030 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
1031 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
1032 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
1033 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
1034 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
1035 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
1036 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
1037
1038 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
1039
1040 =head3 Special Key Placeholders
1041
1042 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
1043 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
1044 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
1045
1046 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
1047 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
1048
1049 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
1050 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
1051
1052 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
1053 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
1054
1055 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
1056 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
1057
1058 =head3 Miscellaneous Placeholders
1059
1060 =for :list
1061 * ☒ C<{BASE}>
1062 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
1063 * ☒ C<{BASE:USERINFO}>
1064 * ☒ C<{BASE:USERNAME}>
1065 * ☒ C<{BASE:PASSWORD}>
1066 * ☒ C<{BASE:HOST}>
1067 * ☒ C<{BASE:PORT}>
1068 * ☒ C<{BASE:PATH}>
1069 * ☒ C<{BASE:QUERY}>
1070 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
1071 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
1072 * ☒ C<{CLIPBOARD-SET:/Text/}>
1073 * ☒ C<{CLIPBOARD}>
1074 * ☒ C<{CMD:/CommandLine/Options/}>
1075 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
1076 * ☑ C<{ENV:}> and C<%ENV%> - Environment variables
1077 * ☒ C<{GROUP_SEL_NOTES}>
1078 * ☒ C<{GROUP_SEL_PATH}>
1079 * ☒ C<{GROUP_SEL}>
1080 * ☒ C<{NEWPASSWORD}>
1081 * ☒ C<{NEWPASSWORD:/Profile/}>
1082 * ☒ C<{PASSWORD_ENC}>
1083 * ☒ C<{PICKCHARS}>
1084 * ☒ C<{PICKCHARS:Field:Options}>
1085 * ☒ C<{PICKFIELD}>
1086 * ☒ C<{T-CONV:/Text/Type/}>
1087 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
1088
1089 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
1090 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
1091 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
1092 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
1093
1094 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
1095 my ($entry) = @_;
1096 ...;
1097 };
1098
1099 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
1100 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
1101 strings or auto-complete key sequences.
1102
1103 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
1104 my ($entry, $arg) = @_; # ^ Notice the colon here
1105 ...;
1106 };
1107
1108 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
1109 everything after the colon and before the end of the placeholder is passed to your placeholder handler
1110 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value B<whatever>.
1111
1112 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1113 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1114 both with and without a colon (or they could be different subroutines):
1115
1116 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1117 (undef, my $arg) = @_;
1118 return defined $arg ? rand($arg) : rand;
1119 };
1120
1121 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1122 all the handlers:
1123
1124 %File::KDBX::PLACEHOLDERS = ();
1125
1126 =head2 One-time Passwords
1127
1128 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1129 configuration storage isn't completely standardized, but this module supports two predominant configuration
1130 styles:
1131
1132 =for :list
1133 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1134 * KeePassXC
1135
1136 B<NOTE:> To use this feature, you must install the suggested dependency:
1137
1138 =for :list
1139 * L<Pass::OTP>
1140
1141 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1142 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1143
1144 To configure TOTP in the KeePass 2 style, set the following strings:
1145
1146 =for :list
1147 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1148 C<HMAC-SHA-512>
1149 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1150 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1151 * C<TimeOtp-Secret> - Text string secret, OR
1152 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1153 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1154 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1155
1156 To configure HOTP in the KeePass 2 style, set the following strings:
1157
1158 =for :list
1159 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1160 is called
1161 * C<HmacOtp-Secret> - Text string secret, OR
1162 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1163 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1164 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1165
1166 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1167 these should actually be set or an error will be thrown.
1168
1169 Here's a basic example:
1170
1171 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1172 # OR
1173 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1174
1175 my $otp = $entry->time_otp;
1176
1177 =cut
This page took 0.105404 seconds and 3 git commands to generate.