package Catalyst::Authentication::Store::LDAP::AD::Simple;
use Moose;
use Net::LDAP;

=head1 Simple LDAP Interface

    - simple class to authenticate through LDAP to Active Directory

=head1 SYNOPSIS

    sub login : Regex('^login/(\w+)/(\w+)$') {
        my ( $self, $c ) = @_;

        my $lds = Hello::Logic::LDAP::Authorize::Simple->new(
            ldap_domain => 'ldap.domain.ru',
            global_user => "cn=BlaBla,ou=LaLaLAA,dc=domain,dc=ru",
            global_pass => "your_password",
            timeout     => 3
        );

        $lds->setup();

        my $auth = $lds->authenticate(
            login         => $c->req->captures->[0],
            password     => $c->req->captures->[1]
        );

        $c->response->body( $auth ? "Accepted" : "Denied" );

    }

    To access this action thought URL print in browser "domain.ru/login/your_login/your_password"

    global_user -     is the same as "dn" - distinguish name.
                    It is the root directory in Active Directory
                    structure which contains users information.
                    Users information contains at least users dn's (distinguish names).

    global_pass -     is the password for distinguish name.
    ldap_domain -     is a domain that can be accessed through LDAP protocol (ldap://).
    timeout     -   When we try to connect to AD, we send request to it and wait for
                    response. If AD is in down and doesn't send us any response for a
                    "timeout"-time we drop the connection and crashes to confess.

=cut

# public
has 'ldap_domain' => (is => 'rw', isa => 'Str', required => 1);
has 'global_user' => (is => 'rw', isa => 'Str', required => 1);
has 'global_pass' => (is => 'rw', isa => 'Str', required => 1);
has 'ldap_base'   => (is => 'rw', isa => 'Str', required => 1);
has 'timeout'     => (is => 'rw', isa => 'Int', required => 1, 'default' => 5);

# private
has 'ldap'     => (is => 'rw', isa    => 'Net::LDAP',    init_arg => undef);

=head1 FUNCTIONS

=over 4

=item setup

    - setting up connection to Active Directory
    Arguments:
        none
    Returns: 1

=cut

sub setup {

    my $self     = shift;

    # create LDAP object
    $self->ldap(Net::LDAP->new($self->ldap_domain) or confess "Net::LDAP error! $!");

    # timeout handler
    $SIG{ALRM} = sub {die("ALRM")};

    my $mesg;

    # try to connect to AD thruogh LDAP
    eval {
        alarm($self->timeout); # generate signal ALRM evnokes after "timeout" seconds from connection start

        # get directories & users
        $mesg = $self->ldap->bind(
                            $self->global_user,
            password    =>    $self->global_pass,
            version     =>     3
        ) or confess("Cant get directories & users");

        alarm(0);
    };

    # error handler
    if ($@) {
        if ($@ =~ /^ALRM/) {
            confess("LDAP connection timed out. No response retrieved from AD!");
        }
        else
        {
            confess("Error connecting to AD: \"$@\"");
        }
    }

    # if code is not zero - it means that there was an error
    confess("LDAP Error: @{[ $mesg->error ]}\n") if $mesg->code;

    $SIG{ALRM} = undef;

    return 1;
}


sub get_user {

=item authenticate

    - trying to authenticate at Active Directory
    Arguments:
        login         => '...',
    Returns: 0|1
        ldap_message->entries->[0] (all available user data retrieved from AD though LDAP)

=cut

    my $self = shift;

    my %arg = @_;

    unless (
        %arg &&
        $arg{login} &&
        $arg{login} =~ /\w/
    )
    {
        confess("No corect args given!");
    }

    # get user
    my $mesg = $self->ldap->search(
        base   => $self->ldap_base,
        filter => "(sAMAccountName=$arg{login})",
    );

    # bad request
    confess("Can't get current user information: $!\n") if $mesg->code;

#use Data::Dumper;
#warn "xY" x 50 . "MESG in load" . Dumper $mesg;

    my $user;

    # no user found
    unless ($mesg->entries) {
#        warn "(!!)" x 50;
        return 0;
    }
    else
    {
        $user = ($mesg->entries)[0];
    }

    # return authentication process bool result
    return !$mesg->code ? $user : undef;

}

sub authenticate {
    my ($self, $dn, $password) = @_;

#warn '[]' x 100;

    # get user distinguished name
    confess("no dn given! : $!\n") unless $dn;

    # timeout handler
    $SIG{ALRM} = sub {die("ALRM")};

    # message
    my $auth_mesg;

    # try to connect to AD thruogh LDAP
    eval {

        alarm($self->timeout);     # generate signal ALRM evnokes after "timeout" seconds
                                # from connection start

        # try to authorize user by DN and USER_PASS
        $auth_mesg = $self->ldap->bind($dn, password => $password)
                            or confess("Cant get directories & users");

        alarm(0);

    };

    # error handler
    if ($@) {
        if ($@ =~ /^ALRM/) {
        confess("LDAP connection timed out. No response retrieved from AD!");
        }
        else
        {
            confess("Error connecting to AD: $@");0
        }
    }

    # timeout handler
    $SIG{ALRM} = undef;

    # if there no error code in $auth_mesg user has been authenticated, else - NO
    return !$auth_mesg->code ? 1 : 0;
}

=back

=head1 AUTHOR

Chergik Andrey Vladimirovich

=head1 CONTACTS

email: andrey@chergik.ru

=head1 LICENSE

This library is free software. You can redistribute it and/or modify
it under the same terms as Perl itself.

=cut

no Moose;
__PACKAGE__->meta->make_immutable;
