File Coverage

blib/lib/Passwd/Keyring/Auto/Chooser.pm
Criterion Covered Total %
statement 67 98 68.3
branch 18 44 40.9
condition 4 12 33.3
subroutine 9 10 90.0
pod 0 2 0.0
total 98 166 59.0


line stmt bran cond sub pod time code
1             package Passwd::Keyring::Auto::Chooser;
2 7     7   3749 use Moo 1.001000;
  7         88705  
  7         37  
3 7     7   8609 use Carp;
  7         10  
  7         379  
4 7     7   2673 use Passwd::Keyring::Auto::Config;
  7         18  
  7         243  
5 7     7   53 use namespace::clean;
  7         8  
  7         43  
6              
7             =head1 NAME
8              
9             Passwd::Keyring::Auto::Chooser - actual implementation of keyring picking algorithm
10              
11             =head1 DESCRIPTION
12              
13             Internal object, not intended to be used directly.
14              
15             Implements prioritizing keyrings and finding the best suitable.
16              
17             See L<Passwd::Keyring::Auto> for algorithm description.
18              
19             =cut
20              
21              
22             has 'app' => (is=>'ro', default=>"Passwd::Keyring");
23             has 'group' => (is=>'ro', default=>"Passwd::Keyring passwords");
24             has 'config' => (is=>'ro');
25             has 'force' => (is=>'ro');
26             has 'prefer' => (is=>'ro');
27             has 'forbid' => (is=>'ro');
28             has 'backend_args' => (is=>'ro');
29              
30             sub BUILDARGS {
31 7     7 0 5622 my ($class, %args) = @_;
32 7         13 my %backend_args;
33 7         22 foreach my $arg_name (keys %args) {
34 14 100       69 unless($arg_name =~ /^(app|group|config|force|prefer|forbid)$/) {
35 7         16 $backend_args{$arg_name} = $args{$arg_name};
36 7         18 delete $args{$arg_name};
37             }
38             }
39 7         19 $args{backend_args} = \%backend_args;
40 7         149 return \%args;
41             }
42              
43             has '_config' => (
44             is=>'lazy', builder=> sub {
45 7     7   1550 my $self = shift;
46 7         129 return Passwd::Keyring::Auto::Config->new(location=>$self->config,
47             debug=>$self->debug);
48             });
49              
50             has 'debug' => (is=>'lazy', builder=>sub {
51 7 50   7   1622 return $ENV{PASSWD_KEYRING_DEBUG} ? 1 : 0;
52             });
53              
54             sub get_keyring {
55 7     7 0 14 my ($self) = @_;
56              
57 7         68 my $debug = $self->debug;
58 7         26 my $app = $self->app;
59 7         17 my $group = $self->group;
60              
61 7         63 my $config = $self->_config;
62              
63 7   33     4028 my $force = $self->force
64             || $ENV{PASSWD_KEYRING_FORCE}
65             || $config->force($app);
66              
67 7 50       25 if($debug) {
68 0   0     0 print STDERR "[Passwd::Keyring] Calculated param: force=", $force || '', "\n";
69             }
70              
71             #################################################################
72             # Fast path for force
73             #################################################################
74              
75 7 50       16 if($force) {
76 0         0 my $keyring = $self->_try_backend($force);
77 0 0       0 return $keyring if $keyring;
78 0         0 croak "Can not load enforced keyring $force";
79             }
80              
81             #################################################################
82             # Remaining params
83             #################################################################
84              
85 7   50     58 my $forbid = $self->forbid
86             || [ split(/\s+/x, $ENV{PASSWD_KEYRING_FORBID}
87             || $config->forbid($app)
88             || '') ];
89 7   50     68 my $prefer = $self->prefer
90             || [ split(/\s+/x, $ENV{PASSWD_KEYRING_PREFER}
91             || $config->prefer($app)
92             || '') ];
93              
94 7 50       22 unless(ref($forbid)) {
95 0         0 $forbid = [$forbid];
96             }
97 7 50       17 unless(ref($prefer)) {
98 0         0 $prefer = [$prefer];
99             }
100              
101 7 50       18 if($debug) {
102 0         0 print STDERR "[Passwd::Keyring] Calculated param: forbid=[", join(", ", @$forbid), "]\n";
103             }
104 7 50       20 if($debug) {
105 0         0 print STDERR "[Passwd::Keyring] Calculated param: prefer=[", join(", ", @$prefer), "]\n";
106             }
107              
108             #################################################################
109             # Selection and scoring of possible options.
110             #################################################################
111              
112             # Note: we prefer to check possibly wrong module than to miss some.
113              
114 7         28 my %candidates =( # name → score, score > 0 means possible
115             'Gnome' => 0,
116             'KDEWallet' => 0,
117             'OSXKeychain' => 0,
118             'Memory' => 1,
119             );
120              
121             # Scoring: +HUGE for preferred, +100 for session-related, +10 for
122             # sensible, +1 for possible
123              
124 7 50       28 if($^O eq 'darwin') {
125 0         0 $candidates{'OSXKeychain'} += 100;
126             }
127              
128 7 50 33     35 if( $ENV{DISPLAY} || $ENV{DESKTOP_SESSION} ) {
129 0         0 $candidates{'KDEWallet'} += 11; # To give it some boost, more portable
130 0         0 $candidates{'Gnome'} += 10;
131             }
132              
133 7 50       19 if($ENV{GNOME_KEYRING_CONTROL}) {
134 0         0 $candidates{'Gnome'} += 100;
135             }
136              
137 7 50       15 if($ENV{DBUS_SESSION_BUS_ADDRESS}) {
138 0         0 $candidates{'KDEWallet'} += 10;
139             }
140              
141 7         9 my $prefer_bonus = 1_000_000;
142 7         24 foreach (@$prefer) {
143 0         0 $candidates{$_} += $prefer_bonus;
144 0         0 $prefer_bonus -= 1_000;
145             }
146              
147 7         12 delete $candidates{$_} foreach (@$forbid);
148              
149 7         18 my @attempts = grep { $candidates{$_} > 0 } keys %candidates;
  28         48  
150              
151 7 0       14 @attempts = sort { ($candidates{$b} <=> $candidates{$a})
  0         0  
152             ||
153             ($a cmp $b)
154             } @attempts;
155              
156 7 50       13 if($debug) {
157 0         0 print STDERR "[Passwd::Keyring] Selected candidates(score): ",
158 0         0 join(", ", map { "$_($candidates{$_})" } @attempts), "\n";
159             }
160              
161 7         16 foreach my $keyring_name (@attempts) {
162 7         17 my $keyring = $self->_try_backend($keyring_name);
163 7 50       125 return $keyring if $keyring;
164             }
165              
166 0         0 croak "Could not load any keyring backend (attempted: " . join(", ", @attempts) . ")";
167             }
168              
169             sub _get_env {
170 0     0   0 my ($self, $name) = @_;
171 0         0 my $full_name = "PASSWD_KEYRING_" . $name;
172 0 0       0 if(exists $ENV{$full_name}) {
173 0         0 print STDERR "[Passwd::Keyring] Found (and using) environment variable $full_name: $ENV{$full_name}\n";
174 0         0 return $ENV{$full_name};
175             }
176             }
177              
178             # Loads module of given name or returns undef if it does not work
179             sub _try_backend {
180 7     7   10 my ($self, $backend_name) = @_;
181              
182 7         144 my $debug = $self->debug;
183              
184             # Sanity check
185 7 50       67 unless($backend_name =~ /^[A-Za-z][A-Za-z0-9_]*$/) {
186 0 0       0 if($debug) {
187 0         0 print STDERR "[Passwd::Keyring] Ignoring illegal backend name: $backend_name\n";
188             }
189 0         0 return undef;
190             }
191              
192 7         120 my @options = (
193             app => $self->app,
194             group => $self->group,
195 7         31 %{ $self->_config->backend_args($self->app, $backend_name) },
196 7         29 %{ $self->backend_args }
197             );
198              
199 7         21 my $keyring;
200 7         15 my $require = "Passwd/Keyring/$backend_name.pm";
201 7         13 my $module = "Passwd::Keyring::$backend_name";
202 7 50       17 if($debug) {
203 0         0 print STDERR "[Passwd::Keyring] Trying to load $module and setup it with (" . join(", ", @options) . ")\n";
204             }
205 7         10 eval {
206 7         2253 require $require;
207 7         1720 $keyring = $module->new(@options);
208             };
209 7 50       116 if($debug) {
210 0 0       0 unless($@) {
211 0         0 print STDERR "[Passwd::Keyring] Succesfully initiated $module, returning it\n";
212             } else {
213 0         0 print STDERR "[Passwd::Keyring] Attempt to use $module failed, error: $@\n";
214             }
215             }
216 7         19 return $keyring;
217             }
218              
219             1;