File Coverage

blib/lib/Data/Validate/Sanctions/Redis.pm
Criterion Covered Total %
statement 27 63 42.8
branch 0 4 0.0
condition 0 17 0.0
subroutine 9 16 56.2
pod 4 4 100.0
total 40 104 38.4


line stmt bran cond sub pod time code
1             package Data::Validate::Sanctions::Redis;
2              
3 7     7   497935 use strict;
  7         36  
  7         230  
4 7     7   42 use warnings;
  7         18  
  7         210  
5              
6 7     7   48 use parent 'Data::Validate::Sanctions';
  7         16  
  7         62  
7              
8 7     7   548 use Data::Validate::Sanctions::Fetcher;
  7         17  
  7         180  
9 7     7   45 use Scalar::Util qw(blessed);
  7         15  
  7         421  
10 7     7   48 use List::Util qw(max);
  7         17  
  7         498  
11 7     7   2227 use JSON::MaybeUTF8 qw(encode_json_utf8 decode_json_utf8);
  7         18058  
  7         425  
12 7     7   1647 use YAML::XS qw(DumpFile);
  7         11139  
  7         368  
13 7     7   56 use Syntax::Keyword::Try;
  7         16  
  7         84  
14              
15             our $VERSION = '0.15'; # VERSION
16              
17             sub new {
18 0     0 1   my ($class, %args) = @_;
19              
20 0           my $self = {};
21              
22 0 0         $self->{connection} = $args{connection} or die 'Redis connection is missing';
23              
24 0           $self->{sources} = [keys Data::Validate::Sanctions::Fetcher::config(eu_token => 'dummy')->%*];
25              
26 0           $self->{args} = {%args};
27 0           $self->{last_time} = 0;
28 0   0       my $object = bless $self, ref($class) || $class;
29 0           $object->_load_data();
30              
31 0           return $object;
32             }
33              
34             sub set_sanction_file {
35 0     0 1   die 'Not applicable';
36             }
37              
38             sub get_sanction_file {
39 0     0 1   die 'Not applicable';
40             }
41              
42             sub get_sanctioned_info {
43 0     0 1   my $self = shift;
44              
45 0 0         die "This function can only be called on an object" unless $self;
46              
47 0           return Data::Validate::Sanctions::get_sanctioned_info($self, @_);
48             }
49              
50             sub _load_data {
51 0     0     my $self = shift;
52              
53 0   0       $self->{last_time} //= 0;
54 0   0       $self->{_data} //= {};
55 0   0       $self->{_sanctioned_name_tokens} //= {};
56 0   0       $self->{_token_sanctioned_names} //= {};
57              
58 0           my $last_time = $self->{last_time};
59 0           for my $source ($self->{sources}->@*) {
60             try {
61             $self->{_data}->{$source} //= {};
62              
63             my ($content, $verified, $updated, $error) = $self->{connection}->hmget("SANCTIONS::$source", qw/content verified updated error/)->@*;
64             $updated //= 0;
65             my $current_update_date = $self->{_data}->{$source}->{updated} // 0;
66             next if $current_update_date && $updated <= $current_update_date;
67              
68             $self->{_data}->{$source}->{content} = decode_json_utf8($content // '[]');
69             $self->{_data}->{$source}->{verified} = $verified // 0;
70             $self->{_data}->{$source}->{updated} = $updated;
71             $self->{_data}->{$source}->{error} = $error // '';
72             $last_time = $updated if $updated > $last_time;
73             } catch ($e) {
74             $self->{_data}->{$source}->{content} = [];
75             $self->{_data}->{$source}->{updated} = 0;
76             $self->{_data}->{$source}->{verified} = 0;
77             $self->{_data}->{$source}->{error} = "Failed to load from Redis: $e";
78             }
79 0           }
80 0           $self->{last_time} = $last_time;
81              
82 0           $self->_index_data();
83              
84 0           foreach my $sanctioned_name (keys $self->{_index}->%*) {
85 0           my @tokens = Data::Validate::Sanctions::_clean_names($sanctioned_name);
86 0           $self->{_sanctioned_name_tokens}->{$sanctioned_name} = \@tokens;
87 0           foreach my $token (@tokens) {
88 0           $self->{_token_sanctioned_names}->{$token}->{$sanctioned_name} = 1;
89             }
90             }
91              
92 0           return $self->{_data};
93             }
94              
95             sub _save_data {
96 0     0     my $self = shift;
97              
98 0           for my $source ($self->{sources}->@*) {
99 0           $self->{_data}->{$source}->{verified} = time;
100             $self->{connection}->hmset(
101             "SANCTIONS::$source",
102             updated => $self->{_data}->{$source}->{updated} // 0,
103             content => encode_json_utf8($self->{_data}->{$source}->{content} // []),
104             verified => $self->{_data}->{$source}->{verified},
105 0   0       error => $self->{_data}->{$source}->{error} // ''
      0        
      0        
106             );
107             }
108              
109 0           return;
110             }
111              
112             sub _default_sanction_file {
113 0     0     die 'Not applicable';
114             }
115              
116             1;
117             __END__
118              
119             =encoding utf-8
120              
121             =head1 NAME
122              
123             Data::Validate::Sanctions::Redis - An extension of L<Data::Validate::Sanctions::Redis> that stores sanction data in redis.
124              
125             =head1 SYNOPSIS
126             ## no critic
127             use Data::Validate::Sanctions::Redis;
128              
129             my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_read);
130              
131             # to validate clients by their name
132             print 'BAD' if $validator->is_sanctioned("$last_name $first_name");
133             # or by more profile data
134             print 'BAD' if $validator->get_sanctioned_info(first_name => $first_name, last_name => $last_name, date_of_birth => $date_of_birth)->{matched};
135              
136             # to update the sanction dataset (needs redis write access)
137             my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis_write); ## no critic
138             $validator->update_data(eu_token => $token);
139              
140             # create object from the parent (factory) class
141             my $validator = Data::Validate::Sanctions->new(storage => 'redis', connection => $redis_write);
142              
143             =head1 DESCRIPTION
144              
145             Data::Validate::Sanctions::Redis is a simple validitor to validate a name against sanctions lists.
146             For more details about the sanction sources please refer to the parent module L<Data::Validate::Sanctions>.
147              
148             =head1 METHODS
149              
150             =head2 new
151              
152             Create the object with the redis object:
153              
154             my $validator = Data::Validate::Sanctions::Redis->new(connection => $redis);
155              
156             =head2 is_sanctioned
157              
158             Checks if the input profile info matches a sanctioned entity.
159             The arguments are the same as those of B<get_sanctioned_info>.
160              
161             It returns 1 if a match is found, otherwise 0.
162              
163             =cut
164              
165             =head2 get_sanctioned_info
166              
167             Tries to find a match a sanction entry matching the input profile args.
168             It takes arguments in two forms. In the new API, it takes a hashref containing the following named arguments:
169              
170             =over 4
171              
172             =item * first_name: first name
173              
174             =item * last_name: last name
175              
176             =item * date_of_birth: (optional) date of birth as a string or epoch
177              
178             =item * place_of_birth: (optional) place of birth as a country name or code
179              
180             =item * residence: (optional) name or code of the country of residence
181              
182             =item * nationality: (optional) name or code of the country of nationality
183              
184             =item * citizen: (optional) name or code of the country of citizenship
185              
186             =item * postal_code: (optional) postal/zip code
187              
188             =item * national_id: (optional) national ID number
189              
190             =item * passport_no: (oiptonal) passort number
191              
192             =back
193              
194             For backward compatibility it also supports the old API, taking the following args:
195              
196             =over 4
197              
198             =item * first_name: first name
199              
200             =item * last_name: last name
201              
202             =item * date_of_birth: (optional) date of birth as a string or epoch
203              
204             =back
205              
206             It returns a hash-ref containg the following data:
207              
208             =over 4
209              
210             =item - matched: 1 if a match was found; 0 otherwise
211              
212             =item - list: the source for the matched entry,
213              
214             =item - matched_args: a name-value hash-ref of the similar arguments,
215              
216             =item - comment: additional comments if necessary,
217              
218             =back
219              
220             =cut
221              
222             =head2 update_data
223              
224             Fetches latest versions of sanction lists, and updates corresponding sections of stored file, if needed
225              
226             =head2 last_updated
227              
228             Returns timestamp of when the latest list was updated.
229             If argument is provided - return timestamp of when that list was updated.
230              
231             =head2 _name_matches
232              
233             Pass in the client's name and sanctioned individual's name to see if they are similar or not
234              
235             =head1 AUTHOR
236              
237             Binary.com E<lt>fayland@binary.comE<gt>
238              
239             =head1 COPYRIGHT
240              
241             Copyright 2022- Binary.com
242              
243             =head1 LICENSE
244              
245             This library is free software; you can redistribute it and/or modify
246             it under the same terms as Perl itself.
247              
248             =head1 SEE ALSO
249              
250             L<Data::Validate::Sanctions>
251              
252             L<Data::Validate::Sanctions::Fetcher>
253              
254             =cut