# Copyright 2003-2007, Fred Steinberg, Brown Bear Software

# LDAP settings

package SysLDAP;
use strict;

use CGI;

use Calendar::GetHTML;
use Calendar::User::LDAPAuth;
use Calendar::LDAPGroup;

use vars ('@ISA');
@ISA = ('Operation');

sub perform {
    my $self = shift;

    my ($save, $cancel, $test, $testEnum,
        $testEnumGroups, $testGroupMembers, $testGroupName,
        $testUserName, $testGroupsForUser) =
                    $self->getParams (qw (Save Cancel Test TestEnum
                                          TestEnumGroup TestGroupMembers
                                          TestGroupName TestUserName
                                          TestGroupsForUser));

    my $i18n  = $self->I18N;
    my $cgi   = new CGI;
    my $prefs = $self->prefs;

    my $message;

    if ($cancel or $self->calendarName) {
        print $self->redir ($self->makeURL ({Op => 'SysAdminPage'}));
        return;
    }

    if ($save) {
        $self->{audit_formsaved}++;

        my ($host, $port, $version, $base, $userAttrib, $userFilter,
            $groupBase, $groupAttrib, $groupFilter,
            $groupMemberAttrib, $userMembershipAttrib,
            $authDomain, $ldapUser, $ldapPass) =
            $self->getParams (qw/LDAPHost LDAPPort LDAPVersion LDAPBase
                                 LDAPUserAttrib LDAPUserFilter
                                 LDAPGroupBase LDAPGroupAttrib LDAPGroupFilter
                                 LDAPGroupMemberAttrib LDAPUserMembershipAttrib
                                 LDAPAuthDomain LDAPUsername LDAPPassword/);

        foreach ($host, $port, $base, $userAttrib, $userFilter,
                 $userMembershipAttrib,
                 $groupBase, $groupAttrib, $groupMemberAttrib, $groupFilter,
                 $authDomain, $ldapUser, $ldapPass) {
            next unless defined;
            s/^\s*//;
            s/\s*$//;
        }

        $self->{audit_LDAPHost}       = $prefs->LDAPHost;
        $self->{audit_LDAPPort}       = $prefs->LDAPPort;
        $self->{audit_LDAPVersion}    = $prefs->LDAPVersion;
        $self->{audit_LDAPBase}       = $prefs->LDAPBase;
        $self->{audit_LDAPUserAttrib} = $prefs->LDAPUserAttrib;
        $self->{audit_LDAPUserFilter} = $prefs->LDAPUserFilter;
        $self->{audit_LDAPGroupBase}   = $prefs->LDAPGroupBase;
        $self->{audit_LDAPGroupAttrib} = $prefs->LDAPGroupAttrib;
        $self->{audit_LDAPGroupMemberAttrib} = $prefs->LDAPGroupMemberAttrib;
        $self->{audit_LDAPGroupFilter} = $prefs->LDAPGroupFilter;
        $self->{audit_LDAPUserMembershipAttrib}
                                      = $prefs->LDAPUserMembershipAttrib;
        $self->{audit_LDAPAuthDomain} = $prefs->LDAPAuthDomain;
        $self->{audit_LDAPUsername}   = $prefs->LDAPUsername;
        $self->{audit_LDAPPassword}   = $prefs->LDAPPassword;

        my %newPrefs = (LDAPHost       => $host,
                        LDAPPort       => $port,
                        LDAPVersion    => $version,
                        LDAPBase       => $base,
                        LDAPUserAttrib => $userAttrib,
                        LDAPUserFilter => $userFilter,
                        LDAPGroupBase   => $groupBase,
                        LDAPGroupAttrib => $groupAttrib,
                        LDAPGroupMemberAttrib => $groupMemberAttrib,
                        LDAPGroupFilter => $groupFilter,
                        LDAPUserMembershipAttrib => $userMembershipAttrib,
                        LDAPAuthDomain => $authDomain,
                        LDAPUsername   => $ldapUser,
                        LDAPPassword   => $ldapPass);
        $self->db->setPreferences (\%newPrefs);
        $prefs = $self->prefs ('force');
    }

    my $host       = $prefs->LDAPHost    || 'localhost';
    my $port       = $prefs->LDAPPort    || 389;
    my $version    = $prefs->LDAPVersion || 3;
    my $baseDN     = $prefs->LDAPBase;
    my $userAttrib = $prefs->LDAPUserAttrib;
    my $userFilter = $prefs->LDAPUserFilter;
    my $groupBaseDN = $prefs->LDAPGroupBase;
    my $groupAttrib = $prefs->LDAPGroupAttrib;
    my $groupMemberAttrib = $prefs->LDAPGroupMemberAttrib;
    my $groupFilter = $prefs->LDAPGroupFilter;
    my $userMembershipAttrib = $prefs->LDAPUserMembershipAttrib;
    my $authDomain = $prefs->LDAPAuthDomain;
    my $ldapUser   = $prefs->LDAPUsername;
    my $ldapPass   = $prefs->LDAPPassword;

    if ($test) {
        my ($user, $pass) = $self->getParams (qw /TestName TestPass/);
        foreach ($user, $pass) {
            next unless defined;
            s/^\s*//;
            s/\s*$//;
        }

        if (User::LDAPAuth->checkPassword ($user, $pass)) {
            $message = "Test succeeded! Password authenticated for $user.";
        } else {
            $message = $User::LDAPAuth::lastError;
        }
    }
    if ($testEnum) {
        my @names = User::LDAPAuth->getUserNames;
        if (@names) {
            my $count = @names;
            $message = "Test succeeded! $count users found:<blockquote>";
            $message .= join ' ', sort @names;
            $message .= '</blockquote>';
        } elsif (my $err = $User::LDAPAuth::lastError) {
            $message = 'Test failed. ' . $err;
        } else {
            $message = "Test succeeded, but no users were found.";
        }
    }
    elsif ($testEnumGroups) {
        my @groups = LDAPGroup->getGroupNames;
        if (@groups) {
            my $count = @groups;
            $message = "Test succeeded! $count groups found:<blockquote>";
            $message .= join ' ', sort @groups;
            $message .= '</blockquote>';
        } elsif (my $err = $LDAPGroup::lastError) {
            $message = 'Test failed. ' . $err;
        } else {
            $message = "Test succeeded, but no groups were found.";
        }
    }
    elsif ($testGroupMembers) {
        my @members = LDAPGroup->getGroupMembers ($testGroupName);
        if (@members) {
            my $count = @members;
            $message = "Test succeeded! $count members found in " .
                       "'$testGroupName':<blockquote>";
            $message .= join ' ', sort @members;
            $message .= '</blockquote>';
        } elsif (my $err = $LDAPGroup::lastError) {
            $message = 'Test failed. ' . $err;
        } else {
            $message = "Test succeeded, but no group members were found " .
                       "in '$testGroupName':";
        }
    }
    elsif ($testGroupsForUser) {
        my @groups = User::LDAPAuth->get_group_ids_for_user ($testUserName);
        if (@groups) {
            my $count = @groups;
            $message = "Test succeeded! $count group(s) found for user " .
                       "'$testUserName':<blockquote>";
            $message .= join ' ', sort @groups;
            $message .= '</blockquote>';
        } elsif (my $err = $User::LDAPAuth::lastError) {
            $message = 'Test failed. ' . $err;
        } else {
            $message = "Test succeeded, but no groups were found for user " .
                       "'$testUserName'";
        }
    }

    # Display (or re-display) the form
    print GetHTML->startHTML (title  => $i18n->get ('LDAP Settings'),
                              op     => $self);
    print GetHTML->SysAdminHeader ($i18n, 'LDAP Authentication Settings', 1);
    print '<br>';

    print $cgi->h3 ($message) if $message;

    print $cgi->startform;

    my $serverNote = $i18n->get ('The full domain name of your LDAP server ' .
                                 '(e.g. ldap.domainname.com). If blank, it ' .
                                 'will default to the same machine Calcium ' .
                                 'is running on.');
    $serverNote .= ' ' . $i18n->get ('You can list multiple fallback servers; '
                                     . 'separate with spaces.');
    my $portNote = $i18n->get ('Standard LDAP port is 389');
    my $baseNote = $i18n->get ('Base Distinguished Name - e.g. ' .
                               '<b>dc=domain, dc=com, c=us</b>');
    my $uaNote   = $i18n->get ('Attribute name for users, e.g. <b>uid</b>');
    my $ufNote   = $i18n->get ('Search filter for enumerating all Calcium ' .
                               'users below the Base DN; e.g. ' .
                               '<b>objectclass=person</b>, ' .
                               'or maybe <b>objectclass=calciumUser</b>');
    my $gBaseNote = $i18n->get ('Base for Group searches - e.g. ' .
                               '<b>ou=groups, dc=domain, dc=com, c=us</b>');
    my $ugNote   = $i18n->get ('Attribute name for groups, e.g. <b>cn</b>');
    my $gfNote   = $i18n->get ('Search filter for enumerating all User ' .
                               'groups, e.g. <b>objectclass=groupOfNames</b>');
    my $ugmNote  = $i18n->get ('Attribute name of group object which identifies'
                               . ' members, e.g. <b>member</b>');
    my $umaNote  = $i18n->get ('Attribute name of user object which identifies '
                               . 'their groups, e.g. <b>groupMembership</b>');
    my $userNote = $i18n->get ('If searches require authentication, '
                               . 'enter an appropriate username/password.'
                               . '<br>(Most servers do '
                               . 'not need this; if not, leave blank.)');

    my @rows =
        ($cgi->Tr ($cgi->td ('<b><nobr>' . $i18n->get ('Host') .
                             ': </nobr></b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPHost',
                                              -default => $host,
                                              -size    => 40)),
                   $cgi->td ("<small>$serverNote</small>")),
         $cgi->Tr ($cgi->td ('<b><nobr>' . $i18n->get ('Port') .
                             ': </nobr></b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPPort',
                                              -default => $port,
                                              -size    => 4)),
                   $cgi->td ("<small>$portNote</small>")),
         $cgi->Tr ($cgi->td ('<b><nobr>' . $i18n->get ('Version') .
                             ': </nobr></b>'),
                   $cgi->td ($cgi->popup_menu (-name    => 'LDAPVersion',
                                               -default => $version,
                                               -Values  => [2, 3]))),
         $cgi->Tr ($cgi->td ({-colspan => 3, -align => 'center'}, '<b>' .
                             $i18n->get ('Finding Users') . '</b>')),
         $cgi->Tr ($cgi->td ('<b><nobr>' . $i18n->get ('Base DN') .
                             ': </nobr></b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPBase',
                                              -default => $baseDN,
                                              -size    => 40)),
                   $cgi->td ("<small>$baseNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Filter for User Search') .
                             ': </b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPUserFilter',
                                              -default => $userFilter,
                                              -size    => 40)),
                   $cgi->td ("<small>$ufNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('User Attribute Name') .
                             ': </b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPUserAttrib',
                                              -default => $userAttrib,
                                              -size    => 10)),
                   $cgi->td ("<small>$uaNote</small>")),
         $cgi->Tr ($cgi->td ({-colspan => 3, -align => 'center'}, '<b>' .
                             $i18n->get ('Finding User Groups') . '</b>')),
         $cgi->Tr ($cgi->td ('<b><nobr>' . $i18n->get ('Base DN') .
                             ': </nobr></b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPGroupBase',
                                              -default => $groupBaseDN,
                                              -size    => 40)),
                   $cgi->td ("<small>$gBaseNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Filter for Group Search') .
                             ': </b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPGroupFilter',
                                              -default => $groupFilter,
                                              -size    => 40)),
                   $cgi->td ("<small>$gfNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Group Attribute Name') .
                             ': </b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPGroupAttrib',
                                              -default => $groupAttrib,
                                              -size    => 10)),
                   $cgi->td ("<small>$ugNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Group Member Attribute Name')
                             . ': </b>'),
                   $cgi->td ($cgi->textfield (-name    =>
                                                  'LDAPGroupMemberAttrib',
                                              -default => $groupMemberAttrib,
                                              -size    => 15)),
                   $cgi->td ("<small>$ugmNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get('User Membership Attribute Name')
                             . ': </b>'),
                   $cgi->td ($cgi->textfield (-name    =>
                                                  'LDAPUserMembershipAttrib',
                                              -default => $userMembershipAttrib,
                                              -size    => 15)),
                   $cgi->td ("<small>$umaNote</small>")),
         $cgi->Tr ($cgi->td ('&nbsp;')),
         $cgi->Tr ($cgi->td ({-colspan => 2},
                             'Login for Search')),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Username') .
                             ': </b>'),
                   $cgi->td ($cgi->textfield (-name    => 'LDAPUsername',
                                              -default => $ldapUser,
                                              -size    => 30)),
                   $cgi->td ({-rowSpan => 2}, "<small>$userNote</small>")),
         $cgi->Tr ($cgi->td ('<b>', $i18n->get ('Password') .
                             ': </b>'),
                   $cgi->td ($cgi->password_field (-name    => 'LDAPPassword',
                                                   -default => $ldapPass,
                                                   -size    => 30))),
        );
    unshift @rows, $cgi->Tr ($cgi->th ({-colspan => 3}, '<b><big>' .
                                       $i18n->get ('Directory Settings') .
                                      '</big></b>'));
    print $cgi->table (@rows);
    print '<center><br>';
    print $cgi->submit (-name  => 'Save',
                        -value => $i18n->get ('Save Settings'));
    print '</center>';
    print $cgi->hidden (-name => 'Op', -value => __PACKAGE__);

    print '<hr width="95%">';
    print '<blockquote>';
    print $i18n->get ('After saving any changes above, you should make sure ' .
                      'that authenticating against the server works.<br>' .
                      'Enter a valid username and password, ' .
                      'then press the "Test Authentication" button:');

    print $cgi->table
        ($cgi->Tr ($cgi->td ($cgi->b ($i18n->get ('Username: '))),
                   $cgi->td ($cgi->textfield (-name => 'TestName',
                                              -size => 20))),
         $cgi->Tr ($cgi->td ($cgi->b ($i18n->get ('Password: '))),
                   $cgi->td ($cgi->password_field (-name => 'TestPass',
                                                   -size => 20))),
         $cgi->Tr ($cgi->td ({colspan => 2, align => 'center'},
                             $cgi->submit (-name  => 'Test',
                                           -value =>
                                       $i18n->get ('Test Authentication')))));

    print '<p>';
    print $i18n->get ('Or, you can try listing all of the existing users or ' .
                      'groups in the directory:');
    print '<br>';
    print $cgi->submit (-name  => 'TestEnum',
                        -value => $i18n->get ('Test User Enumeration'));
    print '&nbsp;&nbsp;';
    print $cgi->submit (-name  => 'TestEnumGroup',
                        -value => $i18n->get ('Test Group Enumeration'));
    print '</p>';
    print '<p>';
    print $i18n->get ('Or, you can try listing all users in a group; ' .
                      'enter group name: ');
    print '&nbsp;';
    print $cgi->textfield (-name => 'TestGroupName',
                           -size => 20);
    print '&nbsp;';
    print $cgi->submit (-name  => 'TestGroupMembers',
                        -value => $i18n->get ('Test Group Members'));
    print '</p>';

    print '<p>';
    print $i18n->get ('Or, you can try listing all groups for a user; ' .
                      'enter user name: ');
    print '&nbsp;';
    print $cgi->textfield (-name => 'TestUserName',
                           -size => 20);
    print '&nbsp;';
    print $cgi->submit (-name  => 'TestGroupsForUser',
                        -value => $i18n->get ('Test User Groups'));
    print '</p>';
    print '</blockquote>';

    print "<hr>";
    print $cgi->submit (-name  => 'Cancel',
                        -value => $i18n->get ('Done'));

    print $cgi->endform;
    print $cgi->end_html;
}

sub auditString {
    my ($self, $short) = @_;
    return unless $self->{audit_formsaved};
    my $line = $self->SUPER::auditString ($short);

    my $info;

    foreach (qw /LDAPHost LDAPPort LDAPVersion LDAPBase LDAPAuthDomain
                 LDAPUsername LDAPPassword/) {
        my $old = $self->{"audit_$_"} || "''";
        my $new = $self->prefs->$_()  || "''";
        next if ($old eq $new);
        $info .= " [$_ $old -> $new]";
    }

    return unless $info;
    return $line . $info;
}

1;
