File Coverage

blib/lib/Dancer2/Plugin/Auth/Extensible/Provider/ActiveDirectory.pm
Criterion Covered Total %
statement 18 62 29.0
branch 0 20 0.0
condition 0 8 0.0
subroutine 6 11 54.5
pod 3 3 100.0
total 27 104 25.9


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::Extensible::Provider::ActiveDirectory;
2              
3 1     1   45735 use feature qw/state/;
  1         1  
  1         65  
4 1     1   3 use Carp qw/croak/;
  1         1  
  1         43  
5 1     1   414 use Dancer2::Core::Types qw/HashRef Str Int ArrayRef/;
  1         7725  
  1         86  
6 1     1   418 use Auth::ActiveDirectory;
  1         109198  
  1         30  
7              
8 1     1   528 use Moo;
  1         6668  
  1         5  
9             with "Dancer2::Plugin::Auth::Extensible::Role::Provider";
10 1     1   1525 use namespace::clean;
  1         8003  
  1         4  
11              
12             our $VERSION = '0.03';
13              
14             =head1 NAME
15              
16             Dancer2::Plugin::Auth::Extensible::Provider::ActiveDirectory - ActiveDirectory authentication provider for Dancer2::Plugin::Auth::Extensible
17              
18             =head1 DESCRIPTION
19              
20             This class is an ActiveDirectory authentication provider.
21              
22             See L<Dancer2::Plugin::Auth::Extensible> for details on how to use the
23             authentication framework.
24              
25             =head1 ATTRIBUTES
26              
27             =head2 host
28              
29             The ActiveDirectory host name or IP address passed to L<Auth::ActiveDirectory>.
30              
31             Required.
32              
33             =cut
34              
35             has host => (
36             is => 'ro',
37             isa => Str,
38             required => 1,
39             );
40              
41             =head2 port
42              
43             The ActiveDirectory port. Defaults to 389.
44              
45             =cut
46              
47             has port => (
48             is => 'ro',
49             isa => Int,
50             default => 389,
51             );
52              
53             =head2 timeout
54              
55             Connection timeout in seconds. Defaults to 60.
56              
57             =cut
58              
59             has timeout => (
60             is => 'ro',
61             isa => Int,
62             timeout => 60,
63             );
64              
65             =head2 domain
66              
67             The ActiveDirectory domain.
68              
69             Required.
70              
71             =cut
72              
73             has domain => (
74             is => 'ro',
75             isa => Str,
76             required => 1,
77             );
78              
79             =head2 principal
80              
81             The ActiveDirectory principal.
82              
83             Required.
84              
85             =cut
86              
87             has principal => (
88             is => 'ro',
89             isa => Str,
90             required => 1,
91             );
92              
93             =head2 allowed_groups
94              
95             List of groups allowed to login. If empty, all groups are allowed.
96              
97             =cut
98              
99             has allowed_groups => (
100             is => 'ro',
101             isa => ArrayRef,
102             default => sub { return [] },
103             );
104              
105             =head1 METHODS
106              
107             =head2 authenticate_user $username, $password
108              
109             Returns true if the user could be authenticated. Returns also false if the user could be
110             authenticated, but is member of any allowed group.
111              
112             =cut
113              
114             sub authenticate_user {
115 0     0 1   my ( $self, $username, $password ) = @_;
116              
117 0 0 0       croak "username and password must be defined"
118             unless defined $username && defined $password;
119              
120 0 0         my $ad = $self->_active_directory or return;
121              
122 0   0       my $user = $ad->authenticate( $username, $password ) || return;
123              
124 0           my $allowed = 1;
125 0 0         if ( @{ $self->allowed_groups } ) {
  0            
126 0           $allowed = 0;
127 0           for my $allowed_group ( @{ $self->allowed_groups } ) {
  0            
128 0 0         if (grep { $_ eq $allowed_group } map { $_->name } @{ $user->groups } ) {
  0            
  0            
  0            
129 0           $allowed = 1;
130 0           last;
131             }
132             }
133             }
134              
135 0 0         if ($allowed) {
136 0           $self->_user_cache($username, $user);
137 0           return 1;
138             }
139 0           return;
140             }
141              
142             =head2 get_user_details $username
143              
144             =cut
145              
146             sub get_user_details {
147 0     0 1   my ( $self, $username ) = @_;
148              
149 0 0         croak "username must be defined" unless defined $username;
150              
151 0           my $user = $self->_user_cache($username);
152 0 0         if (!$user) {
153 0           $self->plugin->app->log( debug => "User information not found: $username" );
154             }
155              
156             return {
157             username => $user->uid,
158             display_name => $user->display_name,
159             firstname => $user->firstname,
160             surname => $user->surname,
161             email => $user->mail,
162 0           roles => [ map { $_->name } @{ $user->groups } ],
  0            
  0            
163             };
164             }
165              
166             =head2 get_user_roles
167              
168             =cut
169              
170             sub get_user_roles {
171 0     0 1   my ( $self, $username ) = @_;
172              
173 0 0         croak "username must be defined"
174             unless defined $username;
175              
176 0           my $user = $self->_user_cache($username);
177 0 0         if (!$user) {
178 0           $self->plugin->app->log( debug => "User information not found: $username" );
179 0           return;
180             }
181              
182 0           return [ map { $_->name } @{ $user->groups } ];
  0            
  0            
183             }
184              
185             =head1 PRIVATE METHODS
186              
187             =head2 _active_directory
188              
189             Returns a connected L<Auth::ActiveDirectory> object.
190              
191             =cut
192              
193             sub _active_directory {
194 0     0     my $self = shift;
195              
196 0   0       my $ad = Auth::ActiveDirectory->new(
197             host => $self->host,
198             port => $self->port,
199             timeout => $self->timeout,
200             domain => $self->domain,
201             principal => $self->principal,
202             ) || croak "ActiveDirectory connect failed for: " . $self->host;
203              
204 0           return $ad;
205             }
206              
207             =head2 _user_cache $username [, $value]
208              
209             Implements the user data cache. As we can only receive the user
210             information from L<Auth::ActiveDirectory> when authenticating,
211             we save it in the cache and can use it when asked via the get_user_details or get_user_roles methods.
212              
213             =cut
214              
215             sub _user_cache {
216 0     0     my ( $self, $username, $value ) = @_;
217 0           state $usercache = {};
218 0 0         return $usercache->{$username} if not defined $value;
219 0           return $usercache->{$username} = $value;
220             }
221              
222             1;
223              
224             =head1 TODO
225              
226             I was not able to create useful tests for this module. I tried to adopt the tests
227             from L<Dancer2::Plugin::Auth::Extensible::Provider::LDAP>, but I was not able to fill
228             the mock object with the correct data for the Test App to work. Please tell me if you
229             can help out.
230              
231              
232             =head1 AUTHOR
233              
234             Dominic Sonntag, C<< <dsonntag at cpan.org> >>
235              
236             =head1 BUGS
237              
238             Please report any bugs or feature requests to C<bug-dancer2-plugin-auth-extensible-provider-activedirectory at rt.cpan.org>, or through
239             the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Dancer2-Plugin-Auth-Extensible-Provider-ActiveDirectory>. I will be notified, and then you'll
240             automatically be notified of progress on your bug as I make changes.
241              
242              
243              
244              
245             =head1 SUPPORT
246              
247             You can find documentation for this module with the perldoc command.
248              
249             perldoc Dancer2::Plugin::Auth::Extensible::Provider::ActiveDirectory
250              
251             If you want to contribute to this module, write me an email or create a
252             Pull request on Github: L<https://github.com/sonntagd/Dancer2-Plugin-Auth-Extensible-Provider-ActiveDirectory>
253              
254              
255             =head1 ACKNOWLEDGEMENTS
256              
257              
258             =head1 LICENSE AND COPYRIGHT
259              
260             Copyright 2016 Dominic Sonntag.
261              
262             This program is free software; you can redistribute it and/or modify it
263             under the terms of the the Artistic License (2.0). You may obtain a
264             copy of the full license at:
265              
266             L<http://www.perlfoundation.org/artistic_license_2_0>
267              
268             Any use, modification, and distribution of the Standard or Modified
269             Versions is governed by this Artistic License. By using, modifying or
270             distributing the Package, you accept this license. Do not use, modify,
271             or distribute the Package, if you do not accept this license.
272              
273             If your Modified Version has been derived from a Modified Version made
274             by someone other than you, you are nevertheless required to ensure that
275             your Modified Version complies with the requirements of this license.
276              
277             This license does not grant you the right to use any trademark, service
278             mark, tradename, or logo of the Copyright Holder.
279              
280             This license includes the non-exclusive, worldwide, free-of-charge
281             patent license to make, have made, use, offer to sell, sell, import and
282             otherwise transfer the Package with respect to any patent claims
283             licensable by the Copyright Holder that are necessarily infringed by the
284             Package. If you institute patent litigation (including a cross-claim or
285             counterclaim) against any party alleging that the Package constitutes
286             direct or contributory patent infringement, then this Artistic License
287             to you shall terminate on the date that such litigation is filed.
288              
289             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
290             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
291             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
292             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
293             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
294             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
295             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
296             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
297              
298              
299             =cut
300              
301             1; # End of Dancer2::Plugin::Auth::Extensible::Provider::ActiveDirectory