File Coverage

blib/lib/Dancer2/Plugin/Auth/ActiveDirectory.pm
Criterion Covered Total %
statement 20 78 25.6
branch 0 18 0.0
condition n/a
subroutine 7 14 50.0
pod n/a
total 27 110 24.5


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