1package Dancer2::Plugin::Auth::Extensible::Provider::Unix;
2
3use Authen::Simple::PAM;
4use Unix::Passwd::File;
5use Moo;
6with "Dancer2::Plugin::Auth::Extensible::Role::Provider";
7use namespace::clean;
8
9our $VERSION = '0.710';
10
11=head1 NAME
12
13Dancer2::Plugin::Auth::Extensible::Unix - authenticate *nix system accounts
14
15=head1 DESCRIPTION
16
17An authentication provider for L<Dancer2::Plugin::Auth::Extensible> which
18authenticates Linux/Unix system accounts.
19
20Uses C<getpwnam> and C<getgrent> to read user and group details,
21and L<Authen::Simple::PAM> to perform authentication via PAM.
22
23Unix group membership is used as a reasonable facsimile for roles - this seems
24sensible.
25
26B<WARNING>: in order to use PAM authentication on most modern Linux/UNIX
27systems the application performing authentication must have read access
28to the C</etc/shadow> file. B<This is a security risk> since it can lead
29to accidental disclosure of sensitive data if you have any path traversal
30vulnerabilities, etc. We strongly recommend B<AGAINST> using this module
31and provide it purely as an example. Any use of it B<IS AT YOUR OWN RISK>.
32You have been warned.
33
34=head1 METHODS
35
36=head2 authenticate_user $username, $password
37
38=cut
39
40sub authenticate_user {
41    my ($class, $username, $password) = @_;
42    my $pam = Authen::Simple::PAM->new( service => 'login' );
43    return $pam->authenticate($username, $password);
44}
45
46=head2 get_user_details $username
47
48Returns information from the C<passwd> file as a hash reference with the
49following keys: uid, gid, quota, comment, gecos,  dir, shell, expire
50
51=cut
52
53sub get_user_details {
54    my ($class, $username) = @_;
55
56    my @result = getpwnam($username);
57
58    return unless @result;
59
60    return {
61        uid      => $result[2],
62        gid      => $result[3],
63        quota    => $result[4],
64        comment  => $result[5],
65        gecos    => $result[6],
66        dir      => $result[7],
67        shell    => $result[8],
68        expire   => $result[9],
69    };
70}
71
72=head2 get_user_roles $username
73
74=cut
75
76sub get_user_roles {
77    my ($class, $username) = @_;
78    my %roles;
79
80    # we also need gid from user_details since username might not be listed
81    # in the group file as being in that group
82    return unless my $user_details = $class->get_user_details($username);
83
84    my @primary_group = getgrgid($user_details->{gid}) if $user_details->{gid};
85
86    $roles{$primary_group[0]} = 1 if @primary_group;
87
88    while ( my ( $group_name, undef, undef, $members ) = getgrent() ) {
89        $roles{$group_name} = 1 if $members =~ m/\b$username\b/;
90    }
91    endgrent();
92
93    return [keys %roles];
94}
95
961;
97