File Coverage

blib/lib/Catalyst/Authentication/Store/UserXML/User.pm
Criterion Covered Total %
statement 13 15 86.6
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 18 20 90.0


line stmt bran cond sub pod time code
1             package Catalyst::Authentication::Store::UserXML::User;
2              
3 1     1   8 use strict;
  1         2  
  1         56  
4 1     1   8 use warnings;
  1         2  
  1         95  
5              
6             our $VERSION = '0.03';
7              
8 1     1   6 use Moose;
  1         1  
  1         7  
9 1     1   8318 use Path::Class;
  1         4  
  1         113  
10 1     1   517 use XML::LibXML;
  0            
  0            
11             use Authen::Passphrase;
12             use Authen::Passphrase::BlowfishCrypt;
13             use Path::Class 0.26 'file';
14              
15             extends 'Catalyst::Authentication::User';
16              
17             has 'xml_filename' => (is=>'ro', isa=>'Path::Class::File', required => 1);
18             has 'xml' => (is=>'ro', isa=>'XML::LibXML::Document', lazy => 1, builder => '_build_xml');
19              
20             use overload '""' => sub { shift->username }, fallback => 1;
21              
22             my $OUR_NS = 'http://search.cpan.org/perldoc?Catalyst%3A%3AAuthentication%3A%3AStore%3A%3AUserXML';
23              
24             sub _build_xml {
25             my $self = shift;
26             my $xml_file = $self->xml_filename;
27              
28             return XML::LibXML->load_xml(
29             location => $xml_file
30             );
31             }
32              
33             sub get_node {
34             my ($self, $element_name) = @_;
35             my $dom = $self->xml->documentElement;
36              
37             my $xc = XML::LibXML::XPathContext->new($dom);
38             $xc->registerNs('userxml', $OUR_NS);
39             my ($node) = $xc->findnodes('//userxml:'.$element_name);
40              
41             return $node;
42             }
43              
44             sub get_node_text {
45             my ($self, $element_name) = @_;
46              
47             my $node = $self->get_node($element_name);
48             return undef unless $node;
49             return $node->textContent;
50             }
51              
52             *id = *username;
53             sub username { return $_[0]->get_node_text('username'); }
54             sub password_hash { return $_[0]->get_node_text('password'); }
55             sub status { return $_[0]->get_node_text('status') // 'active'; }
56              
57             sub supported_features {
58             return {
59             password => {
60             self_check => 1,
61             },
62             session => 1,
63             roles => 1,
64             };
65             }
66              
67             sub check_password {
68             my ( $self, $secret ) = @_;
69              
70             return 0 unless $self->status eq 'active';
71              
72             my $password_hash = $self->password_hash;
73             my $ppr = eval { Authen::Passphrase->from_rfc2307($password_hash) };
74             unless ($ppr) {
75             warn $@;
76             return;
77             }
78             return $ppr->match($secret);
79             }
80              
81             sub set_password {
82             my ( $self, $secret ) = @_;
83             my $password_el = $self->get_node('password');
84              
85             my $ppr = Authen::Passphrase::BlowfishCrypt->new(
86             cost => 8,
87             salt_random => 1,
88             passphrase => $secret,
89             );
90             $password_el->removeChildNodes();
91             $password_el->appendText($ppr->as_rfc2307);
92             $self->store;
93             }
94              
95             sub set_status {
96             my ( $self, $status ) = @_;
97             my $status_el = $self->get_node('status');
98             if (!$status_el) {
99             my $user_el = $self->get_node('password')->parentNode;
100             $user_el->appendText(' 'x4);
101             $status_el = $user_el->addNewChild($OUR_NS, 'status');
102             $user_el->appendText("\n");
103             }
104              
105             $status_el->removeChildNodes();
106             $status_el->appendText($status);
107              
108             $self->store;
109             }
110              
111             sub roles {
112             my $self = shift;
113              
114             my $node = $self->get_node('roles');
115             return () unless $node;
116              
117             my @roles;
118             my $xc = XML::LibXML::XPathContext->new($node);
119             $xc->registerNs('userxml', 'http://search.cpan.org/perldoc?Catalyst%3A%3AAuthentication%3A%3AStore%3A%3AUserXML');
120             foreach my $role_node ($xc->findnodes('//userxml:role')) {
121             push(@roles, $role_node->textContent)
122             }
123              
124             return @roles;
125             }
126              
127             sub for_session {
128             my $self = shift;
129             return $self->username;
130             }
131              
132             sub store {
133             my $self = shift;
134             file($self->xml_filename)->spew($self->xml->toString)
135             }
136              
137             1;
138              
139              
140             __END__
141              
142             =head1 SYNOPSIS
143              
144             my $user = Catalyst::Authentication::Store::UserXML::User->new({
145             xml_filename => $file
146             });
147             say $user->username;
148             die unless $user->check_password('secret');
149              
150             =head1 EXAMPLE
151              
152             <!-- userxml-folder/some-username -->
153             <user>
154             <username>some-username</username>
155             <password>{CLEARTEXT}secret</password>
156             </user>
157              
158             =head1 SEE ALSO
159              
160             L<Authen::Passphrase>
161              
162             =cut