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   3748 use Moo;
  7         89524  
  7         38  
3 7     7   9315 use Carp;
  7         13  
  7         438  
4 7     7   2887 use Passwd::Keyring::Auto::Config;
  7         20  
  7         260  
5 7     7   58 use namespace::clean;
  7         12  
  7         56  
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 4     4 0 5859 my ($class, %args) = @_;
32 4         7 my %backend_args;
33 4         16 foreach my $arg_name (keys %args) {
34 8 100       41 unless($arg_name =~ /^(app|group|config|force|prefer|forbid)$/) {
35 4         12 $backend_args{$arg_name} = $args{$arg_name};
36 4         12 delete $args{$arg_name};
37             }
38             }
39 4         12 $args{backend_args} = \%backend_args;
40 4         73 return \%args;
41             }
42              
43             has '_config' => (
44             is=>'lazy', builder=> sub {
45 4     4   1499 my $self = shift;
46 4         92 return Passwd::Keyring::Auto::Config->new(location=>$self->config,
47             debug=>$self->debug);
48             });
49              
50             has 'debug' => (is=>'lazy', builder=>sub {
51 4 50   4   1373 return $ENV{PASSWD_KEYRING_DEBUG} ? 1 : 0;
52             });
53              
54             sub get_keyring {
55 4     4 0 8 my ($self) = @_;
56              
57 4         15 my $debug = $self->debug;
58 4         19 my $app = $self->app;
59 4         15 my $group = $self->group;
60              
61 4         15 my $config = $self->_config;
62              
63 4   33     3750 my $force = $self->force
64             || $ENV{PASSWD_KEYRING_FORCE}
65             || $config->force($app);
66              
67 4 50       14 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 4 50       22 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 4   50     62 my $forbid = $self->forbid
86             || [ split(/\s+/x, $ENV{PASSWD_KEYRING_FORBID}
87             || $config->forbid($app)
88             || '') ];
89 4   50     55 my $prefer = $self->prefer
90             || [ split(/\s+/x, $ENV{PASSWD_KEYRING_PREFER}
91             || $config->prefer($app)
92             || '') ];
93              
94 4 50       24 unless(ref($forbid)) {
95 0         0 $forbid = [$forbid];
96             }
97 4 50       14 unless(ref($prefer)) {
98 0         0 $prefer = [$prefer];
99             }
100              
101 4 50       10 if($debug) {
102 0         0 print STDERR "[Passwd::Keyring] Calculated param: forbid=[", join(", ", @$forbid), "]\n";
103             }
104 4 50       16 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 4         27 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 4 50       22 if($^O eq 'darwin') {
125 0         0 $candidates{'OSXKeychain'} += 100;
126             }
127              
128 4 50 33     26 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 4 50       10 if($ENV{GNOME_KEYRING_CONTROL}) {
134 0         0 $candidates{'Gnome'} += 100;
135             }
136              
137 4 50       15 if($ENV{DBUS_SESSION_BUS_ADDRESS}) {
138 0         0 $candidates{'KDEWallet'} += 10;
139             }
140              
141 4         6 my $prefer_bonus = 1_000_000;
142 4         12 foreach (@$prefer) {
143 0         0 $candidates{$_} += $prefer_bonus;
144 0         0 $prefer_bonus -= 1_000;
145             }
146              
147 4         12 delete $candidates{$_} foreach (@$forbid);
148              
149 4         17 my @attempts = grep { $candidates{$_} > 0 } keys %candidates;
  16         32  
150              
151 4 0       10 @attempts = sort { ($candidates{$b} <=> $candidates{$a})
  0         0  
152             ||
153             ($a cmp $b)
154             } @attempts;
155              
156 4 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 4         10 foreach my $keyring_name (@attempts) {
162 4         19 my $keyring = $self->_try_backend($keyring_name);
163 4 50       16 return $keyring if $keyring;
164             }
165              
166 4         783 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 4     4   6 my ($self, $backend_name) = @_;
181              
182 4         101 my $debug = $self->debug;
183              
184             # Sanity check
185 4 50       56 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 4         82 my @options = (
193             app => $self->app,
194             group => $self->group,
195 4         27 %{ $self->_config->backend_args($self->app, $backend_name) },
196 4         20 %{ $self->backend_args }
197             );
198              
199 4         13 my $keyring;
200 4         16 my $require = "Passwd/Keyring/$backend_name.pm";
201 4         9 my $module = "Passwd::Keyring::$backend_name";
202 4 50       12 if($debug) {
203 0         0 print STDERR "[Passwd::Keyring] Trying to load $module and setup it with (" . join(", ", @options) . ")\n";
204             }
205 4         7 eval {
206 4         765 require $require;
207 0         0 $keyring = $module->new(@options);
208             };
209 4 50       21 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 4         12 return $keyring;
217             }
218              
219             1;