File Coverage

blib/lib/PlugAuth/Plugin/Audit.pm
Criterion Covered Total %
statement 84 87 96.5
branch 6 12 50.0
condition 3 8 37.5
subroutine 16 17 94.1
pod 0 2 0.0
total 109 126 86.5


line stmt bran cond sub pod time code
1             package PlugAuth::Plugin::Audit;
2              
3 1     1   4 use strict;
  1         2  
  1         34  
4 1     1   4 use warnings;
  1         2  
  1         24  
5 1     1   84 use 5.010001;
  1         4  
6 1     1   4 use Role::Tiny::With;
  1         1  
  1         51  
7 1     1   5 use Path::Class::Dir;
  1         5  
  1         19  
8 1     1   4 use Path::Class::File;
  1         1  
  1         21  
9 1     1   4 use File::HomeDir;
  1         1  
  1         50  
10 1     1   4 use YAML::XS qw( Dump LoadFile );
  1         1  
  1         54  
11 1     1   4 use DateTime;
  1         1  
  1         806  
12              
13             with 'PlugAuth::Role::Plugin';
14              
15             # ABSTRACT: Audit log for authentication/authorization
16             our $VERSION = '0.35'; # VERSION
17              
18              
19             with 'PlugAuth::Role::Plugin';
20              
21             sub init
22             {
23 1     1 0 1 my($self) = @_;
24            
25             $self->app->routes->route('/audit')->name('audit_check')->get(sub {
26 1     1   5470 my($c) = @_;
27 1         33 my ($day,$month,$year) = (localtime(time))[3,4,5];
28 1         4 $year+=1900;
29 1         2 $month++;
30             $c->stash->{autodata} = {
31 1   50     14 today => join('-', $year, sprintf("%02d", $month), sprintf("%02d", $day)),
32             version => $PlugAuth::Plugin::Audit::VERSION // 'dev',
33             };
34 1         4 });
35            
36             $self->app->routes->route('/audit/today')->name('audit_today')->get(sub {
37 1     1   2393 my($c) = @_;
38 1         49 my ($day,$month,$year) = (localtime(time))[3,4,5];
39 1         5 $year+=1900;
40 1         1 $month++;
41 1         11 $c->redirect_to($c->url_for('audit', year => $year, month => sprintf("%02d", $month), day => sprintf("%02d", $day)));
42 1         246 });
43            
44             # TODO: provide an interface for this
45             # in Clustericious
46             my $auth = sub {
47 2     2   5332 my $c = shift;
48 2         14 my $plugin = $self->_self_auth_plugin;
49 2 50       21 return 1 unless defined $plugin;
50 0 0       0 return 0 unless $plugin->authenticate($c, 'ACPS');
51 0 0       0 return 0 unless $plugin->authorize($c, 'accounts', $c->req->url->path);
52 0         0 return 1;
53 1         234 };
54            
55       0     my $authz = sub {
56 1         2 };
57            
58             $self->app->routes->under->to({ cb => $auth })->route('/audit/:year/:month/:day')->name('audit')->get(sub {
59 2     2   278 my($c) = @_;
60 2         8 my $year = $c->stash('year');
61 2         25 my $month = $c->stash('month');
62 2         18 my $day = $c->stash('day');
63 2 50 33     54 return $c->render_message('not ok', 404)
      33        
64             unless $year =~ /^\d\d\d\d$/
65             && $month =~ /^\d\d?$/
66             && $day =~ /^\d\d?$/;
67 2         13 my $filename = $self->log_filename({ year => $year, month => $month, day => $day });
68 2 100       15 return $c->render_message('not ok', 404)
69             unless -r $filename;
70             my(@events) = map {
71 1         78 my $event = $_;
  1         196  
72 1         14 my $dt = DateTime->from_epoch( epoch => $event->{time} );
73 1         536 $dt->set_time_zone('local');
74 1         3621 $event->{time_epoch} = delete $event->{time};
75 1         5 $event->{time_human} = $dt->strftime("%a, %d %b %Y %H:%M:%S %z");
76 1         196 $event->{time_computer} = $dt->strftime("%Y-%m-%dT%H:%M:%S%z");
77 1         115 $event;
78             } LoadFile($filename->stringify);
79 1         7 $c->stash->{autodata} = \@events;
80 1         4 });
81            
82 1         356 my @event_names = qw(
83             create_user
84             delete_user
85             create_group
86             delete_group
87             update_group
88             grant
89             revoke
90             change_password
91             );
92            
93 1         2 foreach my $event_name (@event_names)
94             {
95             $self->app->on($event_name => sub {
96 1     1   24 my($app, $args) = @_;
97            
98 1         5 my %info = %$args;
99 1         4 $info{time} = time;
100 1         3 $info{event} = $event_name;
101 1         5 my $filename = $self->log_filename($info{time});
102 1         9 open(my $fh, '>>', $filename->stringify);
103 1         170 print $fh Dump(\%info);
104 1         50 close $fh;
105 8         36 });
106             }
107             }
108              
109             sub log_filename
110             {
111 3     3 0 5 my($self, $time) = @_;
112            
113 3         5 my($day, $month, $year);
114            
115 3 100       11 if(ref $time)
116             {
117 2         3 $day = $time->{day};
118 2         5 $month = $time->{month};
119 2         4 $year = $time->{year};
120             }
121             else
122             {
123 1         25 ($day,$month,$year) = (localtime($time))[3,4,5];
124 1         3 $year += 1900;
125 1         1 $month++;
126             }
127            
128 3         28 my $filename = Path::Class::File->new(
129             File::HomeDir->my_home,
130             '.plugauth_plugin_audit',
131             sprintf("%04d", $year),
132             sprintf("%02d", $month),
133             sprintf("%02d", $day),
134             'audit.log',
135             );
136 3         573 $filename->dir->mkpath(0,0700);
137 3         1000 $filename;
138             }
139              
140             1;
141              
142             __END__
143              
144             =pod
145              
146             =encoding UTF-8
147              
148             =head1 NAME
149              
150             PlugAuth::Plugin::Audit - Audit log for authentication/authorization
151              
152             =head1 VERSION
153              
154             version 0.35
155              
156             =head1 SYNOPSIS
157              
158             PlugAuth.conf:
159              
160             ---
161             plugins:
162             - PlugAuth::Plugin::Audit: {}
163              
164             =head1 ROUTES
165              
166             =head2 Public routes
167              
168             These routes work for unauthenticated and unauthorized users.
169              
170             =head3 GET /audit
171              
172             You can do a simple GET on this route to see if the plugin is loaded.
173             It will return a JSON string with the version of the plugin as the body
174             and 200 if the plugin is available, if not L<PlugAuth> will return 404.
175              
176             =head2 Accounts Routes
177              
178             These routes are available to users authenticates and authorized to perform
179             the 'accounts' action.
180              
181             =head3 GET /audit/:year/:month/:day
182              
183             Return the audit entries for the given day.
184              
185             =head3 GET /audit/today
186              
187             Redirects to the appropriate URL for today's audit log.
188              
189             =head1 AUTHOR
190              
191             Graham Ollis <gollis@sesda3.com>
192              
193             =head1 COPYRIGHT AND LICENSE
194              
195             This software is copyright (c) 2012 by NASA GSFC.
196              
197             This is free software; you can redistribute it and/or modify it under
198             the same terms as the Perl 5 programming language system itself.
199              
200             =cut