# vim:set sw=4 ts=4 sts=4 ft=perl expandtab:
package Mojolicious::Plugin::FiatTux::GrantAccess;
use Mojo::Base 'Mojolicious::Plugin';

our $VERSION = '0.05';

sub register {
    my ($self, $app) = @_;

    if (defined($app->config('ldap')) || defined($app->config('htpasswd'))) {
        $app->plugin('FiatTux::Helpers');

        $app->sessions->default_expiration($app->config('session_duration'));

        if (defined($app->config('ldap'))) {
            require Net::LDAP;
        }
        if (defined($app->config('htpasswd'))) {
            require Apache::Htpasswd;
            die sprintf('Unable to read %s', $app->config('htpasswd')) unless -r $app->config('htpasswd');
        }
        $app->plugin('Authentication' => {
            autoload_user => 1,
            session_key   => ucfirst($app->moniker),
            load_user     => sub {
                my ($c, $username) = @_;
                return $username;
            },
            validate_user => sub {
                my ($c, $username, $password, $extradata) = @_;

                if ($c->config('ldap')) {
                    my $ldap             = Net::LDAP->new($c->config('ldap')->{uri});
                    my $ldap_user_attr   = $c->config('ldap')->{user_attr};
                    my $ldap_user_filter = $c->config('ldap')->{user_filter};
                    my $mesg;

                    # Start TLS options
                    $mesg = $ldap->start_tls($c->config('ldap')->{start_tls}) if $c->config('ldap')->{start_tls};

                    # LDAP binding
                    if (defined($c->config('ldap')->{bind_dn}) && defined($c->config('ldap')->{bind_pwd})) {
                        # connect to the ldap server using the bind credentials
                        $mesg = $ldap->bind(
                            $c->config('ldap')->{bind_dn},
                            password => $c->config('ldap')->{bind_pwd}
                        );
                    } else {
                        # anonymous bind
                        $mesg = $ldap->bind;
                    }

                    # Has LDAP binding succeed?
                    if ($mesg->code) {
                        $c->app->log->info(sprintf(
                            '[LDAP INFO] Authenticated bind failed - Login %s',
                            $c->config->{ldap}->{bind_dn}
                        )) if defined($c->config('ldap')->{bind_dn});
                        $c->app->log->error(sprintf('[LDAP ERROR] Error on bind: %s', $mesg->error));
                        return undef;
                    }

                    # Search the ldap database for the user who is trying to login
                    $mesg = $ldap->search(
                        base   => $c->config('ldap')->{user_tree},
                        filter => sprintf('(&(%s=%s)%s)', $ldap_user_attr, $username, $ldap_user_filter)
                    );

                    # Errorless search?
                    if ($mesg->code) {
                        $c->app->log->error(sprintf('[LDAP ERROR] Error on search: %s', $mesg->error));
                        return undef;
                    }

                    # Check to make sure that the ldap search returned at least one entry
                    my @entries = $mesg->entries;
                    my $entry   = $entries[0];
                    unless (defined $entry) {
                        $c->app->log->info(sprintf('LDAP INFO] Authentication failed - User %s filtered out, IP: %s', $username, $c->ip));
                        return undef;
                    }

                    # Retrieve the first user returned by the search
                    $c->app->log->debug(sprintf('[LDAP DEBUG] Found user dn: %s', $entry->dn));

                    # Now we know that the user exists
                    $mesg = $ldap->bind(
                        $entry->dn,
                        password => $password
                    );

                    # Was it the good password?
                    if ($mesg->code) {
                        $c->app->log->info(sprintf('[LDAP INFO] Authentication failed - Login: %s, IP: %s', $username, $c->ip));
                        $c->app->log->error(sprintf('[LDAP ERROR] Authentication failed: %s', $mesg->error));
                        return undef;
                    }

                    $c->app->log->info(sprintf('[LDAP INFO] Authentication successful - Login: %s, IP: %s', $username, $c->ip));
                } elsif ($c->config('htpasswd')) {
                    my $htpasswd = new Apache::Htpasswd({
                        passwdFile => $c->config('htpasswd'),
                        ReadOnly   => 1
                    });
                    if (!$htpasswd->htCheckPassword($username, $password)) {
                        return undef;
                    }
                    $c->app->log->info(sprintf('[Simple authentication successful] login: %s, IP: %s', $username, $c->ip));
                }
                return $username
            }
        });
    }
}

1;
__END__

=encoding utf8

=head1 NAME

Mojolicious::Plugin::FiatTux::GrantAccess - Mojolicious Plugin

=head1 SYNOPSIS

  # Mojolicious
  $self->plugin('FiatTux::GrantAccess');

  # Mojolicious::Lite
  plugin 'FiatTux::GrantAccess';

=head1 DESCRIPTION

L<Mojolicious::Plugin::FiatTux::GrantAccess> is a L<Mojolicious> plugin.

=head1 METHODS

L<Mojolicious::Plugin::FiatTux::GrantAccess> inherits all methods from
L<Mojolicious::Plugin> and implements the following new ones.

=head2 register

  $plugin->register(Mojolicious->new);

Register plugin in L<Mojolicious> application.

=head1 SEE ALSO

L<Mojolicious>, L<Mojolicious::Guides>, L<https://mojolicious.org>.

=cut
