File Coverage

blib/lib/Dancer2/Plugin/Auth/ActiveDirectory.pm
Criterion Covered Total %
statement 17 75 22.6
branch 0 18 0.0
condition n/a
subroutine 6 13 46.1
pod n/a
total 23 106 21.7


line stmt bran cond sub pod time code
1             package Dancer2::Plugin::Auth::ActiveDirectory;
2              
3             =head1 NAME
4              
5             Dancer2::Plugin::Auth::ActiveDirectory - Authentication module for MS ActiveDirectory
6              
7             =head1 VERSION
8              
9             Version 0.02
10              
11             =cut
12              
13             our $VERSION = '0.02';
14              
15 1     1   13344 use 5.10.0;
  1         2  
16 1     1   3 use strict;
  1         1  
  1         18  
17 1     1   2 use warnings FATAL => 'all';
  1         7  
  1         31  
18 1     1   477 use Dancer2::Plugin;
  1         139587  
  1         5  
19 1     1   21834 use Net::LDAP qw[];
  1         111470  
  1         24  
20 1     1   7 use Net::LDAP::Constant qw[LDAP_INVALID_CREDENTIALS];
  1         1  
  1         746  
21              
22             # -----------------------------------------------
23             # Preloaded methods go here.
24             # -----------------------------------------------
25             # Encapsulated class data.
26              
27             sub _authenticate {
28 0     0     my ( $dsl, $s_username, $s_auth_password ) = @_;
29 0           my $hr_stg = plugin_setting();
30 0           my $or_connection = Net::LDAP->new( $hr_stg->{host}, port => 389, timeout => 60 );
31 0 0         unless ( defined $or_connection ) {
32 0           my $host = $hr_stg->{host};
33 0           $dsl->error(qq/Failed to connect to '$host'. Reason: '$@'/);
34 0           return undef;
35             }
36 0           my $s_principal = $hr_stg->{principal};
37 0           my $s_user = sprintf( '%s@%s', $s_username, $s_principal );
38 0           my $message = $or_connection->bind( $s_user, password => $s_auth_password );
39 0 0         return undef if ( $dsl->_v_is_error( $message, $s_user ) );
40 0           my $s_domain = $hr_stg->{domain};
41 0           my $result = $or_connection->search( # perform a search
42             base => qq/dc=$s_principal,dc=$s_domain/,
43             filter => qq/(&(objectClass=person)(userPrincipalName=$s_user.$s_domain))/,
44             );
45 0           foreach ( $result->entries ) {
46 0           my $groups = [];
47 0           foreach my $group ( $_->get_value(q/memberOf/) ) {
48 0 0         push( @$groups, $1 ) if ( $group =~ m/^CN=(.*),OU=.*$/ );
49             }
50             return {
51 0           uid => $s_username,
52             firstname => $_->get_value(q/givenName/),
53             surname => $_->get_value(q/sn/),
54             groups => $groups,
55             rights => _rights_by_user( $hr_stg, $groups ),
56             user => $s_user,
57             };
58             }
59 0           return undef;
60             }
61              
62             sub _authenticate_config {
63 0     0     return plugin_setting();
64             }
65              
66             sub _has_right {
67 0     0     my ( $dsl, $o_session_user, $s_right_name ) = @_;
68 0           my $hr_rights = plugin_setting()->{rights};
69 0           my $s_ad_group = $hr_rights->{$s_right_name};
70 0           return grep( /$s_ad_group/, @{ $o_session_user->{groups} } );
  0            
71             }
72              
73             sub _list_users {
74 0     0     my ( $dsl, $o_session_user, $search_string ) = @_;
75 0           my $hr_stg = plugin_setting();
76 0           my $or_connection = Net::LDAP->new( $hr_stg->{host}, port => 389, timeout => 60 );
77 0 0         unless ( defined $or_connection ) {
78 0           my $host = $hr_stg->{host};
79 0           $dsl->error(qq/Failed to connect to '$host'. Reason: '$@'/);
80 0           return undef;
81             }
82 0           my $s_user = $o_session_user->{user};
83 0           my $message = $or_connection->bind( $s_user, password => $o_session_user->{password} );
84              
85 0 0         return undef if ( $dsl->_v_is_error( $message, $s_user ) );
86 0           my $s_principal = $hr_stg->{principal};
87 0           my $s_domain = $hr_stg->{domain};
88 0           my $result = $or_connection->search(
89             base => qq/dc=$s_principal,dc=$s_domain/,
90             filter => qq/(&(objectClass=person)(name=$search_string*))/,
91             );
92 0           my $return_names = [];
93 0           push( @$return_names, { name => $_->get_value(q/name/), uid => $_->get_value(q/sAMAccountName/), } ) foreach ( $result->entries );
94 0           return $return_names;
95             }
96              
97             sub _v_is_error {
98 0     0     my ( $dsl, $message, $s_user ) = @_;
99 0 0         if ( $message->is_error ) {
100 0           my $error = $message->error;
101 0 0         my $level = $message->code == LDAP_INVALID_CREDENTIALS ? 'debug' : 'error';
102 0           $dsl->error(qq/Failed to authenticate user '$s_user'. Reason: '$error'/);
103 0           return 1;
104             }
105 0           return 0;
106             }
107              
108             sub _rights {
109 0     0     return plugin_setting()->{rights};
110             }
111              
112             sub _rights_by_user {
113 0     0     my ( $hr_stg, $a_user_groups ) = @_;
114 0           my $hr_rights = $hr_stg->{rights};
115 0 0         return unless $hr_rights;
116 0           my $ret_rights = {};
117 0           foreach ( keys %$hr_rights ) {
118 0           my $s_ad_group = $hr_rights->{$_};
119 0 0         $ret_rights->{$_} = 1 if ( grep( /$s_ad_group/, @{$a_user_groups} ) );
  0            
120             }
121 0           return $ret_rights;
122             }
123              
124             =head1 SYNOPSIS
125              
126             Configuration:
127              
128             plugins:
129             Auth::ActiveDirectory:
130             host: 0.0.0.0
131             principal: yourprincpal
132             domain: somedomain
133             rights:
134             definedright1: ad-group
135             definedright2: ad-group
136             definedright3: another-ad-group
137             definedright4: another-ad-group
138              
139             Code:
140              
141             post '/login' => sub {
142             session 'user' => authenticate( params->{user}, params->{pass} );
143             return template 'index', { html_error => 'Authentication failed!!' }
144             unless ( session('user') );
145             return template 'index', { html_error => 'No right for this page!!' }
146             if( !has_right( session('user'), 'definedright1') );
147             template 'index', { loggedin => 1 };
148             };
149              
150             =head1 SUBROUTINES/METHODS
151              
152             =head2 authenticate
153              
154             Basicaly the subroutine for authentication in the ActiveDirectory
155              
156             =cut
157              
158             register authenticate => \&_authenticate;
159              
160             =head2 authenticate_config
161              
162             Subroutine to get configuration for ActiveDirectory
163              
164             =cut
165              
166             register authenticate_config => \&_authenticate_config;
167              
168             =head2 has_right
169              
170             Check if loged in user has one of the configured rights
171              
172             =cut
173              
174             register has_right => \&_has_right;
175              
176             =head2 list_users
177              
178             =cut
179              
180             register list_users => \&_list_users;
181              
182             =head2 rights
183              
184             Subroutine to get configurated rights
185              
186             =cut
187              
188             register rights => \&_rights;
189              
190             =head2 for_versions
191              
192             =cut
193              
194             register_plugin for_versions => [2];
195              
196             1; # Dancer2::Plugin::Auth::ActiveDirectory
197              
198             __END__